From 80079e14e3f92accb309c25778fe87e3916e0143 Mon Sep 17 00:00:00 2001 From: Charles Krinke Date: Sat, 7 Jun 2008 15:43:16 +0000 Subject: Mantis#1475. Thank you kindly, Kinoc for a patch that: This patch brings the Yield Prolog in sync with the YP r669. Biggest item is support for functions asserta and assertz , providing dynamic databases. --- .../DotNetEngine/Compiler/YieldProlog/YP.cs | 270 ++++++++++++++++++--- 1 file changed, 237 insertions(+), 33 deletions(-) (limited to 'OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/YieldProlog/YP.cs') diff --git a/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/YieldProlog/YP.cs b/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/YieldProlog/YP.cs index a03cd30..68cfd3e 100644 --- a/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/YieldProlog/YP.cs +++ b/OpenSim/Region/ScriptEngine/DotNetEngine/Compiler/YieldProlog/YP.cs @@ -149,6 +149,9 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog YP.getValue(((Functor2)term)._arg2) == Atom.NIL) // Assume it is a char type like "a". term = YP.getValue(((Functor2)term)._arg1); + if (term is Variable) + throw new PrologException(Atom.a("instantiation_error"), + "Expected a number but the argument is an unbound variable"); return Convert.ToDouble(term); } @@ -982,11 +985,22 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog return YP.getValue(Term) is Atom; } + public static bool integer(object Term) + { + // Debug: Should exhaustively check for all integer types. + return getValue(Term) is int; + } + + // Use isFloat instead of float because it is a reserved keyword. + public static bool isFloat(object Term) + { + // Debug: Should exhaustively check for all float types. + return getValue(Term) is double; + } + public static bool number(object Term) { - Term = getValue(Term); - // Debug: Should exhaustively check for all number types. - return Term is int || Term is double; + return YP.integer(Term) || YP.isFloat(Term); } public static bool atomic(object Term) @@ -1060,6 +1074,11 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog _outputStream = Console.Out; } + public static IEnumerable current_output(object Stream) + { + return YP.unify(Stream, _outputStream); + } + public static void write(object x) { x = YP.getValue(x); @@ -1108,6 +1127,93 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog return YP.unify(code, _inputStream.Read()); } + public static void asserta(object Term, Type declaringClass) + { + assertDynamic(Term, declaringClass, true); + } + + public static void assertz(object Term, Type declaringClass) + { + assertDynamic(Term, declaringClass, false); + } + + public static void assertDynamic(object Term, Type declaringClass, bool prepend) + { + Term = getValue(Term); + if (Term is Variable) + throw new PrologException("instantiation_error", "Term to assert is an unbound variable"); + + Variable.CopyStore copyStore = new Variable.CopyStore(); + object TermCopy = makeCopy(Term, copyStore); + object Head, Body; + if (TermCopy is Functor2 && ((Functor2)TermCopy)._name == Atom.RULE) + { + Head = YP.getValue(((Functor2)TermCopy)._arg1); + Body = YP.getValue(((Functor2)TermCopy)._arg2); + } + else + { + Head = TermCopy; + Body = Atom.a("true"); + } + + Atom name = getFunctorName(Head) as Atom; + if (name == null) + // name is a non-Atom, such as a number. + throw new PrologException + (new Functor2("type_error", Atom.a("callable"), Head), "Term to assert is not callable"); + object[] args = getFunctorArgs(Head); + if (!isDynamic(name, args.Length)) + throw new PrologException + (new Functor3("permission_error", Atom.a("modify"), Atom.a("static_procedure"), + new Functor2(Atom.SLASH, name, args.Length)), + "Assert cannot modify static predicate " + name + "/" + args.Length); + + if (copyStore.getNUniqueVariables() == 0 && Body == Atom.a("true")) + { + // Debug: Until IndexedAnswers supports prepend, compile the fact so we can prepend it below. + if (!prepend) + { + // This is a fact with no unbound variables + // assertFact uses IndexedAnswers, so don't we don't need to compile. + assertFact(name, args); + return; + } + } + + IClause clause = YPCompiler.compileAnonymousClause(Head, Body, declaringClass); + + // Add the clause to the entry in _predicatesStore. + NameArity nameArity = new NameArity(name, args.Length); + List clauses; + if (!_predicatesStore.TryGetValue(nameArity, out clauses)) + // Create an entry for the nameArity. + _predicatesStore[nameArity] = (clauses = new List()); + + if (prepend) + clauses.Insert(0, clause); + else + clauses.Add(clause); + } + + private static bool isDynamic(Atom name, int arity) + { + if (arity == 2 && (name == Atom.a(",") || name == Atom.a(";") || name == Atom.DOT)) + return false; + // Use the same mapping to static predicates in YP as the compiler. + foreach (bool l1 in YPCompiler.functorCallYPFunctionName(name, arity, new Variable())) + return false; + // Debug: Do we need to check if name._module is null? + return true; + } + + /// + /// Assert values at the end of the set of facts for the predicate with the + /// name and with arity values.Length. + /// + /// must be an Atom + /// the array of arguments to the fact predicate. + /// It is an error if an value has an unbound variable. public static void assertFact(Atom name, object[] values) { NameArity nameArity = new NameArity(name, values.Length); @@ -1130,7 +1236,15 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog indexedAnswers.addAnswer(values); } - public static IEnumerable matchFact(Atom name, object[] arguments) + /// + /// Match all clauses of the dynamic predicate with the name and with arity + /// arguments.Length. + /// It is an error if the predicate is not defined. + /// + /// must be an Atom + /// an array of arity number of arguments + /// an iterator which you can use in foreach + public static IEnumerable matchDynamic(Atom name, object[] arguments) { List clauses; if (!_predicatesStore.TryGetValue(new NameArity(name, arguments.Length), out clauses)) @@ -1147,20 +1261,46 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog /// /// Call match(arguments) for each IClause in clauses. We make this a separate - /// function so that matchFact itself does not need to be an iterator object. + /// function so that matchDynamic itself does not need to be an iterator object. /// /// /// /// private static IEnumerable matchAllClauses(List clauses, object[] arguments) { + // Debug: If the clause asserts another clause into this same predicate, the iterator + // over clauses will be corrupted. Should we take the time to copy clauses? foreach (IClause clause in clauses) { foreach (bool lastCall in clause.match(arguments)) + { yield return false; + if (lastCall) + // This happens after a cut in a clause. + yield break; + } } } + /// + /// This is deprecated and just calls matchDynamic. This matches all clauses, + /// not just the ones defined with assertFact. + /// + /// + /// + /// + public static IEnumerable matchFact(Atom name, object[] arguments) + { + return matchDynamic(name, arguments); + } + + /// + /// This actually searches all clauses, not just + /// the ones defined with assertFact, but we keep the name for + /// backwards compatibility. + /// + /// must be an Atom + /// an array of arity number of arguments public static void retractFact(Atom name, object[] arguments) { NameArity nameArity = new NameArity(name, arguments.Length); @@ -1219,40 +1359,18 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog /// public static IEnumerable getIterator(object Goal, Type declaringClass) { + Goal = YP.getValue(Goal); + if (Goal is Variable) + throw new PrologException("instantiation_error", "Goal to call is an unbound variable"); #if true List variableSetList = new List(); addUniqueVariables(Goal, variableSetList); Variable[] variableSet = variableSetList.ToArray(); - object Head = Functor.make("function", variableSet); - object Rule = new Functor2(Atom.RULE, Head, Goal); - object RuleList = ListPair.make(new Functor2(Atom.F, Rule, Atom.NIL)); - StringWriter functionCode = new StringWriter(); - TextWriter saveOutputStream = _outputStream; - try - { - tell(functionCode); - Variable FunctionCode = new Variable(); - foreach (bool l1 in YPCompiler.makeFunctionPseudoCode(RuleList, FunctionCode)) - { - if (YP.termEqual(FunctionCode, Atom.a("getDeclaringClass"))) - // Ignore getDeclaringClass since we have access to the one passed in. - continue; - - // Debug: should check if FunctionCode is a single call. - YPCompiler.convertFunctionCSharp(FunctionCode); - } - told(); - } - finally - { - // Restore after calling tell. - _outputStream = saveOutputStream; - } - return YPCompiler.compileAnonymousFunction - (functionCode.ToString(), variableSet.Length, declaringClass).match(variableSet); + // Use Atom.F since it is ignored. + return YPCompiler.compileAnonymousClause + (Functor.make(Atom.F, variableSet), Goal, declaringClass).match(variableSet); #else - Goal = YP.getValue(Goal); Atom name; object[] args; while (true) @@ -1436,5 +1554,91 @@ namespace OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog throw new NotImplementedException(); } } + + /// + /// An enumerator that wraps another enumerator in order to catch a PrologException. + /// + public class Catch : IEnumerator, IEnumerable + { + private IEnumerator _enumerator; + private PrologException _exception = null; + + public Catch(IEnumerable iterator) + { + _enumerator = iterator.GetEnumerator(); + } + + /// + /// Call _enumerator.MoveNext(). If it throws a PrologException, set _exception + /// and return false. After this returns false, call unifyExceptionOrThrow. + /// Assume that, after this returns false, it will not be called again. + /// + /// + public bool MoveNext() + { + try + { + return _enumerator.MoveNext(); + } + catch (PrologException exception) + { + _exception = exception; + return false; + } + } + + /// + /// Call this after MoveNext() returns false to check for an exception. If + /// MoveNext did not get a PrologException, don't yield. + /// Otherwise, unify the exception with Catcher and yield so the caller can + /// do the handler code. However, if can't unify with Catcher then throw the exception. + /// + /// + /// + public IEnumerable unifyExceptionOrThrow(object Catcher) + { + if (_exception != null) + { + bool didUnify = false; + foreach (bool l1 in YP.unify(_exception._term, Catcher)) + { + didUnify = true; + yield return false; + } + if (!didUnify) + throw _exception; + } + } + + public IEnumerator GetEnumerator() + { + return (IEnumerator)this; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public bool Current + { + get { return _enumerator.Current; } + } + + object IEnumerator.Current + { + get { return _enumerator.Current; } + } + + public void Dispose() + { + _enumerator.Dispose(); + } + + public void Reset() + { + throw new NotImplementedException(); + } + } } } -- cgit v1.1