#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;
namespace Prolog
{
///
/// Used to hold suspended goals for dif/2 and freeze/2.
/// Suspended goals are run when the logic variable to which this Suspension is bound is unified.
/// Delayed goals run on any unification, frozen only on unification with non-variable terms.
///
[DebuggerDisplay("{DebuggerDisplay}")]
sealed class Metastructure
{
///
/// Create a new set of suspended goals.
///
/// Goal to run upon unification with any value.
/// Goal to run upon unification with a non-variable term.
/// Context in which to run goals.
public Metastructure(Structure delayedGoal, Structure frozenGoal, PrologContext prologContext)
{
DelayedGoal = delayedGoal;
FrozenGoal = frozenGoal;
Context = prologContext;
}
public Metastructure(Structure delayedGoal, Structure frozenGoal, PrologContext prologContext, Metastructure old) :
this(CombineGoals(delayedGoal, old?.DelayedGoal),
CombineGoals(frozenGoal, old?.FrozenGoal),
prologContext)
{ }
///
/// Frozen goal - only runs when variable is bound to a value, but not when aliased to another
/// variable. Used in implementation of freeze/2.
///
public Structure FrozenGoal { get; }
///
/// Delayed goal - runs when variable bound to anything (including another variable).
/// Used in implementation of dif/2.
///
public Structure DelayedGoal { get; }
#region Iterator-based unification
///
/// Prolog context in which the goals were suspended. Logic variables (and hence meta structures)
/// should never be shared across contexts without term copying.
///
public readonly PrologContext Context;
///
/// Merge the information from two Metastructures into one new metastructure.
///
/// The Metastructure to merge with.
/// Test for whether the merging succeeded. Generally a woken goal.
/// The merged Metastructure.
public Metastructure MetaMetaUnify(Metastructure value, out IEnumerable filter)
{
if (value == null) throw new ArgumentTypeException("MetaMetaUnify", "value", value, typeof(Metastructure));
if (Context != value.Context) throw new ArgumentException("Can't unify suspended goals across PrologContexts.");
filter = Prover(CombineGoals(DelayedGoal, value.DelayedGoal));
return MakeSuspension(null, CombineGoals(FrozenGoal, value.FrozenGoal));
}
///
/// Called after the variable bound to this Metastructure is unified with an unbound variable
/// that is not (itself) bound to a Metastructure.
///
/// The logic variable with which to unify
/// Test for whether the merging succeeded. Generally a woken goal.
/// The Metastructure to bind to the newly aliased variables.
public Metastructure MetaVarUnify(LogicVariable them, out IEnumerable filter)
{
filter = Prover(DelayedGoal);
return MakeSuspension(null, FrozenGoal);
}
///
/// Called after the variable bound to this Metastructure is unified with a non-variable term.
///
/// The term to which to unify.
/// Iterator for any suspended goals.
public IEnumerable MetaTermUnify(object value)
{
return Prover(CombineGoals(DelayedGoal, FrozenGoal));
}
#endregion
#region Trail-based unification
///
/// Merge the information from two Metastructures into one new metastructure.
///
/// The Metastructure to merge with.
/// Context in which to execute suspended goals.
/// The merged Metastructure.
public Metastructure MetaMetaUnify(Metastructure theirMetaStructure, PrologContext context)
{
if (theirMetaStructure == null) throw new ArgumentTypeException("MetaMetaUnify", "theirMetaStructure", theirMetaStructure, typeof(Metastructure));
if (context != theirMetaStructure.Context) throw new ArgumentException("Can't unify suspended goals across PrologContexts.");
context.WakeUpGoal(CombineGoals(DelayedGoal, theirMetaStructure.DelayedGoal));
return MakeSuspension(null, CombineGoals(FrozenGoal, theirMetaStructure.FrozenGoal));
}
///
/// Called after the variable bound to this Metastructure is unified with an unbound variable
/// that is not (itself) bound to a Metastructure.
///
/// The logic variable with which to Unify.
/// Context in which to execute suspended goals.
/// The Metastructure to bind to the newly aliased variables.
public Metastructure MetaVarUnify(LogicVariable l, PrologContext context)
{
if (DelayedGoal != null)
context.WakeUpGoal(DelayedGoal);
return MakeSuspension(null, FrozenGoal);
}
///
/// Called after the variable bound to this Metastructure is unified with a non-variable term.
///
/// The term to which to unify
/// Context in which to execute suspended goals.
public void MetaTermUnify(object value, PrologContext contextOfBinding)
{
Debug.Assert(contextOfBinding == Context, "Delayed goal woken in a different context than it was created in.");
contextOfBinding.WakeUpGoal(CombineGoals(DelayedGoal, FrozenGoal));
}
#endregion
#region Utilities
IEnumerable Prover(Structure goal)
{
if (goal == null)
return CutStateSequencer.Succeed();
return Context.Prove(goal);
}
Metastructure MakeSuspension(Structure delayed, Structure frozen)
{
if (delayed == null && frozen == null)
return null;
return new Metastructure(delayed, frozen, Context);
}
static Structure CombineGoals(Structure goal1, Structure goal2)
{
if (goal1 == null)
return goal2;
if (goal2 == null)
return goal1;
return new Structure(Symbol.Comma, goal1, goal2);
}
internal string DebuggerDisplay =>
$"Suspension(delayed={Term.ToStringInPrologFormat(DelayedGoal)}, frozen={Term.ToStringInPrologFormat(FrozenGoal)})"
;
#endregion
}
}