#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 { /// /// Prolog-like logic variable /// [DebuggerDisplay("{DebuggerName}")] public sealed class LogicVariable : AlphaConvertibleTerm { #region Constructor /// /// Creates a new logic variable /// public LogicVariable(Symbol name) { Name = name; UID = ++UIDCounter; mValue = this; } /// /// Creates a new logic variable /// /// Print name for the variable public LogicVariable(string name) : this(Symbol.Intern(name)) { } #endregion #region Instance fields and non-derived properties /// /// Name (for debugging purposes) of variable. /// public Symbol Name { get; private set; } /// /// UID of the variable. /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "UID")] // ReSharper disable once InconsistentNaming public uint UID { get; private set; } /// /// The internal value slot of the variable /// DO NOT USE THIS UNLESS YOU ARE THE LOGIC VARIABLE CODE OR THE TRAIL CODE /// // ReSharper disable once InconsistentNaming internal object mValue; /// /// True if the variable is bound to a value or another logic variable /// public bool IsBound { get { return mValue != this && !(mValue is Metastructure); } } #endregion #region Derived properties [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object)"), SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object,System.Object)"), SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object)")] // ReSharper disable UnusedMember.Local string DebuggerName // ReSharper restore UnusedMember.Local { get { if (IsBound) return string.Format("{0}{1}={2}", Name.Name, UID, Value); return string.Format("{0}{1}", Name.Name, UID); } } /// /// Current value of the variable. If the variable is part of a chain of aliased variables, /// returns the variable at the end of the chain, if it is unbound, or its value, if its bound. /// public object Value { get { return Deref(mValue); } set { mValue = value; //IsBound = true; } } internal object UncanonicalizedValue { get { return mValue; } } internal Metastructure MetaBinding { get { return (mValue == null) ? null : (mValue as Metastructure); } } #endregion #region Static variables /// /// Global counter for allocating UIDs to logic variables /// // ReSharper disable InconsistentNaming static uint UIDCounter; // ReSharper restore InconsistentNaming #endregion #region Trail-based Unification /// /// Saves variable to the trail and updates it. /// void SaveAndUpdate(object value, PrologContext context) { context.SaveVariable(this); mValue = value; } public void AddSuspendedGoalSavingToTrail(Structure goal, PrologContext context) { SaveAndUpdate(new Metastructure(goal, null, context, MetaBinding), context); } internal bool UnifyWithCanonicalValue(object value, PrologContext context) { if (!IsBound) { Metastructure m = MetaBinding; if (m == null) { // We're binding a truly unbound variable to something. if (value != this) { var xl = value as LogicVariable; if (xl != null) { Metastructure xm2; if ((xm2 = xl.MetaBinding) != null) { // We're binding a truly unbound variable to a meta-bound variable xl.UnifyMetaVar(xm2, this, context); return true; } } SaveAndUpdate(value, context); // sets IsBound } return true; } // This is an attributed (metabound) variable var l = value as LogicVariable; if (l == null) { UnifyMetaTerm(m, value, context); return true; } Metastructure m2 = l.MetaBinding; if (m2 == null) { // Need to alias l to this, that's most easily done by letting l unify to this. UnifyMetaVar(m, l, context); return true; } UnifyMetaMeta(m, m2, l, context); return true; } return Value.Equals(value); } /// /// This is an attributed variable with attribute m, unify it with value. /// /// Current attribute /// Value to give it /// Prolog context (to get trail and wakeup list) /// Success void UnifyMetaTerm(Metastructure m, object value, PrologContext context) { Value = value; m.MetaTermUnify(value, context); } /// /// This is an attributed variable with attribute myMetaStructure, unify it with attributed variable them, with attribute theirMetaStructure. /// /// This variable's metavalue /// The meta-value of the variable we're unifying with /// The variable to unify with /// Prolog context void UnifyMetaMeta(Metastructure myMetaStructure, Metastructure theirMetaStructure, LogicVariable them, PrologContext context) { SaveAndUpdate(myMetaStructure.MetaMetaUnify(theirMetaStructure, context), context); them.SaveAndUpdate(this, context); } /// /// This is an atributed variable with metastructure m, unify with unattributed and unbound variable v. /// /// This variable's metavalue /// The completely unbound logic variable to which we are binding. /// Prolog context void UnifyMetaVar(Metastructure m, LogicVariable l, PrologContext context) { Debug.Assert(l.MetaBinding == null); l.SaveAndUpdate(this, context); this.SaveAndUpdate(m.MetaVarUnify(l, context), context); } internal override bool UnifyWithStructure(Structure value, PrologContext context) { return UnifyWithCanonicalValue(value, context); } internal override bool UnifyWithTerm(Term term, PrologContext context) { return UnifyWithCanonicalValue(term, context); } internal override bool UnifyWithAtomicConstant(object value, PrologContext context) { return UnifyWithCanonicalValue(value, context); } /// /// Attempt to unify the logic variable against the specified value (may be another logic variable or not). /// public bool Unify(object value, PrologContext context) { return Unify(this, value, context); } #endregion #region Iterator-based unification internal IEnumerable AddFrozenGoal(Structure goal, PrologContext context) { var old = MetaBinding; mValue = new Metastructure(null, goal, context, old); try { yield return CutState.Continue; } finally { mValue = old; } } internal IEnumerable AddSuspendedGoal(Structure goal, PrologContext context) { var old = MetaBinding; mValue = new Metastructure(goal, null, context, old); try { yield return CutState.Continue; } finally { mValue = (object)old??this; } } internal IEnumerable UnifyWithCanonicalValue(object value) { if (!IsBound) { Metastructure m = MetaBinding; if (m == null) { // We're binding a truly unbound variable to something. if (value != this) { var xl = value as LogicVariable; if (xl != null) { Metastructure xm2; if ((xm2 = xl.MetaBinding) != null) { // We're binding a truly unbound variable to a meta-bound variable return xl.UnifyMetaVar(xm2, this); } } Value = value; // sets IsBound } return SucceedOnceAndThenUnBind(); } // This is an attributed (metabound) variable var l = value as LogicVariable; if (l==null) return UnifyMetaTerm(m, value); Metastructure m2 = l.MetaBinding; if (m2 == null) // Need to alis l to this, that's most easily done by letting l unify to this. return UnifyMetaVar(m, l); return UnifyMetaMeta(m, m2, l); } return ToEnumerator(Value.Equals(value)); } IEnumerable UnifyMetaTerm(Metastructure m, object value) { Value = value; try { #pragma warning disable 414, 168, 219 // ReSharper disable UnusedVariable foreach (var ignore in m.MetaTermUnify(value)) // ReSharper restore UnusedVariable #pragma warning restore 414, 168, 219 yield return false; } finally { mValue = m; //IsBound = false; } } IEnumerable UnifyMetaMeta(Metastructure myMetaStructure, Metastructure theirMetaStructure, LogicVariable them) { IEnumerable filter; object mergedMetaValue = myMetaStructure.MetaMetaUnify(theirMetaStructure, out filter); mValue = mergedMetaValue??this; them.Value = this; #pragma warning disable 414, 168, 219 // ReSharper disable UnusedVariable foreach (var ignore in filter) // ReSharper restore UnusedVariable #pragma warning restore 414, 168, 219 yield return false; mValue = myMetaStructure; them.mValue = theirMetaStructure; //null; //them.IsBound = false; } IEnumerable UnifyMetaVar(Metastructure m, LogicVariable l) { Debug.Assert(l.MetaBinding == null); l.Value = this; IEnumerable goal; mValue = (object)m.MetaVarUnify(l, out goal)??this; try { #pragma warning disable 414, 168, 219 // ReSharper disable UnusedVariable foreach (var ignore in goal) // ReSharper restore UnusedVariable #pragma warning restore 414, 168, 219 yield return false; } finally { // Reset our own binding to its previous binding mValue = m; // Reset binding of l l.mValue = l; //l.IsBound = false; } } internal override IEnumerable UnifyWithStructure(Structure value) { return UnifyWithCanonicalValue(value); } internal override IEnumerable UnifyWithTerm(Term term) { return UnifyWithCanonicalValue(term); } internal override IEnumerable UnifyWithAtomicConstant(object value) { return UnifyWithCanonicalValue(value); } /// /// Attempt to unify the logic variable against the specified value (may be another logic variable or not). /// public IEnumerable Unify(object value) { return Unify(this, value); } IEnumerable SucceedOnceAndThenUnBind() { try { yield return false; } finally { //IsBound = false; this.ForciblyUnbind(); } } internal void ForciblyUnbind() { this.mValue = this; } internal IEnumerable MetaUnify(Metastructure m) { if (IsBound) throw new InvalidOperationException("Cannot meta-unify a bound variable"); Metastructure old = MetaBinding; IEnumerable filter = null; if (old != null) { var merged = old.MetaMetaUnify(m, out filter); if (merged != null) mValue = merged; else // All we have a suspended goal, which is now in the filter // there's no frozen goal, so we just unbind the variable // and see what the filter does. mValue = this; } else { mValue = m; } try { if (filter != null) #pragma warning disable 414, 168, 219 // ReSharper disable UnusedVariable foreach (var ignore in filter) // ReSharper restore UnusedVariable #pragma warning restore 414, 168, 219 yield return CutState.Continue; else yield return CutState.Continue; } finally { mValue = old; } } #endregion #region Other methods /// /// All we have to do here is check whether this is one of the variables we're looking for. /// public override object AlphaConvert(List oldVars, LogicVariable[] newVars, PrologContext context, bool evalIndexicals) { var index = oldVars.IndexOf(this); if (index < 0) return this; return newVars[index] ?? (newVars[index] = new LogicVariable(this.Name)); } /// /// The name and UID of the object /// /// public override string ToString() { if (IsBound) return (Value == null) ? "null" : Value.ToString(); return string.Format("{0}{1}", Name.Name, UID); } #endregion } }