#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
{
///
/// Represents all the KB information about a predicate
///
[DebuggerDisplay("{Name}")]
class PredicateInfo
{
///
/// Creates a blank DB entry for the specified functor
///
public PredicateInfo(Symbol functorName, int arity, KnowledgeBase kb)
{
Name = functorName;
Arity = arity;
Entries = new List();
KnowledgeBase = kb;
}
///
/// Name of the predicate
///
public Symbol Name { get; private set; }
///
/// Arity of the predicate
///
public int Arity { get; private set; }
///
/// Whether the predicate should be traced
///
public bool Trace { get; set; }
///
/// Whether the predicate can be randomized
///
public bool Randomizable { get; set; }
///
/// Whether the predicate can shadow definitions in the parent KB
///
public bool Shadow { get; set; }
///
/// This PredicateInfo is a placeholder for a predicate that is either optionally defined or is defined in a different KB.
///
public bool External { get; set; }
///
/// This predicate is expected to be called from outside, so don't worry that it's unreferenced
///
public bool Public { get; set; }
///
/// The KB within which this predicate is defined.
///
public KnowledgeBase KnowledgeBase { get; private set; }
///
/// Indicies of the predicate's arguments that are higher order (i.e. goals to be called).
///
public int[] HigherOrderArguments { get; set; }
///
/// Specific KnowledgeBaseEntry objects for this predicate.
///
public List Entries { get; private set; }
///
/// True if this predicate has been called since the last time it is modified.
/// If it's false, then it's safe to overwrite the current list in place.
///
private bool entriesListUsed;
public bool Compiled
{
get { return Entries.Count>0 && Entries[0] is ByteCompiledRule; }
}
///
/// Byte compiles all the rules in this predicate.
///
public void Compile()
{
for (int i=0; i
/// Prints to the console the disassembled bytecode for all rules in this predicate.
///
public void Disassemble()
{
foreach (var knowledgeBaseEntry in Entries)
{
var rule = (ByteCompiledRule)knowledgeBaseEntry;
Console.WriteLine("");
Console.Write(ISOPrologWriter.WriteToString(rule.Head));
Console.Write(" :- \n ");
Console.Write(ISOPrologWriter.WriteToString(rule.Body));
Console.WriteLine(".");
rule.Disassemble();
}
}
internal IEnumerable StackCall(PrologContext context)
{
if (Compiled)
return TestCompiledClauses(context);
return Prove(context.GetCallArgumentsAsArray(Arity), context);
}
private IEnumerable TestCompiledClauses(PrologContext context)
{
foreach (var knowledgeBaseEntry in Entries)
{
var rule = (ByteCompiledRule)knowledgeBaseEntry;
foreach (var result in rule.StackCall(context))
if (result == CutState.ForceFail)
yield break;
else
yield return result;
}
}
internal IEnumerable Prove(object[] args, PrologContext context)
{
var myFrame = context.CurrentFrame;
if (KnowledgeBase.Trace || Trace)
context.TraceOutput("Goal: {0}", new Structure(Name, args));
if (Compiled)
{
context.PushArguments(args);
return StackCall(context);
}
if (context.Randomize && Randomizable && Entries.Count > 1)
return TestShuffledClauses(args, context, myFrame);
return TestClausesInOrder(args, context, myFrame);
}
///
/// Tests clauses in the order they appear in the database.
///
IEnumerable TestClausesInOrder(object[] args, PrologContext context, ushort myFrame)
{
var mark = context.MarkTrail();
var argIndexers = PredicateArgumentIndexer.ArglistIndexers(args);
entriesListUsed = true;
foreach (var entry in Entries)
{
if (entry.Prematch(argIndexers))
{
context.SetCurrentRule(entry);
foreach (var cutState in entry.Prove(args, context, myFrame))
{
if (cutState == CutState.ForceFail)
{
if (KnowledgeBase.Trace || Trace)
context.TraceOutput("Cut: {0}", new Structure(Name, args));
goto fail;
}
if (KnowledgeBase.Trace || Trace)
context.TraceOutput("Succeed: {0}", new Structure(Name, args));
yield return CutState.Continue;
if (KnowledgeBase.Trace || Trace)
context.TraceOutput("Retry: {0}", new Structure(Name, args));
}
}
}
fail:
context.RestoreVariables(mark);
if (KnowledgeBase.Trace || Trace)
context.TraceOutput("Fail: {0}", new Structure(Name, args));
//context.UnwindStack(Name, args);
context.UnwindStack(myFrame);
}
///
/// Tests clauses in a randomized order (but still exhaustively).
/// Uses Shuffler to generate a random permutation.
///
IEnumerable TestShuffledClauses(object[] args, PrologContext context, ushort myFrame)
{
entriesListUsed = true;
var mark = context.MarkTrail();
var shuffler = new Shuffler((ushort)Entries.Count);
var argIndexers = PredicateArgumentIndexer.ArglistIndexers(args);
while (!shuffler.Done)
{
var entry = Entries[shuffler.Next()];
if (entry.Prematch(argIndexers))
{
// This shouldn't be here...
//context.PushGoalStack(Name, args, myFrame);
context.SetCurrentRule(entry);
foreach (var cutState in entry.Prove(args, context, myFrame))
{
if (cutState == CutState.ForceFail)
{
if (KnowledgeBase.Trace || Trace)
context.TraceOutput("Cut: {0}", new Structure(Name, args));
goto fail;
}
if (KnowledgeBase.Trace || Trace)
context.TraceOutput("Succeed: {0}", new Structure(Name, args));
yield return CutState.Continue;
if (KnowledgeBase.Trace || Trace)
context.TraceOutput("Retry: {0}", new Structure(Name, args));
}
}
}
fail:
context.RestoreVariables(mark);
if (KnowledgeBase.Trace || Trace)
context.TraceOutput("Fail: {0}", new Structure(Name, args));
//context.UnwindStack(Name, args);
context.UnwindStack(myFrame);
}
List GetEntriesListForUpdate()
{
List entries = Entries;
if (entriesListUsed)
{
entriesListUsed = false;
return Entries = new List(entries);
}
return this.Entries;
}
///
/// Adds a KnowledgeBaseRule to the predicate.
/// NOT THREADSAFE!
///
/// The rule to add
/// If true, adds to be beginning, else the end.
public void Assert(KnowledgeBaseRule assertion, bool atEnd)
{
var entries = GetEntriesListForUpdate();
if (atEnd)
entries.Add(assertion);
else
entries.Insert(0, assertion);
}
public void RetractAll(Structure head)
{
var entries = GetEntriesListForUpdate();
for (int i = entries.Count - 1; i >= 0; i--)
{
if (Term.Unifiable(head, entries[i].Head))
{
entries.RemoveAt(i);
}
}
}
public IEnumerable Retract(Structure head, object body)
{
var entries = GetEntriesListForUpdate();
bool gotOne = true;
while (gotOne && entries.Count>0)
{
gotOne = false;
for (int i = 0; !gotOne && i < entries.Count; i++)
{
var entry = entries[i];
// Have to recopy the rule just in case it's being used in a pending subgoal.
// If it is, then the subgoal will see a modified version of the rule.
var rule = (Structure)Term.CopyInstantiation(new Structure(Symbol.Implication, entry.Head, entry.Body));
#pragma warning disable 168
// ReSharper disable UnusedVariable
foreach (var ignore1 in Term.Unify(head, rule.Argument(0)))
foreach (var ignore2 in Term.Unify(body, rule.Argument(1)))
// ReSharper restore UnusedVariable
#pragma warning restore 168
{
gotOne = true;
entries.RemoveAt(i);
yield return CutState.Continue;
entries = GetEntriesListForUpdate();
}
}
}
}
public IEnumerable FindClauses(Structure head, object body)
{
foreach (var entry in Entries)
{
var rule =
(Structure) Term.CopyInstantiation(new Structure(Symbol.Implication, entry.Head, entry.Body));
#pragma warning disable 414, 168, 219
// ReSharper disable UnusedVariable
foreach (var ignore1 in Term.Unify(rule.Argument(0), head))
{
foreach (var ignroe2 in Term.Unify(rule.Argument(1), body))
#pragma warning restore 414, 168, 219
// ReSharper restore UnusedVariable
yield return CutState.Continue;
}
}
}
public override string ToString()
{
return string.Format("PredicateInfo({0}/{1})", this.Name, this.Arity);
}
}
}