From 6b7930104bdb845d3b9c085dc04f52b6446f23b1 Mon Sep 17 00:00:00 2001 From: lbsa71 Date: Tue, 24 Jun 2008 21:09:49 +0000 Subject: * Applied patch from Melanie, mantis issue #1581 - "Refactor LSL language, api and compiler out of XEngine" "First stage in a major Script Engine refactor, that will result in the LSL implementaions ebing reconverged. Not there yet, but one major part is done." Thank you, Melanie! --- .../Shared/Api/Runtime/YieldProlog/YP.cs | 1644 ++++++++++++++++++++ 1 file changed, 1644 insertions(+) create mode 100644 OpenSim/Region/ScriptEngine/Shared/Api/Runtime/YieldProlog/YP.cs (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 new file mode 100644 index 0000000..74704aa --- /dev/null +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Runtime/YieldProlog/YP.cs @@ -0,0 +1,1644 @@ +/* + * Copyright (C) 2007-2008, Jeff Thompson + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog +{ + /// + /// YP has static methods for general functions in Yield Prolog such as + /// and . + /// + public class YP + { + private static Fail _fail = new Fail(); + private static Repeat _repeat = new Repeat(); + private static Dictionary> _predicatesStore = + new Dictionary>(); + private static TextWriter _outputStream = System.Console.Out; + private static TextReader _inputStream = System.Console.In; + private static List _operatorTable = null; + + /// + /// An IClause is used so that dynamic predicates can call match. + /// + public interface IClause + { + IEnumerable match(object[] args); + } + + public static object getValue(object value) + { + if (value is Variable) + return ((Variable)value).getValue(); + else + return value; + } + + public static IEnumerable unify(object arg1, object arg2) + { + arg1 = getValue(arg1); + arg2 = getValue(arg2); + if (arg1 is IUnifiable) + return ((IUnifiable)arg1).unify(arg2); + else if (arg2 is IUnifiable) + return ((IUnifiable)arg2).unify(arg1); + else + { + // Arguments are "normal" types. + if (arg1.Equals(arg2)) + return new Succeed(); + else + return _fail; + } + } + + /// + /// This is used for the lookup key in _factStore. + /// + public struct NameArity + { + public readonly Atom _name; + public readonly int _arity; + + public NameArity(Atom name, int arity) + { + _name = name; + _arity = arity; + } + + public override bool Equals(object obj) + { + if (obj is NameArity) + { + NameArity nameArity = (NameArity)obj; + return nameArity._name.Equals(_name) && nameArity._arity.Equals(_arity); + } + else + { + return false; + } + } + + public override int GetHashCode() + { + return _name.GetHashCode() ^ _arity.GetHashCode(); + } + } + + /// + /// Convert term to an int. + /// If term is a single-element List, use its first element + /// (to handle the char types like "a"). If can't convert, throw an exception. + /// + /// + /// + public static int convertInt(object term) + { + term = YP.getValue(term); + if (term is Functor2 && ((Functor2)term)._name == Atom.DOT && + YP.getValue(((Functor2)term)._arg2) == Atom.NIL) + // Assume it is a char type like "a". + term = YP.getValue(((Functor2)term)._arg1); + + return (int)term; + } + + /// + /// Convert term to a double. This may convert an int to a double, etc. + /// If term is a single-element List, use its first element + /// (to handle the char types like "a"). If can't convert, throw an exception. + /// + /// + /// + public static double convertDouble(object term) + { + term = YP.getValue(term); + if (term is Functor2 && ((Functor2)term)._name == Atom.DOT && + 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); + } + + /// + /// If term is an integer, set intTerm. + /// If term is a single-element List, use its first element + /// (to handle the char types like "a"). Return true for success, false if can't convert. + /// We use a success return value because throwing an exception is inefficient. + /// + /// + /// + public static bool getInt(object term, out int intTerm) + { + term = YP.getValue(term); + if (term is Functor2 && ((Functor2)term)._name == Atom.DOT && + YP.getValue(((Functor2)term)._arg2) == Atom.NIL) + // Assume it is a char type like "a". + term = YP.getValue(((Functor2)term)._arg1); + + if (term is int) + { + intTerm = (int)term; + return true; + } + + intTerm = 0; + return false; + } + + public static bool equal(object x, object y) + { + x = YP.getValue(x); + if (x is DateTime) + return (DateTime)x == (DateTime)YP.getValue(y); + // Assume convertDouble converts an int to a double perfectly. + return YP.convertDouble(x) == YP.convertDouble(y); + } + + public static bool notEqual(object x, object y) + { + x = YP.getValue(x); + if (x is DateTime) + return (DateTime)x != (DateTime)YP.getValue(y); + // Assume convertDouble converts an int to a double perfectly. + return YP.convertDouble(x) != YP.convertDouble(y); + } + + public static bool greaterThan(object x, object y) + { + x = YP.getValue(x); + if (x is DateTime) + return (DateTime)x > (DateTime)YP.getValue(y); + // Assume convertDouble converts an int to a double perfectly. + return YP.convertDouble(x) > YP.convertDouble(y); + } + + public static bool lessThan(object x, object y) + { + x = YP.getValue(x); + if (x is DateTime) + return (DateTime)x < (DateTime)YP.getValue(y); + // Assume convertDouble converts an int to a double perfectly. + return YP.convertDouble(x) < YP.convertDouble(y); + } + + public static bool greaterThanOrEqual(object x, object y) + { + x = YP.getValue(x); + if (x is DateTime) + return (DateTime)x >= (DateTime)YP.getValue(y); + // Assume convertDouble converts an int to a double perfectly. + return YP.convertDouble(x) >= YP.convertDouble(y); + } + + public static bool lessThanOrEqual(object x, object y) + { + x = YP.getValue(x); + if (x is DateTime) + return (DateTime)x <= (DateTime)YP.getValue(y); + // Assume convertDouble converts an int to a double perfectly. + return YP.convertDouble(x) <= YP.convertDouble(y); + } + + public static object negate(object x) + { + int intX; + if (getInt(x, out intX)) + return -intX; + return -convertDouble(x); + } + + public static object abs(object x) + { + int intX; + if (getInt(x, out intX)) + return Math.Abs(intX); + return Math.Abs(convertDouble(x)); + } + + public static object sign(object x) + { + int intX; + if (getInt(x, out intX)) + return Math.Sign(intX); + return Math.Sign(convertDouble(x)); + } + + /// + /// The ISO standard returns an int. + /// + /// + /// + public static object floor(object x) + { + return (int)Math.Floor(convertDouble(x)); + } + + /// + /// The ISO standard returns an int. + /// + /// + /// + public static object truncate(object x) + { + return (int)Math.Truncate(convertDouble(x)); + } + + /// + /// The ISO standard returns an int. + /// + /// + /// + public static object round(object x) + { + return (int)Math.Round(convertDouble(x)); + } + + /// + /// The ISO standard returns an int. + /// + /// + /// + public static object ceiling(object x) + { + return (int)Math.Ceiling(convertDouble(x)); + } + + public static object sin(object x) + { + return Math.Sin(YP.convertDouble(x)); + } + + public static object cos(object x) + { + return Math.Cos(YP.convertDouble(x)); + } + + public static object atan(object x) + { + return Math.Atan(YP.convertDouble(x)); + } + + public static object exp(object x) + { + return Math.Exp(YP.convertDouble(x)); + } + + public static object log(object x) + { + return Math.Log(YP.convertDouble(x)); + } + + public static object sqrt(object x) + { + return Math.Sqrt(convertDouble(x)); + } + + public static object bitwiseComplement(object x) + { + return ~YP.convertInt(x); + } + + public static object add(object x, object y) + { + int intX, intY; + if (getInt(x, out intX) && getInt(y, out intY)) + return intX + intY; + return convertDouble(x) + convertDouble(y); + } + + public static object subtract(object x, object y) + { + int intX, intY; + if (getInt(x, out intX) && getInt(y, out intY)) + return intX - intY; + return convertDouble(x) - convertDouble(y); + } + + public static object multiply(object x, object y) + { + int intX, intY; + if (getInt(x, out intX) && getInt(y, out intY)) + return intX * intY; + return convertDouble(x) * convertDouble(y); + } + + /// + /// Return floating point, even if both arguments are integer. + /// + /// + /// + /// + public static object divide(object x, object y) + { + return convertDouble(x) / convertDouble(y); + } + + public static object intDivide(object x, object y) + { + int intX, intY; + if (getInt(x, out intX) && getInt(y, out intY)) + return intX / intY; + // Still allow passing a double, but treat as an int. + return (int)convertDouble(x) / (int)convertDouble(y); + } + + public static object mod(object x, object y) + { + int intX, intY; + if (getInt(x, out intX) && getInt(y, out intY)) + return intX % intY; + // Still allow passing a double, but treat as an int. + return (int)convertDouble(x) % (int)convertDouble(y); + } + + public static object pow(object x, object y) + { + return Math.Pow(YP.convertDouble(x), YP.convertDouble(y)); + } + + public static object bitwiseShiftRight(object x, object y) + { + return YP.convertInt(x) >> YP.convertInt(y); + } + + public static object bitwiseShiftLeft(object x, object y) + { + return YP.convertInt(x) << YP.convertInt(y); + } + + public static object bitwiseAnd(object x, object y) + { + return YP.convertInt(x) & YP.convertInt(y); + } + + public static object bitwiseOr(object x, object y) + { + return YP.convertInt(x) | YP.convertInt(y); + } + + public static object min(object x, object y) + { + int intX, intY; + if (getInt(x, out intX) && getInt(y, out intY)) + return Math.Min(intX, intY); + return Math.Min(convertDouble(x), convertDouble(y)); + } + + public static object max(object x, object y) + { + int intX, intY; + if (getInt(x, out intX) && getInt(y, out intY)) + return Math.Max(intX, intY); + return Math.Max(convertDouble(x), convertDouble(y)); + } + + public static IEnumerable copy_term(object inTerm, object outTerm) + { + return YP.unify(outTerm, YP.makeCopy(inTerm, new Variable.CopyStore())); + } + + public static void addUniqueVariables(object term, List variableSet) + { + term = YP.getValue(term); + if (term is IUnifiable) + ((IUnifiable)term).addUniqueVariables(variableSet); + } + + public static object makeCopy(object term, Variable.CopyStore copyStore) + { + term = YP.getValue(term); + if (term is IUnifiable) + return ((IUnifiable)term).makeCopy(copyStore); + else + // term is a "normal" type. Assume it is ground. + return term; + } + + /// + /// Sort the array in place according to termLessThan. This does not remove duplicates + /// + /// + public static void sortArray(object[] array) + { + Array.Sort(array, YP.compareTerms); + } + + /// + /// Sort the array in place according to termLessThan. This does not remove duplicates + /// + /// + public static void sortArray(List array) + { + array.Sort(YP.compareTerms); + } + + /// + /// Sort List according to termLessThan, remove duplicates and unify with Sorted. + /// + /// + /// + /// + public static IEnumerable sort(object List, object Sorted) + { + object[] array = ListPair.toArray(List); + if (array == null) + return YP.fail(); + if (array.Length > 1) + sortArray(array); + return YP.unify(Sorted, ListPair.makeWithoutRepeatedTerms(array)); + } + + + + /// + /// Use YP.unify to unify each of the elements of the two arrays, and yield + /// once if they all unify. + /// + /// + /// + /// + public static IEnumerable unifyArrays(object[] array1, object[] array2) + { + if (array1.Length != array2.Length) + yield break; + + IEnumerator[] iterators = new IEnumerator[array1.Length]; + bool gotMatch = true; + int nIterators = 0; + // Try to bind all the arguments. + for (int i = 0; i < array1.Length; ++i) + { + IEnumerator iterator = YP.unify(array1[i], array2[i]).GetEnumerator(); + iterators[nIterators++] = iterator; + // MoveNext() is true if YP.unify succeeds. + if (!iterator.MoveNext()) + { + gotMatch = false; + break; + } + } + + try + { + if (gotMatch) + yield return false; + } + finally + { + // Manually finalize all the iterators. + for (int i = 0; i < nIterators; ++i) + iterators[i].Dispose(); + } + } + + /// + /// Return an iterator (which you can use in a for-in loop) which does + /// zero iterations. This returns a pre-existing iterator which is + /// more efficient than letting the compiler generate a new one. + /// + /// + public static IEnumerable fail() + { + return _fail; + } + + /// + /// Return an iterator (which you can use in a for-in loop) which does + /// one iteration. This returns a pre-existing iterator which is + /// more efficient than letting the compiler generate a new one. + /// + /// + public static IEnumerable succeed() + { + return new Succeed(); + } + + /// + /// Return an iterator (which you can use in a for-in loop) which repeats + /// indefinitely. This returns a pre-existing iterator which is + /// more efficient than letting the compiler generate a new one. + /// + /// + public static IEnumerable repeat() + { + return _repeat; + } + + public static IEnumerable univ(object Term, object List) + { + Term = YP.getValue(Term); + List = YP.getValue(List); + + if (nonvar(Term)) + return YP.unify(new ListPair + (getFunctorName(Term), ListPair.make(getFunctorArgs(Term))), List); + + Variable Name = new Variable(); + Variable ArgList = new Variable(); + foreach (bool l1 in new ListPair(Name, ArgList).unify(List)) + { + object[] args = ListPair.toArray(ArgList); + if (args == null) + throw new Exception("Expected a list. Got: " + ArgList.getValue()); + if (args.Length == 0) + // Return the Name, even if it is not an Atom. + return YP.unify(Term, Name); + if (!atom(Name)) + throw new Exception("Expected an atom. Got: " + Name.getValue()); + + return YP.unify(Term, Functor.make((Atom)YP.getValue(Name), args)); + } + + return YP.fail(); + } + + public static IEnumerable functor(object Term, object FunctorName, object Arity) + { + Term = YP.getValue(Term); + FunctorName = YP.getValue(FunctorName); + Arity = YP.getValue(Arity); + + if (!(Term is Variable)) + { + foreach (bool l1 in YP.unify(FunctorName, getFunctorName(Term))) + { + foreach (bool l2 in YP.unify(Arity, getFunctorArgs(Term).Length)) + yield return false; + } + } + else + throw new NotImplementedException("Debug: must finish functor/3"); + } + + public static IEnumerable arg(object ArgNumber, object Term, object Value) + { + if (YP.var(ArgNumber)) + throw new NotImplementedException("Debug: must finish arg/3"); + else + { + int argNumberInt = convertInt(ArgNumber); + if (argNumberInt < 0) + throw new Exception("ArgNumber must be non-negative"); + object[] termArgs = YP.getFunctorArgs(Term); + // Silently fail if argNumberInt is out of range. + if (argNumberInt >= 1 && argNumberInt <= termArgs.Length) + { + // The first ArgNumber is at 1, not 0. + foreach (bool l1 in YP.unify(Value, termArgs[argNumberInt - 1])) + yield return false; + } + } + } + + public static bool termEqual(object Term1, object Term2) + { + Term1 = YP.getValue(Term1); + if (Term1 is IUnifiable) + return ((IUnifiable)Term1).termEqual(Term2); + return Term1.Equals(YP.getValue(Term2)); + } + + public static bool termNotEqual(object Term1, object Term2) + { + return !termEqual(Term1, Term2); + } + + public static bool termLessThan(object Term1, object Term2) + { + Term1 = YP.getValue(Term1); + Term2 = YP.getValue(Term2); + int term1TypeCode = getTypeCode(Term1); + int term2TypeCode = getTypeCode(Term2); + if (term1TypeCode != term2TypeCode) + return term1TypeCode < term2TypeCode; + + // The terms are the same type code. + if (term1TypeCode == -2) + { + // Variable. + // We always check for equality first because we want to be sure + // that less than returns false if the terms are equal, in + // case that the less than check really behaves like less than or equal. + if ((Variable)Term1 != (Variable)Term2) + // The hash code should be unique to a Variable object. + return Term1.GetHashCode() < Term2.GetHashCode(); + return false; + } + if (term1TypeCode == 0) + return ((Atom)Term1)._name.CompareTo(((Atom)Term2)._name) < 0; + if (term1TypeCode == 1) + return ((Functor1)Term1).lessThan((Functor1)Term2); + if (term1TypeCode == 2) + return ((Functor2)Term1).lessThan((Functor2)Term2); + if (term1TypeCode == 3) + return ((Functor3)Term1).lessThan((Functor3)Term2); + if (term1TypeCode == 4) + return ((Functor)Term1).lessThan((Functor)Term2); + + // Type code is -1 for general objects. First compare their type names. + // Note that this puts Double before Int32 as required by ISO Prolog. + string term1TypeName = Term1.GetType().ToString(); + string term2TypeName = Term2.GetType().ToString(); + if (term1TypeName != term2TypeName) + return term1TypeName.CompareTo(term2TypeName) < 0; + + // The terms are the same type name. + if (Term1 is int) + return (int)Term1 < (int)Term2; + else if (Term1 is double) + return (double)Term1 < (double)Term2; + else if (Term1 is DateTime) + return (DateTime)Term1 < (DateTime)Term2; + else if (Term1 is String) + return ((String)Term1).CompareTo((String)Term2) < 0; + // Debug: Should we try arrays, etc.? + + if (!Term1.Equals(Term2)) + // Could be equal or greater than. + return Term1.GetHashCode() < Term2.GetHashCode(); + return false; + } + + /// + /// Type code is -2 if term is a Variable, 0 if it is an Atom, + /// 1 if it is a Functor1, 2 if it is a Functor2, 3 if it is a Functor3, + /// 4 if it is Functor. + /// Otherwise, type code is -1. + /// This does not call YP.getValue(term). + /// + /// + /// + private static int getTypeCode(object term) + { + if (term is Variable) + return -2; + else if (term is Atom) + return 0; + else if (term is Functor1) + return 1; + else if (term is Functor2) + return 2; + else if (term is Functor3) + return 3; + else if (term is Functor) + return 4; + else + return -1; + } + + public static bool termLessThanOrEqual(object Term1, object Term2) + { + if (YP.termEqual(Term1, Term2)) + return true; + return YP.termLessThan(Term1, Term2); + } + + public static bool termGreaterThan(object Term1, object Term2) + { + return !YP.termLessThanOrEqual(Term1, Term2); + } + + public static bool termGreaterThanOrEqual(object Term1, object Term2) + { + // termLessThan should ensure that it returns false if terms are equal, + // so that this would return true. + return !YP.termLessThan(Term1, Term2); + } + + public static int compareTerms(object Term1, object Term2) + { + if (YP.termEqual(Term1, Term2)) + return 0; + else if (YP.termLessThan(Term1, Term2)) + return -1; + else + return 1; + } + + public static bool ground(object Term) + { + Term = YP.getValue(Term); + if (Term is IUnifiable) + return ((IUnifiable)Term).ground(); + return true; + } + + public static IEnumerable current_op + (object Priority, object Specifier, object Operator) + { + if (_operatorTable == null) + { + // Initialize. + _operatorTable = new List(); + _operatorTable.Add(new object[] { 1200, Atom.a("xfx"), Atom.a(":-") }); + _operatorTable.Add(new object[] { 1200, Atom.a("xfx"), Atom.a("-->") }); + _operatorTable.Add(new object[] { 1200, Atom.a("fx"), Atom.a(":-") }); + _operatorTable.Add(new object[] { 1200, Atom.a("fx"), Atom.a("?-") }); + _operatorTable.Add(new object[] { 1100, Atom.a("xfy"), Atom.a(";") }); + _operatorTable.Add(new object[] { 1050, Atom.a("xfy"), Atom.a("->") }); + _operatorTable.Add(new object[] { 1000, Atom.a("xfy"), Atom.a(",") }); + _operatorTable.Add(new object[] { 900, Atom.a("fy"), Atom.a("\\+") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("=") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("\\=") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("==") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("\\==") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("@<") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("@=<") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("@>") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("@>=") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("=..") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("is") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("=:=") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("=\\=") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("<") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a("=<") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a(">") }); + _operatorTable.Add(new object[] { 700, Atom.a("xfx"), Atom.a(">=") }); + _operatorTable.Add(new object[] { 600, Atom.a("xfy"), Atom.a(":") }); + _operatorTable.Add(new object[] { 500, Atom.a("yfx"), Atom.a("+") }); + _operatorTable.Add(new object[] { 500, Atom.a("yfx"), Atom.a("-") }); + _operatorTable.Add(new object[] { 500, Atom.a("yfx"), Atom.a("/\\") }); + _operatorTable.Add(new object[] { 500, Atom.a("yfx"), Atom.a("\\/") }); + _operatorTable.Add(new object[] { 400, Atom.a("yfx"), Atom.a("*") }); + _operatorTable.Add(new object[] { 400, Atom.a("yfx"), Atom.a("/") }); + _operatorTable.Add(new object[] { 400, Atom.a("yfx"), Atom.a("//") }); + _operatorTable.Add(new object[] { 400, Atom.a("yfx"), Atom.a("rem") }); + _operatorTable.Add(new object[] { 400, Atom.a("yfx"), Atom.a("mod") }); + _operatorTable.Add(new object[] { 400, Atom.a("yfx"), Atom.a("<<") }); + _operatorTable.Add(new object[] { 400, Atom.a("yfx"), Atom.a(">>") }); + _operatorTable.Add(new object[] { 200, Atom.a("xfx"), Atom.a("**") }); + _operatorTable.Add(new object[] { 200, Atom.a("xfy"), Atom.a("^") }); + _operatorTable.Add(new object[] { 200, Atom.a("fy"), Atom.a("-") }); + _operatorTable.Add(new object[] { 200, Atom.a("fy"), Atom.a("\\") }); + // Debug: This is hacked in to run the Prolog test suite until we implement op/3. + _operatorTable.Add(new object[] { 20, Atom.a("xfx"), Atom.a("<--") }); + } + + object[] args = new object[] { Priority, Specifier, Operator }; + foreach (object[] answer in _operatorTable) + { + foreach (bool l1 in YP.unifyArrays(args, answer)) + yield return false; + } + } + + public static IEnumerable atom_length(object atom, object Length) + { + return YP.unify(Length, ((Atom)YP.getValue(atom))._name.Length); + } + + public static IEnumerable atom_concat(object Start, object End, object Whole) + { + // Debug: Should implement for var(Start) which is a kind of search. + // Debug: Should we try to preserve the _declaringClass? + return YP.unify(Whole, Atom.a(((Atom)YP.getValue(Start))._name + + ((Atom)YP.getValue(End))._name)); + } + + public static IEnumerable sub_atom + (object atom, object Before, object Length, object After, object Sub_atom) + { + // Debug: Should implement for var(atom) which is a kind of search. + // Debug: Should we try to preserve the _declaringClass? + Atom atomAtom = (Atom)YP.getValue(atom); + int beforeInt = YP.convertInt(Before); + int lengthInt = YP.convertInt(Length); + if (beforeInt < 0) + throw new Exception("Before must be non-negative"); + if (lengthInt < 0) + throw new Exception("Length must be non-negative"); + int afterInt = atomAtom._name.Length - (beforeInt + lengthInt); + if (afterInt >= 0) + { + foreach (bool l1 in YP.unify(After, afterInt)) + { + foreach (bool l2 in YP.unify + (Sub_atom, Atom.a(atomAtom._name.Substring(beforeInt, lengthInt)))) + yield return false; + } + } + } + + public static IEnumerable atom_codes(object atom, object List) + { + atom = YP.getValue(atom); + List = YP.getValue(List); + + if (nonvar(atom)) + { + string name = ((Atom)atom)._name; + object codeList = Atom.NIL; + // Start from the back to make the list. + for (int i = name.Length - 1; i >= 0; --i) + codeList = new ListPair((int)name[i], codeList); + return YP.unify(List, codeList); + } + { + object[] codeArray = ListPair.toArray(List); + char[] charArray = new char[codeArray.Length]; + for (int i = 0; i < codeArray.Length; ++i) + charArray[i] = (char)YP.convertInt(codeArray[i]); + return YP.unify(atom, Atom.a(new String(charArray))); + } + } + + public static IEnumerable number_codes(object number, object List) + { + number = YP.getValue(number); + List = YP.getValue(List); + + if (nonvar(number)) + { + string numberString = null; + // Try converting to an int first. + int intNumber; + if (YP.getInt(number, out intNumber)) + numberString = intNumber.ToString(); + else + numberString = YP.doubleToString(YP.convertDouble(number)); + + object codeList = Atom.NIL; + // Start from the back to make the list. + for (int i = numberString.Length - 1; i >= 0; --i) + codeList = new ListPair((int)numberString[i], codeList); + return YP.unify(List, codeList); + } + { + object[] codeArray = ListPair.toArray(List); + char[] charArray = new char[codeArray.Length]; + for (int i = 0; i < codeArray.Length; ++i) + charArray[i] = (char)YP.convertInt(codeArray[i]); + String numberString = new String(charArray); + // Debug: Is there a way in C# to ask if a string parses as int without throwing an exception? + try + { + // Try an int first. + return YP.unify(number, Convert.ToInt32(numberString)); + } + catch (FormatException) { } + return YP.unify(number, Convert.ToDouble(numberString)); + } + } + + /// + /// If term is an Atom or functor type, return its name. + /// Otherwise, return term. + /// + /// + /// + public static object getFunctorName(object term) + { + term = YP.getValue(term); + if (term is Functor1) + return ((Functor1)term)._name; + else if (term is Functor2) + return ((Functor2)term)._name; + else if (term is Functor3) + return ((Functor3)term)._name; + else if (term is Functor) + return ((Functor)term)._name; + else + return term; + } + + /// + /// If term is an Atom or functor type, return an array of its args. + /// Otherwise, return an empty array. + /// + /// + /// + public static object[] getFunctorArgs(object term) + { + term = YP.getValue(term); + if (term is Functor1) + { + Functor1 functor = (Functor1)term; + return new object[] { functor._arg1 }; + } + else if (term is Functor2) + { + Functor2 functor = (Functor2)term; + return new object[] { functor._arg1, functor._arg2 }; + } + else if (term is Functor3) + { + Functor3 functor = (Functor3)term; + return new object[] { functor._arg1, functor._arg2, functor._arg3 }; + } + else if (term is Functor) { + Functor functor = (Functor)term; + return functor._args; + } + else + return new object[0]; + } + + public static bool var(object Term) + { + return YP.getValue(Term) is Variable; + } + + public static bool nonvar(object Term) + { + return !YP.var(Term); + } + + public static bool atom(object Term) + { + 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) + { + return YP.integer(Term) || YP.isFloat(Term); + } + + public static bool atomic(object Term) + { + return YP.atom(Term) || YP.number(Term); + } + + public static bool compound(object Term) + { + Term = getValue(Term); + return Term is Functor1 || Term is Functor2 || Term is Functor3 || Term is Functor; + } + + public static void see(object input) + { + input = YP.getValue(input); + if (input is TextReader) + { + _inputStream = (TextReader)input; + return; + } + else if (input is Atom) + { + _inputStream = new StreamReader(((Atom)input)._name); + return; + } + else if (input is String) + { + _inputStream = new StreamReader((String)input); + return; + } + else + throw new InvalidOperationException("Can't open stream for " + input); + } + + public static void seen() + { + if (_inputStream == Console.In) + return; + _inputStream.Close(); + _inputStream = Console.In; + } + + public static void tell(object output) + { + output = YP.getValue(output); + if (output is TextWriter) + { + _outputStream = (TextWriter)output; + return; + } + else if (output is Atom) + { + _outputStream = new StreamWriter(((Atom)output)._name); + return; + } + else if (output is String) + { + _outputStream = new StreamWriter((String)output); + return; + } + else + throw new InvalidOperationException("Can't open stream for " + output); + } + + public static void told() + { + if (_outputStream == Console.Out) + return; + _outputStream.Close(); + _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); + if (x is double) + _outputStream.Write(doubleToString((double)x)); + else + _outputStream.Write(x.ToString()); + } + + /// + /// Format x as a string, making sure that it will parse as an int later. I.e., for 1.0, don't just + /// use "1" which will parse as an int. + /// + /// + /// + private static string doubleToString(double x) + { + string xString = x.ToString(); + // Debug: Is there a way in C# to ask if a string parses as int without throwing an exception? + try + { + Convert.ToInt32(xString); + // The string will parse as an int, not a double, so re-format so that it does. + // Use float if possible, else exponential if it would be too big. + return x.ToString(x >= 100000.0 ? "E1" : "f1"); + } + catch (FormatException) + { + // Assume it will parse as a double. + } + return xString; + } + + public static void put_code(object x) + { + _outputStream.Write((char)YP.convertInt(x)); + } + + public static void nl() + { + _outputStream.WriteLine(); + } + + public static IEnumerable get_code(object code) + { + 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); + List clauses; + IndexedAnswers indexedAnswers; + if (!_predicatesStore.TryGetValue(nameArity, out clauses)) + { + // Create an IndexedAnswers as the first clause of the predicate. + _predicatesStore[nameArity] = (clauses = new List()); + clauses.Add(indexedAnswers = new IndexedAnswers()); + } + else + { + indexedAnswers = clauses[clauses.Count - 1] as IndexedAnswers; + if (indexedAnswers == null) + // The latest clause is not an IndexedAnswers, so add one. + clauses.Add(indexedAnswers = new IndexedAnswers()); + } + + indexedAnswers.addAnswer(values); + } + + /// + /// 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)) + throw new UndefinedPredicateException + ("Undefined fact: " + name + "/" + arguments.Length, name, + arguments.Length); + + if (clauses.Count == 1) + // Usually there is only one clause, so return it without needing to wrap it in an iterator. + return clauses[0].match(arguments); + else + return matchAllClauses(clauses, arguments); + } + + /// + /// Call match(arguments) for each IClause in clauses. We make this a separate + /// 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); + List clauses; + if (!_predicatesStore.TryGetValue(nameArity, out clauses)) + // Can't find, so ignore. + return; + + foreach (object arg in arguments) + { + if (!YP.var(arg)) + throw new InvalidOperationException("All arguments must be unbound"); + } + // Set to a fresh empty IndexedAnswers. + _predicatesStore[nameArity] = (clauses = new List()); + clauses.Add(new IndexedAnswers()); + } + + public static IEnumerable current_predicate(object NameSlashArity) + { + NameSlashArity = YP.getValue(NameSlashArity); + // First check if Name and Arity are nonvar so we can do a direct lookup. + if (YP.ground(NameSlashArity)) + { + if (NameSlashArity is Functor2) + { + Functor2 NameArityFunctor = (Functor2)NameSlashArity; + if (NameArityFunctor._name == Atom.SLASH) + { + if (_predicatesStore.ContainsKey(new NameArity + ((Atom)YP.getValue(NameArityFunctor._arg1), + (int)YP.getValue(NameArityFunctor._arg2)))) + // The predicate is defined. + yield return false; + } + } + yield break; + } + + foreach (NameArity key in _predicatesStore.Keys) + { + foreach (bool l1 in YP.unify + (new Functor2(Atom.SLASH, key._name, key._arity), NameSlashArity)) + yield return false; + } + } + + /// + /// Use YP.getFunctorName(Goal) and invoke the static method of this name in the + /// declaringClass, using arguments from YP.getFunctorArgs(Goal). + /// Note that Goal must be a simple functor, not a complex expression. + /// If not found, this throws UndefinedPredicateException. + /// + /// + /// the class for looking up default function references + /// + 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(); + + // Use Atom.F since it is ignored. + return YPCompiler.compileAnonymousClause + (Functor.make(Atom.F, variableSet), Goal, declaringClass).match(variableSet); +#else + Atom name; + object[] args; + while (true) + { + name = (Atom)YP.getFunctorName(Goal); + args = YP.getFunctorArgs(Goal); + if (name == Atom.HAT && args.Length == 2) + // Assume this is called from a bagof operation. Skip the leading qualifiers. + Goal = YP.getValue(((Functor2)Goal)._arg2); + else + break; + } + try + { + return (IEnumerable)declaringClass.InvokeMember + (name._name, BindingFlags.InvokeMethod, null, null, args); + } + catch (TargetInvocationException exception) + { + throw exception.InnerException; + } + catch (MissingMethodException) + { + throw new UndefinedPredicateException + ("Cannot find predicate function: " + name + "/" + args.Length + " in " + + declaringClass.FullName, name, args.Length); + } +#endif + } + + public static void throwException(object Term) + { + throw new PrologException(Term); + } + + /// + /// script_event calls hosting script with events as a callback method. + /// + /// + /// + /// + public static void script_event(object script_event, object script_params) + { + string function = ((Atom)YP.getValue(script_event))._name; + object[] array = ListPair.toArray(script_params); + if (array == null) + return; // YP.fail(); + if (array.Length > 1) + { + //m_CmdManager.m_ScriptEngine.m_EventQueManager.AddToScriptQueue + //(localID, itemID, function, array); + // sortArray(array); + } + //return YP.unify(Sorted, ListPair.makeWithoutRepeatedTerms(array)); + } + + /// + /// An enumerator that does zero loops. + /// + private class Fail : IEnumerator, IEnumerable + { + public bool MoveNext() + { + return false; + } + + public IEnumerator GetEnumerator() + { + return (IEnumerator)this; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public bool Current + { + get { return true; } + } + + object IEnumerator.Current + { + get { return true; } + } + + public void Dispose() + { + } + + public void Reset() + { + throw new NotImplementedException(); + } + } + + /// + /// An enumerator that does one iteration. + /// + private class Succeed : IEnumerator, IEnumerable + { + private bool _didIteration = false; + + public bool MoveNext() + { + if (!_didIteration) + { + _didIteration = true; + return true; + } + else + return false; + } + + public IEnumerator GetEnumerator() + { + return (IEnumerator)this; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public bool Current + { + get { return false; } + } + + object IEnumerator.Current + { + get { return false; } + } + + public void Dispose() + { + } + + public void Reset() + { + throw new NotImplementedException(); + } + } + + /// + /// An enumerator that repeats forever. + /// + private class Repeat : IEnumerator, IEnumerable + { + public bool MoveNext() + { + return true; + } + + public IEnumerator GetEnumerator() + { + return (IEnumerator)this; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public bool Current + { + get { return false; } + } + + object IEnumerator.Current + { + get { return false; } + } + + public void Dispose() + { + } + + public void Reset() + { + 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