#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.Diagnostics.CodeAnalysis;
namespace Prolog
{
///
/// Represents a Prolog term.
/// Currently includes Structures (not structs), Symbols (what Prolog calls Atoms), and LogicVariables.
/// Will likely be extended in the future to include boxed constants.
///
[DebuggerDisplay("{DebuggerDisplay}")]
public abstract class Term
{
#region Canonicalization
///
/// Returns value, unless it is a LogicVariable, in which case it returns the variable's value.
///
public static object Deref(object value)
{
//LogicVariable alias = value as LogicVariable;
//if (alias != null && alias.IsBound)
// return alias.Value;
//return value;
var v = value as LogicVariable;
while (v != null && v.IsBound)
{
value = v.UncanonicalizedValue;
v = value as LogicVariable;
}
return value;
}
private static readonly object[] NoArgs = new object[0];
internal static Structure Structurify(object term, string errorMessage)
{
term = Deref(term);
if (term is bool)
{
term = (bool)term?Symbol.True:Symbol.Fail;
}
var t = term as Structure;
if (t != null)
return t;
var s = term as Symbol;
if (s != null)
return new Structure(s, NoArgs);
var offendingVariable = term as LogicVariable;
if (offendingVariable != null)
throw new InstantiationException(offendingVariable, errorMessage);
throw new GoalException(term, errorMessage);
}
#endregion
#region Ground testing
///
/// True if term contains no unbound variables
///
public static bool IsGround(object term)
{
term = Deref(term);
var s = term as Structure;
if (s != null)
{
foreach (var arg in s.Arguments)
if (!IsGround(arg))
return false;
return true;
}
return !(term is LogicVariable);
}
///
/// Returns first uninstantiated variable found in term.
///
public static LogicVariable FindUninstantiatedVariable(object term)
{
term = Deref(term);
var s = term as Structure;
if (s != null)
{
LogicVariable v;
foreach (var arg in s.Arguments)
if ((v = FindUninstantiatedVariable(arg)) != null)
return v;
return null;
}
return (term as LogicVariable);
}
#endregion
#region Comparison and sorting
///
/// Compares to prolog terms for purposes of sorting
///
public static int Compare(object term1, object term2)
{
int type1 = TypeNumber(term1);
int comp = type1-TypeNumber(term2);
if (comp != 0)
return comp;
switch (type1)
{
case 0: // logicvariables
return (int)((LogicVariable) term1).UID - (int)((LogicVariable) term2).UID;
case 1: // floats
return Convert.ToDouble(term1).CompareTo(Convert.ToDouble(term2));
case 2: // ints
return (int) term1 - (int) term2;
case 3: // null
return 0; // they're both null
case 4: // symbols/atoms
// ReSharper disable StringCompareToIsCultureSpecific
return ((Symbol) term1).Name.CompareTo(((Symbol) term2).Name);
case 5: // strings
return ((string) term1).CompareTo((string) term2);
case 6: // structures
var s1 = (Structure) term1;
var s2 = (Structure) term2;
if (s1.Arguments.Length != s2.Arguments.Length)
return s1.Arguments.Length.CompareTo(s2.Arguments.Length);
if (s1.Functor != s2.Functor)
return s1.Functor.Name.CompareTo(s2.Functor.Name);
// ReSharper restore StringCompareToIsCultureSpecific
for (int i=0; i
/// Sorts arbitrary list of Prolog terms
///
public static void Sort(List terms, bool deleteDuplicates)
{
if (terms.Count == 0)
return;
terms.Sort(Compare);
if (deleteDuplicates)
{
for (int i=terms.Count-2; i>0; i--)
if (Compare(terms[i], terms[i+1])==0)
terms.RemoveAt(i+1);
if (terms.Count>1 && Compare(terms[0], terms[1])==0)
terms.RemoveAt(1);
}
}
///
/// Sorts arbitrary list of Prolog terms
///
public static void KeySort(List terms, bool deleteDuplicates)
{
if (terms.Count == 0)
return;
terms.Sort(CompareKeys);
if (deleteDuplicates)
{
for (int i = terms.Count - 2; i > 0; i--)
if (Compare(terms[i], terms[i + 1]) == 0)
terms.RemoveAt(i + 1);
if (terms.Count > 1 && Compare(terms[0], terms[1]) == 0)
terms.RemoveAt(1);
}
}
public static object SortPrologList(object list, bool deleteDuplicates)
{
var l = Prolog.PrologListToIList(list);
Sort(l, deleteDuplicates);
return Prolog.IListToPrologList(l);
}
public static object KeySortPrologList(object list, bool deleteDuplicates)
{
var l = Prolog.PrologListToIList(list);
KeySort(l, deleteDuplicates);
return Prolog.IListToPrologList(l);
}
#endregion
#region Trail-based Unification
///
/// Attempt to unify the two values. Either, both, or neither may be logic variables.
///
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "v")]
public static bool Unify(object v1, object v2, PrologContext context)
{
object o1 = Deref(v1);
object o2 = Deref(v2);
if (o1 == o2)
// Fast path
return true;
var t1 = o1 as Term;
var t2 = o2 as Term;
if (t1 != null)
{
if (t2 != null)
return t1.UnifyWithTerm(t2, context);
return t1.UnifyWithAtomicConstant(o2, context);
}
// o1 isn't a Term
if (t2 != null)
// o2 is a Term
return t2.UnifyWithAtomicConstant(o1, context);
// Neither is a Term
if (o1 == null)
{
return o2 == null;
}
return o1.Equals(o2);
}
internal virtual bool UnifyWithAtomicConstant(object value, PrologContext context)
{
return Equals(value);
}
internal virtual bool UnifyWithStructure(Structure value, PrologContext context)
{
return false;
}
internal abstract bool UnifyWithTerm(Term term, PrologContext context);
#endregion
#region Iterator-based Unification
///
/// Attempt to unify the two values. Either, both, or neither may be logic variables.
///
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "v")]
public static IEnumerable Unify(object v1, object v2)
{
object o1 = Deref(v1);
object o2 = Deref(v2);
var t1 = o1 as Term;
var t2 = o2 as Term;
if (t1 != null)
{
if (t2 != null)
return t1.UnifyWithTerm(t2);
return t1.UnifyWithAtomicConstant(o2);
}
// o1 isn't a Term
if (t2 != null)
// o2 is a Term
return t2.UnifyWithAtomicConstant(o1);
// Neither is a Term
if (o1 == null)
{
return ToEnumerator(o2 == null);
}
return ToEnumerator(o1.Equals(o2));
}
internal virtual IEnumerable UnifyWithAtomicConstant(object value)
{
return ToEnumerator(Equals(value));
}
internal virtual IEnumerable UnifyWithStructure(Structure value)
{
return FailEnumerator;
}
internal abstract IEnumerable UnifyWithTerm(Term term);
///
/// Attempt to unify the two values. Either, both, or neither may be logic variables.
///
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "v")]
internal static IEnumerable UnifyAndReturnCutState(object v1, object v2)
{
// ReSharper disable UnusedVariable
#pragma warning disable 414, 168, 219
foreach (var ignore in Unify(v1, v2))
#pragma warning restore 414, 168, 219
// ReSharper restore UnusedVariable
yield return CutState.Continue;
//object o1 = Canonicalize(v1);
//object o2 = Canonicalize(v2);
//LogicVariable lv = o1 as LogicVariable;
//if (lv != null)
//{
// // ReSharper disable UnusedVariable
// foreach (bool ignore in lv.Unify(o2))
// // ReSharper restore UnusedVariable
// yield return CutState.Continue;
//}
//else
//{
// lv = o2 as LogicVariable;
// if (lv != null)
// {
// // ReSharper disable UnusedVariable
// foreach (bool ignore in lv.Unify(o1))
// // ReSharper restore UnusedVariable
// yield return CutState.Continue;
// }
// else
// {
// Structure t1 = o1 as Structure;
// Structure t2 = o2 as Structure;
// if (t1 == null || t2 == null)
// {
// if (o1 == null)
// {
// if (o2 == null) yield return CutState.Continue;
// }
// else if (o1.Equals(o2))
// yield return CutState.Continue;
// }
// else
// {
// if (t1.Functor == t2.Functor && t1.Arguments.Length == t2.Arguments.Length)
// {
// // ReSharper disable UnusedVariable
// foreach (bool ignore in UnifyArrays(t1.Arguments, t2.Arguments))
// // ReSharper restore UnusedVariable
// yield return CutState.Continue;
// }
// }
// }
//}
}
#endregion
#region Array unification
#pragma warning disable 414, 168, 219
///
/// Unifies arrays using trailing
///
/// Success
internal static bool UnifyArrays(object[] a1, object[] a2, PrologContext context)
{
if (a1.Length != a2.Length)
return false;
for (int i = 0; i < a1.Length; i++)
if (!Unify(a1[i], a2[i], context))
return false;
return true;
}
internal static IEnumerable UnifyArraysFast(object[] a1, object[] a2, PrologContext context)
{
int mark = context.MarkTrail();
if (UnifyArrays(a1, a2, context))
yield return false;
context.RestoreVariables(mark);
}
///
/// Unifies two arrays of variable length
///
internal static IEnumerable UnifyArrays(object[] a1, object[] a2)
{
if (a1.Length == a2.Length)
{
switch (a1.Length)
{
case 0:
return UnifyArrays0(a1, a2);
case 1:
return UnifyArrays1(a1, a2);
case 2:
return UnifyArrays2(a1, a2);
case 3:
return UnifyArrays3(a1, a2);
case 4:
return UnifyArrays4(a1, a2);
case 5:
return UnifyArrays5(a1, a2);
case 6:
return UnifyArrays6(a1, a2);
case 7:
return UnifyArrays7(a1, a2);
case 8:
return UnifyArrays8(a1, a2);
case 9:
return UnifyArrays9(a1, a2);
case 10:
return UnifyArrays10(a1, a2);
default:
throw new ArgumentException("Attempting to unify arrays that are too long.");
}
}
return FailEnumerator;
}
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "a2"), SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "a1")]
// ReSharper disable UnusedParameter.Local
private static IEnumerable UnifyArrays0(object[] a1, object[] a2)
// ReSharper restore UnusedParameter.Local
{
yield return false;
}
private static IEnumerable UnifyArrays1(object[] a1, object[] a2)
{
return Unify(a1[0], a2[0]);
}
private static IEnumerable UnifyArrays2(object[] a1, object[] a2)
{
// ReSharper disable UnusedVariable
foreach (var i00 in Unify(a1[0], a2[0]))
foreach (var i10 in Unify(a1[0], a2[0]))
foreach (var i11 in Unify(a1[1], a2[1]))
yield return false;
// ReSharper restore UnusedVariable
}
private static IEnumerable UnifyArrays3(object[] a1, object[] a2)
{
// ReSharper disable UnusedVariable
foreach (var i20 in Unify(a1[0], a2[0]))
foreach (var i21 in Unify(a1[1], a2[1]))
foreach (var i22 in Unify(a1[2], a2[2]))
yield return false;
// ReSharper restore UnusedVariable
}
private static IEnumerable UnifyArrays4(object[] a1, object[] a2)
{
// ReSharper disable UnusedVariable
foreach (var i40 in Unify(a1[0], a2[0]))
foreach (var i41 in Unify(a1[1], a2[1]))
foreach (var i42 in Unify(a1[2], a2[2]))
foreach (var i43 in Unify(a1[3], a2[3]))
yield return false;
}
private static IEnumerable UnifyArrays5(object[] a1, object[] a2)
{
foreach (var i50 in Unify(a1[0], a2[0]))
foreach (var i51 in Unify(a1[1], a2[1]))
foreach (var i52 in Unify(a1[2], a2[2]))
foreach (var i53 in Unify(a1[3], a2[3]))
foreach (var i54 in Unify(a1[4], a2[4]))
yield return false;
}
private static IEnumerable UnifyArrays6(object[] a1, object[] a2)
{
foreach (var i60 in Unify(a1[0], a2[0]))
foreach (var i61 in Unify(a1[1], a2[1]))
foreach (var i62 in Unify(a1[2], a2[2]))
foreach (var i63 in Unify(a1[3], a2[3]))
foreach (var i64 in Unify(a1[4], a2[4]))
foreach (var i65 in Unify(a1[5], a2[5]))
yield return false;
}
private static IEnumerable UnifyArrays7(object[] a1, object[] a2)
{
foreach (var i70 in Unify(a1[0], a2[0]))
foreach (var i71 in Unify(a1[1], a2[1]))
foreach (var i72 in Unify(a1[2], a2[2]))
foreach (var i73 in Unify(a1[3], a2[3]))
foreach (var i74 in Unify(a1[4], a2[4]))
foreach (var i75 in Unify(a1[5], a2[5]))
foreach (var i76 in Unify(a1[6], a2[6]))
yield return false;
}
private static IEnumerable UnifyArrays8(object[] a1, object[] a2)
{
foreach (var i80 in Unify(a1[0], a2[0]))
foreach (var i81 in Unify(a1[1], a2[1]))
foreach (var i82 in Unify(a1[2], a2[2]))
foreach (var i83 in Unify(a1[3], a2[3]))
foreach (var i84 in Unify(a1[4], a2[4]))
foreach (var i85 in Unify(a1[5], a2[5]))
foreach (var i86 in Unify(a1[6], a2[6]))
foreach (var i87 in Unify(a1[7], a2[7]))
yield return false;
// ReSharper restore UnusedVariable
}
private static IEnumerable UnifyArrays9(object[] a1, object[] a2)
{
// ReSharper disable UnusedVariable
foreach (var i80 in Unify(a1[0], a2[0]))
foreach (var i81 in Unify(a1[1], a2[1]))
foreach (var i82 in Unify(a1[2], a2[2]))
foreach (var i83 in Unify(a1[3], a2[3]))
foreach (var i84 in Unify(a1[4], a2[4]))
foreach (var i85 in Unify(a1[5], a2[5]))
foreach (var i86 in Unify(a1[6], a2[6]))
foreach (var i87 in Unify(a1[7], a2[7]))
foreach (var i88 in Unify(a1[8], a2[8]))
yield return false;
// ReSharper restore UnusedVariable
}
private static IEnumerable UnifyArrays10(object[] a1, object[] a2)
{
// ReSharper disable UnusedVariable
foreach (var i80 in Unify(a1[0], a2[0]))
foreach (var i81 in Unify(a1[1], a2[1]))
foreach (var i82 in Unify(a1[2], a2[2]))
foreach (var i83 in Unify(a1[3], a2[3]))
foreach (var i84 in Unify(a1[4], a2[4]))
foreach (var i85 in Unify(a1[5], a2[5]))
foreach (var i86 in Unify(a1[6], a2[6]))
foreach (var i87 in Unify(a1[7], a2[7]))
foreach (var i88 in Unify(a1[8], a2[8]))
foreach (var i89 in Unify(a1[9], a2[9]))
yield return false;
// ReSharper restore UnusedVariable
}
#pragma warning restore 414, 168, 219
#endregion
#region Checking unifiability
///
/// Returns true if its arguments are unifiable but does not actually unify them.
///
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "v"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unifiable")]
public static bool Unifiable(object v1, object v2)
{
List vars = null;
List values = null;
return Unifiable(v1, v2, ref vars, ref values);
}
///
/// Determine if V1 and V2 can be unified, without actually unifying them.
/// If they are unifiable, determine the substitions that would be necessary to unify them.
///
/// Value to test for unifiability
/// Other value to test
/// Variables in unifier
/// Values in unifier
/// True if V1 and V2 can be unified.
internal static bool Unifiable(object v1, object v2, ref List vars, ref List values)
{
v1 = CanonicalizeWithExplicitBindingList(v1, vars, values);
v2 = CanonicalizeWithExplicitBindingList(v2, vars, values);
if (v1 == v2)
// Fast path
return true;
var l1 = v1 as LogicVariable;
if (l1 != null)
{
// v1 is an unbound variable; bind it.
AddBinding(l1, v2, ref vars, ref values);
return true;
}
var l2 = v2 as LogicVariable;
if (l2 != null)
{
// v2 is an unbound variable; bind it
AddBinding(l2, v1, ref vars, ref values);
return true;
}
// Neither is an unbound variable
var s1 = v1 as Structure;
var s2 = v2 as Structure;
if (s1 != null && s2 != null)
{
// They're both structures
if (s1.Functor != s2.Functor || s1.Arguments.Length != s2.Arguments.Length)
return false;
for (int i = 0; i < s1.Arguments.Length; i++)
if (!Unifiable(s1.Arguments[i], s2.Arguments[i], ref vars, ref values))
return false;
return true;
}
// None of the above
if (v1 == null)
return v2 == null;
return v1.Equals(v2);
}
///
/// Perform substitutions from var/values, and dereference any variables
///
/// Value to canonicalize
/// Variables to be substituted
/// Values to substitute for the variables
/// Substituted and dereferenced value.
static object CanonicalizeWithExplicitBindingList(object value, List vars, List values)
{
value = Deref(value);
if (vars == null)
return value;
var lv = value as LogicVariable;
while (lv != null)
{
int bindingPosition;
if ((bindingPosition = vars.IndexOf(lv)) >= 0)
{
// it's aliased by the binding list
value = values[bindingPosition];
lv = value as LogicVariable;
}
else
// It's not in the binding list, so we're done.
return value;
}
return value;
}
static void AddBinding(LogicVariable lv, object value, ref List vars, ref List values)
{
if (vars == null)
{
vars = new List { lv };
values = new List { value };
}
else
{
vars.Add(lv);
values.Add(value);
}
}
#endregion
#region Alpha conversion and copying
///
/// Substitutes occurances of newVars for all occurances of oldVars in argList.
/// Returns new array, if substitutions were made, the original array if not.
/// Original array is not modified
///
internal static object[] AlphaConvertArglist(object[] argList, List oldVars, LogicVariable[] newVars, PrologContext context, bool evalIndexicals)
{
object[] newArgs = null;
for (int i = 0; i < argList.Length; i++)
{
var term = argList[i] as AlphaConvertibleTerm;
if (term != null)
{
object converted = term.AlphaConvert(oldVars, newVars, context, evalIndexicals);
if (converted != argList[i])
{
if (newArgs == null)
{
newArgs = new object[argList.Length];
argList.CopyTo(newArgs, 0);
}
newArgs[i] = converted;
}
}
}
// Return newArgs unless it turns out it was never actually allocated
// (in which case none of the args had renamed vars)
return newArgs ?? argList;
}
///
/// Recopy the term to replace bound variables with their values and alpha convert any unbound variables.
/// This has the effect of removing interference between this term and the copy should one or the other
/// have its bindings changed (either through unification or backtracking).
///
public static object CopyInstantiation(object term)
{
return CopyInstantiation(term, new Dictionary());
}
static object CopyInstantiation(object term, Dictionary subs)
{
term = Deref(term);
var l = term as LogicVariable;
if (l != null)
{
LogicVariable sub;
if (subs.TryGetValue(l, out sub))
return sub;
var l2 = new LogicVariable(l.Name);
subs[l] = l2;
return l2;
}
var t = term as Structure;
if (t == null)
return term;
var newArgs = new object[t.Arguments.Length];
for (int i = 0; i < newArgs.Length; i++)
newArgs[i] = CopyInstantiation(t.Argument(i), subs);
return new Structure(t.Functor, newArgs);
}
#endregion
#region Syntactic equality
///
/// True if the two objects are syntactically identical.
///
///
public static bool Identical(object a, object b)
{
a = Deref(a);
b = Deref(b);
var x = a as Structure;
var y = b as Structure;
if (x == null)
{
if (a == null)
return null == b;
return a.Equals(b);
}
if (y != null)
{
// They're both structures
if (x.Functor != y.Functor || x.Arguments.Length != y.Arguments.Length)
return false;
for (int i = 0; i < x.Arguments.Length; i++)
if (!Identical(x.Arguments[i], y.Arguments[i]))
return false;
return true;
}
// a is a structure, b is not
return false;
}
#endregion
#region Prolog-format output
#if !OldPrologWriter
///
/// Converts an arbitrary object to a string in Prolog format.
///
public static string ToStringInPrologFormat(object value)
{
return ISOPrologWriter.WriteToString(value);
}
#else
public static string ToStringInPrologFormat(object value)
{
var sb = new StringBuilder();
Write(sb, value);
return sb.ToString();
}
internal static void Write(StringBuilder s, object term)
{
term = Canonicalize(term);
if (term == null)
s.Append("null");
else
{
Structure t = term as Structure;
if (t == null)
{
Symbol sym = term as Symbol;
if (sym != null)
{
if (IsQuoteNeeded(sym))
{
s.Append('\'');
s.Append(sym.Name);
s.Append('\'');
}
else
s.Append(sym.Name);
}
else
{
string str = term as string;
if (str != null)
{
s.Append('"');
foreach (char ch in str)
switch (ch)
{
case '"':
s.Append("\\\"");
break;
case '\\':
s.Append("\\\\");
break;
default:
s.Append(ch);
break;
}
s.Append('"');
}
else
s.Append(term);
}
}
else
t.ToStringBuilder(s);
}
}
internal static void WriteAndPossiblyParenthesize(StringBuilder s, object o)
{
Structure t = o as Structure;
if (t != null && t.IsFunctor(Symbol.Comma, 2))
{
s.Append('(');
Write(s, o);
s.Append(')');
}
else
{
Write(s, o);
}
}
static bool IsQuoteNeeded(Symbol s)
{
string name = s.Name;
if (Char.IsUpper(name[0]))
return true;
foreach (char ch in name)
if (Char.IsWhiteSpace(ch))
return true;
return false;
}
#endif
internal string DebuggerDisplay
{
get
{
return ToStringInPrologFormat(this);
}
}
#endregion
#region Utilities
///
/// An iterator that always fails.
///
static IEnumerable MakeFailEnumerator()
{
yield break;
}
internal static IEnumerable FailEnumerator = MakeFailEnumerator();
internal static IEnumerable MakeSucceedEnumerator()
{
yield return false;
}
internal static IEnumerable ToEnumerator(bool succeed)
{
return succeed ? MakeSucceedEnumerator() : FailEnumerator;
}
#endregion
internal static Structure PredicateIndicatorExpression(Structure term)
{
return new Structure(Symbol.Slash, term.Functor, term.Arguments.Length);
}
internal static Structure PredicateIndicatorExpression(Symbol functor, int arity)
{
return new Structure(Symbol.Slash, functor, arity);
}
}
}