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