#region Copyright
// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (C) 2015 Ian Horswill
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in the
// Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// --------------------------------------------------------------------------------------------------------------------
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using UnityEngine;
namespace Prolog
{
///
/// Read/eval/print loop for MinimaLisp
///
public class Repl
{
#region Global flags
///
/// If true, Repls start in Prolog mode
///
public static bool DefaultToPrologMode
{
get;
set;
}
///
/// If true, Repls start in timing mode
///
public static bool DefaultToTimingMode
{
get;
set;
}
#endregion
#region Instance variables
///
/// Raw input stream for reading input
///
public Stream InputStream { get; set; }
///
/// TextWriter for writing output
///
public TextWriter Output { get; set; }
///
/// Callback for when KB is changed from one gameobject to another
///
public Action OnChangeKB;
///
/// Outputs a newline to the current repl's output stream.
///
public void NewLine()
{
Output.WriteLine();
}
///
/// Prevents REPL from automatically printing results of commands
///
public bool SuppressReturnValue { get; set; }
public GameObject CurrentGameObject { get; set; }
public KnowledgeBase CurrentKnowledgeBase
{
get
{
if (CurrentGameObject == null)
return KnowledgeBase.Global;
return CurrentGameObject.KnowledgeBase();
}
}
///
/// Last exception object thrown by user code
///
public Exception LastException { get; private set; }
#endregion
public void ProcessCommandLine(string command)
{
command = command.Trim();
try
{
string[] parsed = command.Split();
switch ((parsed.Length > 0) ? parsed[0].Trim(new [] {'.'}) : "")
{
case "quit":
case "exit":
case "halt":
Application.Quit();
break;
case "within":
SwitchComponents(command);
break;
case "global":
this.SwitchComponents("global");
break;
case "make":
ReloadModifiedSourceFilesForAllComponents();
break;
default:
PrologModeCommandLineHandler(command);
break;
}
}
catch (EndOfStreamException)
{
//CaptureStack();
StartErrorReport();
Output.WriteLine(
"Syntax error: incomplete expression. Command line ended before the end of an expression or the file being loaded is incomplete.");
}
catch (Exception e)
{
LastException = e;
//CaptureStack();
while (e is TargetInvocationException)
e = e.InnerException;
var s = e as SyntaxErrorException;
StartErrorReport();
if (currentException == InnermostException(e))
Output.Write("\n{0}", currentExceptionSourceLocation);
if (s != null)
{
string message = s.Message;
Output.WriteLine(
"{0}:\n{1}{2}In expression:",
s.GetType().Name,
message,
message.EndsWith("\n") ? "" : "\n");
NewLine();
}
else
{
Output.WriteLine("{0}: {1}", e.GetType().Name, e.Message);
}
}
if (!SuppressReturnValue)
{
NewLine();
Prompt();
}
}
private void Prompt()
{
//throw new NotImplementedException();
}
private void ReloadModifiedSourceFilesForAllComponents()
{
throw new NotImplementedException();
}
private void SwitchComponents(string command)
{
var gameObjectName = command.Replace("within", "").Trim();
if (gameObjectName.EndsWith("."))
gameObjectName = gameObjectName.Substring(0, gameObjectName.Length - 1);
if (gameObjectName == "global")
gameObjectName = "GlobalKB";
var newGameObject = GameObject.Find(gameObjectName);
if (newGameObject == null)
newGameObject = GameObject.Find(char.ToUpper(gameObjectName[0]) + gameObjectName.Substring(1));
if (newGameObject == null)
{
Output.WriteLine("No GameObject named {0}", gameObjectName);
return;
}
var kb = newGameObject.GetComponent();
if (kb == null)
{
Output.WriteLine("Cannot change GameObjects: {0} has no KB.", gameObjectName);
return;
}
CurrentGameObject = newGameObject;
PrologContext.KnowledgeBase = kb.KnowledgeBase;
PrologContext.Reset(CurrentGameObject);
if (OnChangeKB != null)
OnChangeKB(kb.KnowledgeBase);
Output.WriteLine("Now using the KB of {0}", gameObjectName);
}
#region Prolog mode command processing
IEnumerator prologModeAnswerStream;
readonly List freeVariablesInCurrentQuery = new List();
bool foundOneSolution;
PrologContext prologContext;
PrologContext PrologContext
{
get { return prologContext ?? (prologContext = PrologContext.Allocate(CurrentKnowledgeBase, CurrentGameObject)); }
}
void PrologModeCommandLineHandler(string command)
{
string trimmed = command.Trim(' ', '.');
switch (trimmed)
{
case "time":
timeCommands = true;
break;
case "notime":
timeCommands = false;
break;
case "trace":
CurrentKnowledgeBase.Trace = true;
break;
case "notrace":
CurrentKnowledgeBase.Trace = false;
break;
case ";":
case "next":
if (prologModeAnswerStream == null)
Output.WriteLine("No pending goal.");
else
PrintNextQuerySolution();
break;
case "stack":
DumpPrologStack(PrologContext);
break;
case "errorstack":
if (PrologContext.LastExceptionContext == null)
Output.WriteLine("No asynchronous exceptions have been caught by the prolog interpreter. Did you mean the 'stack' command?");
DumpPrologStack(PrologContext.LastExceptionContext);
break;
default:
StartNewQuery(command);
break;
}
}
private void DumpPrologStack(PrologContext context)
{
if (context.GoalStackDepth > 0)
for (ushort i = 0; i <= context.CurrentFrame; i++)
{
Structure g = context.GoalStackGoal(i);
if (g != null)
{
ushort frame = i;
while (frame != 0)
{
//Output.Write("{0}/", frame);
Output.Write(" ");
frame = context.GoalStackParent(frame);
}
//Output.Write(' ');
//Output.Write("{0}<{1}: ", i, PrologContext.GoalStackParent(i));
Output.WriteLine(Term.ToStringInPrologFormat(g));
}
}
else
Output.WriteLine("Goal stack is empty.");
}
private void StartNewQuery(string command)
{
try
{
foundOneSolution = false;
PrologContext.Reset(CurrentGameObject);
PrologContext.Output = Output;
PrologContext.PushGoalStack(Symbol.Intern("parse"), new object[] { command }, 0);
object query = ISOPrologReader.ReadAndGetFreeVariables(command, freeVariablesInCurrentQuery);
if (query == null) throw new ArgumentNullException("command", "Prolog query may not be null.");
var goal = Term.Structurify(query, "Not a valid Prolog goal.");
if (goal.IsFunctor(Symbol.PrologListConstructor, 2) && goal.Argument(1) == null)
goal = new Structure("reconsult", goal.Argument(0));
prologModeAnswerStream = PrologContext.ResetStackAndProve(goal).GetEnumerator();
PrintNextQuerySolution();
}
catch (Exception)
{
StartErrorReport();
prologModeAnswerStream = null;
if (PrologContext.GoalStackDepth>0)
Output.WriteLine("In goal: {0}", PrologContext.GoalStackTop);
throw;
}
}
private readonly Stopwatch timer = new Stopwatch();
private bool timeCommands;
void PrintNextQuerySolution()
{
try
{
PrologContext.ResetStepLimit();
timer.Reset();
timer.Start();
bool gotOne = prologModeAnswerStream.MoveNext();
timer.Stop();
if (gotOne)
{
if (freeVariablesInCurrentQuery.Count > 0)
{
foundOneSolution = true;
foreach (LogicVariable v in freeVariablesInCurrentQuery)
Output.WriteLine("{0} = {1}", v.Name, Term.ToStringInPrologFormat(Term.Deref(v)));
}
else
{
Output.WriteLine("yes");
prologModeAnswerStream = null;
}
}
else
{
Output.WriteLine(foundOneSolution?"no more solutions found":"no");
prologModeAnswerStream = null;
}
if (timeCommands)
{
double ms = timer.Elapsed.TotalMilliseconds;
Output.WriteLine("{0:###}ms, {1} inference steps, {2:0.##} KLIPS.\n", ms, prologContext.StepsUsed, prologContext.StepsUsed/ms);
}
}
catch (Exception)
{
prologModeAnswerStream = null;
throw;
}
}
#endregion
#region Exception handling
//private static bool suppressStyleChecks;
void StartErrorReport()
{
Output.Write("\nError:\n");
//suppressStyleChecks = true;
}
private static Exception currentException;
private static string currentExceptionSourceLocation;
///
/// Prints an error message from loading a file at startup time.
/// Needed because there may not be a Repl to do the printing.
///
public static void PrintExceptionToConsole(Exception e)
{
e = InnermostException(e);
Console.Write("Error:");
if (currentException == e)
Console.Write("\n{0}", currentExceptionSourceLocation);
Console.WriteLine(e.Message);
//suppressStyleChecks = true;
}
///
/// Unwindws InnerExceptions until it finds the original exception that was thrown.
///
static Exception InnermostException(Exception e)
{
if (e.InnerException == null)
return e;
return InnermostException(e.InnerException);
}
///
/// Declare that the exception was generated at the specified line of the CurrentSourceFile.
///
public static void RecordExceptionSourceLocation(Exception e, int lineNumber)
{
e = InnermostException(e);
if (currentException != e) // Only accept the first attempt to define the location of an exception.
{
currentException = e;
currentExceptionSourceLocation = string.Format("{0}:{1} ", Prolog.CurrentSourceFile, lineNumber);
}
//suppressStyleChecks = true;
}
#endregion
}
}