#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
}
}