using System;
using System.Collections.Generic;
namespace Prolog
{
///
/// General driver for user-defined binary primitives
/// (i.e. prolog predicates written in C# by users) on atomic
/// arguments (i.e. not structures that unification will be run on).
///
/// Supports arbitrary relations, including left/right-unique relations
///
/// CAVEATS:
/// - Assumes arguments are atomic as far as Prolog is concerned; it
/// it doesn't bother doing full unification of outputs.
/// - It does not fully support reflexive relations. In particular,
/// the predicate will throw an exception if you call it with the
/// same unbound variaable for both arguments.
///
/// Type for the first argument
/// Type for the second argument
public class BinaryPrimitive
{
///
/// Adds a new user-defined binary primitive predicate given
/// C# delegates to implement its different modes. Assumes
/// arguments are always atomic, and that the relation won't
/// be called with the same unbound variable for both arguments.
///
/// Name to give to the predicate
/// Description of the operation of the predicate
/// Name of the first argument (for documentation and error messages)
/// Name of hte second argument (for documentation and error messages)
/// Implementation of predicate for case where both arguments are instantiated.
/// For non-left-unique predicates: implementation for case where only the second argument is instantiated, so the values of the first argument must be enumerated.
/// For left-unique predicates: implementation for the case where only the second argument is instantiated, so need to compute the value of the first (if any).
/// For non-right-unique predicates: implementation for case where only the first argument is instantiated, so the values of the second argument must be enumerated.
/// For right-unique predicates: implementation for the case where only the first argument is instantiated, so need to compute the value of the second (if any)
/// Implementation of the case where neither argument is instantiated.
public static void Declare(
string name,
string documentation,
string arg1Name,
string arg2Name,
Filter filter,
Arg1Enumerator arg1Enumerator = null,
Arg1Function arg1Function = null,
Arg2Enumerator arg2Enumerator = null,
Arg2Function arg2Function = null,
DoubleEnumerator doubleEnumerator = null)
{
PrologPrimitives.DefinePrimitive(
Symbol.Intern(name),
new BinaryPrimitive(
name,
arg1Name,
arg2Name,
filter,
arg1Enumerator,
arg1Function,
arg2Enumerator,
arg2Function,
doubleEnumerator).BinaryPrimitiveImplementation,
null,
documentation,
arg1Name,
arg2Name);
}
#region Constructor
private BinaryPrimitive(
string name,
string arg1Name,
string arg2Name,
Filter filterImplementation,
Arg1Enumerator arg1EnumeratorImplementation,
Arg1Function arg1FunctionImplementation,
Arg2Enumerator arg2EnumeratorImplementation,
Arg2Function arg2FunctionImplementation,
DoubleEnumerator doubleEnumeratorImplementation)
{
this.Name = name;
this.arg1Name = arg1Name;
this.arg2Name = arg2Name;
this.expectedArguments = new object[] { arg1Name, arg2Name };
filter = filterImplementation;
arg1Enumerator = arg1EnumeratorImplementation;
arg1Function = arg1FunctionImplementation;
arg2Enumerator = arg2EnumeratorImplementation;
arg2Function = arg2FunctionImplementation;
doubleEnumerator = doubleEnumeratorImplementation;
}
#endregion
#region Driver loops
private IEnumerable BinaryPrimitiveImplementation(object[] args, PrologContext context)
{
if (args.Length != 2)
throw new ArgumentCountException(Name, args, expectedArguments);
var arg1 = Term.Deref(args[0]);
var arg2 = Term.Deref(args[1]);
var arg1AsLogicVariable = arg1 as LogicVariable;
if (arg1AsLogicVariable != null)
{
var arg2AsLogicVariable = arg2 as LogicVariable;
if (arg2AsLogicVariable != null)
{
// Enumerating both arguments
if (doubleEnumerator == null)
throw new InstantiationException(
arg1AsLogicVariable,
"At least one argument to "+Name+"/2 must be instantiated.");
if (arg1AsLogicVariable == arg2AsLogicVariable)
throw new InvalidOperationException(Name + "(X,X) is not supported.");
return EnumerateBothDriver(arg1AsLogicVariable, arg2AsLogicVariable, context);
}
if (!(arg2 is T2))
throw new ArgumentTypeException(this.Name, this.arg2Name, arg1, typeof(T1));
if (arg1Function != null)
{
// It's left-unique
T1 arg1Value;
if (arg1Function((T2)arg2, out arg1Value))
return Term.UnifyAndReturnCutState(arg1, arg1Value);
return PrologPrimitives.FailImplementation;
}
// It's not left-unique
if (arg1Enumerator == null)
throw new InstantiationException(arg1AsLogicVariable, Name + "/2: first argument must be instantiated.");
return this.EnumerateArg1Driver(arg1AsLogicVariable, (T2)arg2, context);
}
var variable1 = arg2 as LogicVariable;
if (variable1 != null)
{
if (!(arg1 is T1))
throw new ArgumentTypeException(this.Name, this.arg1Name, arg1, typeof(T1));
if (arg2Function != null)
{
// It's right-unique
T2 arg2Value;
if (arg2Function((T1)arg1, out arg2Value))
return Term.UnifyAndReturnCutState(arg2, arg2Value);
return PrologPrimitives.FailImplementation;
}
// It's not right-unqiue
if (arg2Enumerator == null)
throw new InstantiationException(variable1, Name + "/2: second argument must be instantiated.");
return this.EnumerateArg2Driver((T1)arg1, variable1, context);
}
// Filtering
if (!(arg1 is T1))
throw new ArgumentTypeException(this.Name, this.arg1Name, arg1, typeof(T1));
if (!(arg2 is T2))
throw new ArgumentTypeException(this.Name, this.arg1Name, arg1, typeof(T1));
if (filter == null)
throw new InstantiatedVariableException(null, Name + ": at least one argument must be uninstantiated.");
return CutStateSequencer.FromBoolean(this.filter((T1)arg1, (T2)arg2));
}
private IEnumerable EnumerateArg2Driver(T1 arg1, LogicVariable arg2, PrologContext context)
{
int tracePointer = context.MarkTrail();
foreach (var newValue in arg2Enumerator(arg1))
{
arg2.UnifyWithAtomicConstant(newValue, context);
yield return CutState.Continue;
context.RestoreVariables(tracePointer);
}
}
private IEnumerable EnumerateArg1Driver(LogicVariable arg1, T2 arg2, PrologContext context)
{
int tracePointer = context.MarkTrail();
foreach (var newValue in arg1Enumerator(arg2))
{
arg1.UnifyWithAtomicConstant(newValue, context);
yield return CutState.Continue;
context.RestoreVariables(tracePointer);
}
}
private IEnumerable EnumerateBothDriver(LogicVariable arg1, LogicVariable arg2, PrologContext context)
{
int tracePointer = context.MarkTrail();
foreach (var pair in doubleEnumerator())
{
arg1.UnifyWithAtomicConstant(pair.Arg1, context);
arg2.UnifyWithAtomicConstant(pair.Arg2, context);
yield return CutState.Continue;
context.RestoreVariables(tracePointer);
}
}
#endregion
#region Fields
public readonly string Name;
private readonly string arg1Name;
private readonly string arg2Name;
private readonly Filter filter;
private readonly Arg1Enumerator arg1Enumerator;
private readonly Arg1Function arg1Function;
private readonly Arg2Enumerator arg2Enumerator;
private readonly Arg2Function arg2Function;
private readonly DoubleEnumerator doubleEnumerator;
private readonly object[] expectedArguments;
#endregion
#region Type definitions
public delegate bool Filter(T1 arg1, T2 arg2);
public delegate IEnumerable Arg1Enumerator(T2 arg2);
public delegate bool Arg1Function(T2 arg2, out T1 arg1);
public delegate IEnumerable Arg2Enumerator(T1 arg1);
public delegate bool Arg2Function(T1 arg1, out T2 arg2);
public struct ValuePair
{
public ValuePair(T1 arg1, T2 arg2)
{
Arg1 = arg1;
Arg2 = arg2;
}
public T1 Arg1;
public T2 Arg2;
}
public delegate IEnumerable DoubleEnumerator();
#endregion
}
//public static class BinaryPrimitiveExample
//{
// public static void DeclareTestPrimitives()
// {
// BinaryPrimitive.Declare(
// "even_sum",
// "True if arguments sum to and even number",
// "int1",
// "int2",
// // ReSharper disable once RedundantArgumentName
// filter: (a, b) => ((a + b) % 2 == 0),
// // ReSharper disable once RedundantArgumentName
// arg1Enumerator: EnumerateOne,
// arg2Enumerator: EnumerateOne,
// doubleEnumerator: EnumerateBoth);
// BinaryPrimitive.Declare(
// "double",
// "True if Y is 2*X",
// "X",
// "Y",
// filter: (x, y) => y == 2*x,
// arg2Function: (int x, out int y) => { y = 2 * x; return true; },
// arg1Function: (int y, out int x) =>
// {
// if ((y & 1) == 0)
// {
// x = y / 2;
// return true;
// }
// x = 0;
// return false;
// });
// }
// static IEnumerable EnumerateOne(int other)
// {
// int val = (other % 2 == 0) ? 0 : 1;
// while (true)
// {
// yield return val;
// val += 2;
// }
// // ReSharper disable once FunctionNeverReturns
// }
// static IEnumerable.ValuePair> EnumerateBoth()
// {
// int val = 0;
// while (true)
// {
// yield return new BinaryPrimitive.ValuePair(val, -val);
// val += 1;
// }
// // ReSharper disable once FunctionNeverReturns
// }
//}
}