From 323ada012d3bed0c6f7a6d5d0ee14b409b7457c7 Mon Sep 17 00:00:00 2001 From: Charles Krinke Date: Wed, 13 Aug 2008 14:13:49 +0000 Subject: Mantis#1931. Thank you kindly, Kinoc for a patch that: * Yield Prolog 1.0.1 Released : it passes all but 9 of the 421 tests in the ISO Prolog test suite (97.8%) . * support dynamic predicates and rules. * support 'import' to use external static functions improves connection to C# functions * Matches Yield Prolog r831 --- .../Shared/Api/Runtime/YieldProlog/YP.cs | 319 +++++++++++++++++++-- 1 file changed, 303 insertions(+), 16 deletions(-) (limited to 'OpenSim/Region/ScriptEngine/Shared/Api/Runtime/YieldProlog/YP.cs') diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/YieldProlog/YP.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/YieldProlog/YP.cs index 76e307a..97c9087 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/YieldProlog/YP.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/YieldProlog/YP.cs @@ -52,6 +52,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog private static TextWriter _outputStream = System.Console.Out; private static TextReader _inputStream = System.Console.In; private static IndexedAnswers _operatorTable = null; + private static Dictionary _prologFlags = new Dictionary(); + public const int MAX_ARITY = 255; /// /// An IClause is used so that dynamic predicates can call match. @@ -62,6 +64,16 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog IEnumerable clause(object Head, object Body); } + /// + /// If value is a Variable, then return its getValue. Otherwise, just + /// return value. You should call YP.getValue on any object that + /// may be a Variable to get the value to pass to other functions in + /// your system that are not part of Yield Prolog, such as math functions + /// or file I/O. + /// For more details, see http://yieldprolog.sourceforge.net/tutorial1.html + /// + /// + /// public static object getValue(object value) { if (value is Variable) @@ -70,6 +82,17 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog return value; } + /// + /// If arg1 or arg2 is an object with a unify method (such as Variable or + /// Functor) then just call its unify with the other argument. The object's + /// unify method will bind the values or check for equals as needed. + /// Otherwise, both arguments are "normal" (atomic) values so if they + /// are equal then succeed (yield once), else fail (don't yield). + /// For more details, see http://yieldprolog.sourceforge.net/tutorial1.html + /// + /// + /// + /// public static IEnumerable unify(object arg1, object arg2) { arg1 = getValue(arg1); @@ -622,6 +645,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog if (args.Length == 0) // Return the Name, even if it is not an Atom. return YP.unify(Term, Name); + if (args.Length > MAX_ARITY) + throw new PrologException + (new Functor1("representation_error", Atom.a("max_arity")), + "Functor arity " + args.Length + " may not be greater than " + MAX_ARITY); if (!atom(Name)) throw new PrologException (new Functor2("type_error", Atom.a("atom"), Name), @@ -666,6 +693,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog } else { + if ((int)Arity > MAX_ARITY) + throw new PrologException + (new Functor1("representation_error", Atom.a("max_arity")), + "Functor arity " + Arity + " may not be greater than " + MAX_ARITY); if (!(FunctorName is Atom)) throw new PrologException (new Functor2("type_error", Atom.a("atom"), FunctorName), "FunctorName is not an atom"); @@ -903,8 +934,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog _operatorTable.addAnswer(new object[] { 20, Atom.a("xfx"), Atom.a("<--") }); } - foreach (bool l1 in _operatorTable.match(new object[] { Priority, Specifier, Operator })) - yield return false; + return _operatorTable.match(new object[] { Priority, Specifier, Operator }); } public static IEnumerable atom_length(object atom, object Length) @@ -1491,9 +1521,22 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog return Term is Functor1 || Term is Functor2 || Term is Functor3 || Term is Functor; } + /// + /// If input is a TextReader, use it. If input is an Atom or String, create a StreamReader with the + /// input as the filename. If input is a Prolog list, then read character codes from it. + /// + /// public static void see(object input) { input = YP.getValue(input); + if (input is Variable) + throw new PrologException(Atom.a("instantiation_error"), "Arg is an unbound variable"); + + if (input == null) + { + _inputStream = null; + return; + } if (input is TextReader) { _inputStream = (TextReader)input; @@ -1509,21 +1552,48 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog _inputStream = new StreamReader((String)input); return; } + else if (input is Functor2 && ((Functor2)input)._name == Atom.DOT) + { + _inputStream = new CodeListReader(input); + return; + } else - throw new InvalidOperationException("Can't open stream for " + input); + throw new PrologException + (new Functor2("domain_error", Atom.a("stream_or_alias"), input), + "Input stream specifier not recognized"); } public static void seen() { + if (_inputStream == null) + return; if (_inputStream == Console.In) return; _inputStream.Close(); _inputStream = Console.In; } + public static IEnumerable current_input(object Stream) + { + return YP.unify(Stream, _inputStream); + } + + /// + /// If output is a TextWriter, use it. If output is an Atom or a String, create a StreamWriter + /// with the input as the filename. + /// + /// public static void tell(object output) { output = YP.getValue(output); + if (output is Variable) + throw new PrologException(Atom.a("instantiation_error"), "Arg is an unbound variable"); + + if (output == null) + { + _outputStream = null; + return; + } if (output is TextWriter) { _outputStream = (TextWriter)output; @@ -1540,11 +1610,15 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog return; } else - throw new InvalidOperationException("Can't open stream for " + output); + throw new PrologException + (new Functor2("domain_error", Atom.a("stream_or_alias"), output), + "Can't open stream for " + output); } public static void told() { + if (_outputStream == null) + return; if (_outputStream == Console.Out) return; _outputStream.Close(); @@ -1558,6 +1632,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog public static void write(object x) { + if (_outputStream == null) + return; x = YP.getValue(x); if (x is double) _outputStream.Write(doubleToString((double)x)); @@ -1591,6 +1667,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog public static void put_code(object x) { + if (_outputStream == null) + return; if (var(x)) throw new PrologException(Atom.a("instantiation_error"), "Arg 1 is an unbound variable"); int xInt; @@ -1602,11 +1680,16 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog public static void nl() { + if (_outputStream == null) + return; _outputStream.WriteLine(); } public static IEnumerable get_code(object code) { + if (_inputStream == null) + return YP.unify(code, -1); + else return YP.unify(code, _inputStream.Read()); } @@ -1763,7 +1846,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog /// /// 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. + /// If the predicate is not defined, return the result of YP.unknownPredicate. /// /// must be an Atom /// an array of arity number of arguments @@ -1772,11 +1855,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog { List clauses; if (!_predicatesStore.TryGetValue(new NameArity(name, arguments.Length), out clauses)) - throw new PrologException - (new Functor2 - (Atom.a("existence_error"), Atom.a("procedure"), - new Functor2(Atom.SLASH, name, arguments.Length)), - "Undefined predicate: " + name + "/" + arguments.Length); + return unknownPredicate(name, arguments.Length, + "Undefined dynamic predicate: " + name + "/" + arguments.Length); if (clauses.Count == 1) // Usually there is only one clause, so return it without needing to wrap it in an iterator. @@ -1809,6 +1889,34 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog } /// + /// If _prologFlags["unknown"] is fail then return fail(), else if + /// _prologFlags["unknown"] is warning then write the message to YP.write and + /// return fail(), else throw a PrologException for existence_error. . + /// + /// + /// + /// + /// + public static IEnumerable unknownPredicate(Atom name, int arity, string message) + { + establishPrologFlags(); + + if (_prologFlags["unknown"] == Atom.a("fail")) + return fail(); + else if (_prologFlags["unknown"] == Atom.a("warning")) + { + write(message); + nl(); + return fail(); + } + else + throw new PrologException + (new Functor2 + (Atom.a("existence_error"), Atom.a("procedure"), + new Functor2(Atom.SLASH, name, arity)), message); + } + + /// /// This is deprecated and just calls matchDynamic. This matches all clauses, /// not just the ones defined with assertFact. /// @@ -1951,7 +2059,17 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog _predicatesStore[nameArity] = new List(); } - public static IEnumerable current_predicate(object NameSlashArity) + /// + /// If NameSlashArity is var, match with all the dynamic predicates using the + /// Name/Artity form. + /// If NameSlashArity is not var, check if the Name/Arity exists as a static or + /// dynamic predicate. + /// + /// + /// if not null, used to resolve references to the default + /// module Atom.a("") + /// + public static IEnumerable current_predicate(object NameSlashArity, Type declaringClass) { NameSlashArity = YP.getValue(NameSlashArity); // First check if Name and Arity are nonvar so we can do a direct lookup. @@ -1976,7 +2094,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog (new Functor2("domain_error", Atom.a("not_less_than_zero"), arity), "Arity may not be less than zero"); - if (_predicatesStore.ContainsKey(new NameArity((Atom)name, (int)arity))) + if (YPCompiler.isCurrentPredicate((Atom)name, (int)arity, declaringClass)) // The predicate is defined. yield return false; } @@ -1991,6 +2109,18 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog } } + /// + /// Return true if the dynamic predicate store has an entry for the predicate + /// with name and arity. + /// + /// + /// + /// + public static bool isDynamicCurrentPredicate(Atom name, int arity) + { + return _predicatesStore.ContainsKey(new NameArity(name, arity)); + } + public static void abolish(object NameSlashArity) { NameSlashArity = YP.getValue(NameSlashArity); @@ -2019,6 +2149,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog throw new PrologException (new Functor2("domain_error", Atom.a("not_less_than_zero"), arity), "Arity may not be less than zero"); + if ((int)arity > MAX_ARITY) + throw new PrologException + (new Functor1("representation_error", Atom.a("max_arity")), + "Arity may not be greater than " + MAX_ARITY); if (isSystemPredicate((Atom)name, (int)arity)) throw new PrologException @@ -2077,7 +2211,108 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog { throw new PrologException(Term); } + /// + /// This must be called by any function that uses YP._prologFlags to make sure + /// the initial defaults are loaded. + /// + private static void establishPrologFlags() + { + if (_prologFlags.Count > 0) + // Already established. + return; + + // List these in the order they appear in the ISO standard. + _prologFlags["bounded"] = Atom.a("true"); + _prologFlags["max_integer"] = Int32.MaxValue; + _prologFlags["min_integer"] = Int32.MinValue; + _prologFlags["integer_rounding_function"] = Atom.a("toward_zero"); + _prologFlags["char_conversion"] = Atom.a("off"); + _prologFlags["debug"] = Atom.a("off"); + _prologFlags["max_arity"] = MAX_ARITY; + _prologFlags["unknown"] = Atom.a("error"); + _prologFlags["double_quotes"] = Atom.a("codes"); + } + + public static IEnumerable current_prolog_flag(object Key, object Value) + { + establishPrologFlags(); + + Key = YP.getValue(Key); + Value = YP.getValue(Value); + + if (Key is Variable) + { + // Bind all key values. + foreach (string key in _prologFlags.Keys) + { + foreach (bool l1 in YP.unify(Key, Atom.a(key))) + { + foreach (bool l2 in YP.unify(Value, _prologFlags[key])) + yield return false; + } + } + } + else + { + if (!(Key is Atom)) + throw new PrologException + (new Functor2("type_error", Atom.a("atom"), Key), "Arg 1 Key is not an atom"); + if (!_prologFlags.ContainsKey(((Atom)Key)._name)) + throw new PrologException + (new Functor2("domain_error", Atom.a("prolog_flag"), Key), + "Arg 1 Key is not a recognized flag"); + + foreach (bool l1 in YP.unify(Value, _prologFlags[((Atom)Key)._name])) + yield return false; + } + } + + public static void set_prolog_flag(object Key, object Value) + { + establishPrologFlags(); + + Key = YP.getValue(Key); + Value = YP.getValue(Value); + + if (Key is Variable) + throw new PrologException(Atom.a("instantiation_error"), + "Arg 1 Key is an unbound variable"); + if (Value is Variable) + throw new PrologException(Atom.a("instantiation_error"), + "Arg 1 Key is an unbound variable"); + if (!(Key is Atom)) + throw new PrologException + (new Functor2("type_error", Atom.a("atom"), Key), "Arg 1 Key is not an atom"); + + string keyName = ((Atom)Key)._name; + if (!_prologFlags.ContainsKey(keyName)) + throw new PrologException + (new Functor2("domain_error", Atom.a("prolog_flag"), Key), + "Arg 1 Key " + Key + " is not a recognized flag"); + + bool valueIsOK = false; + if (keyName == "char_conversion") + valueIsOK = (Value == _prologFlags[keyName]); + else if (keyName == "debug") + valueIsOK = (Value == _prologFlags[keyName]); + else if (keyName == "unknown") + valueIsOK = (Value == Atom.a("fail") || Value == Atom.a("warning") || + Value == Atom.a("error")); + else if (keyName == "double_quotes") + valueIsOK = (Value == Atom.a("codes") || Value == Atom.a("chars") || + Value == Atom.a("atom")); + else + throw new PrologException + (new Functor3("permission_error", Atom.a("modify"), Atom.a("flag"), Key), + "May not modify Prolog flag " + Key); + if (!valueIsOK) + throw new PrologException + (new Functor2("domain_error", Atom.a("flag_value"), new Functor2("+", Key, Value)), + "May not set arg 1 Key " + Key + " to arg 2 Value " + Value); + + _prologFlags[keyName] = Value; + } /// /// script_event calls hosting script with events as a callback method. /// @@ -2303,19 +2538,35 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog private IEnumerator _enumerator; private PrologException _exception = null; - public Catch(IEnumerable iterator) + /// + /// Call YP.getIterator(Goal, declaringClass) and save the returned iterator. + /// If getIterator throws an exception, save it the same as MoveNext(). + /// + /// + /// + public Catch(object Goal, Type declaringClass) + { + try { - _enumerator = iterator.GetEnumerator(); + _enumerator = getIterator(Goal, declaringClass).GetEnumerator(); + } + catch (PrologException exception) + { + // MoveNext() will check this. + _exception = exception; + } } /// /// 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() { + if (_exception != null) + return false; + try { return _enumerator.MoveNext(); @@ -2372,6 +2623,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog public void Dispose() { + if (_enumerator != null) _enumerator.Dispose(); } @@ -2407,7 +2659,42 @@ namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog foreach (bool l2 in YP.unify(Body, _Body)) yield return false; } - #pragma warning disable 0168 + #pragma warning restore 0168 + } + } + + /// + /// CodeListReader extends TextReader and overrides Read to read the next code from + /// the CodeList which is a Prolog list of integer character codes. + /// + public class CodeListReader : TextReader + { + private object _CodeList; + + public CodeListReader(object CodeList) + { + _CodeList = YP.getValue(CodeList); + } + + /// + /// If the head of _CodeList is an integer, return it and advance the list. Otherwise, + /// return -1 for end of file. + /// + /// + public override int Read() + { + Functor2 CodeListPair = _CodeList as Functor2; + int code; + if (!(CodeListPair != null && CodeListPair._name == Atom.DOT && + getInt(CodeListPair._arg1, out code))) + { + _CodeList = Atom.NIL; + return -1; + } + + // Advance. + _CodeList = YP.getValue(CodeListPair._arg2); + return code; } } } -- cgit v1.1