#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; using System.IO; using UnityEngine; using Debug = UnityEngine.Debug; namespace Prolog { internal enum CutState { ForceFail, Continue }; /// /// Stores a collection of KnowledgeBaseEntries, indexed by predicate functor. /// [DebuggerDisplay("KnowledgeBase {Name}"),SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "KnowledgeBase")] public sealed class KnowledgeBase { #region Global KB /// /// Global KB that other KBs inherit from. /// [Documentation("Global KB that other KBs inherit from.")] public static KnowledgeBase Global { get; private set; } #endregion #region Constructors [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static KnowledgeBase() { Global = new KnowledgeBase("global", null, null); PrologPrimitives.InstallPrimitives(); } /// /// Creates a new KB that inherits from the global knowledgebase. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "KBs")] [Documentation("Creates a new KB that inherits from the global knowledgebase.")] public KnowledgeBase(string kbName, GameObject gameObject) : this(kbName, gameObject, Global) { } /// /// Creates a new KB that inherits from the specified KBs. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "KBs")] [Documentation("Creates a new KB that inherits from the specified KBs.")] public KnowledgeBase(string kbName, GameObject gameObject, KnowledgeBase parent, params KnowledgeBase[] otherImports) { if (kbName == null) throw new ArgumentNullException("kbName"); Name = kbName; this.Parent = parent; this.GameObject = gameObject; this.ELRoot = new ELNode(null, Symbol.Intern("root")); //if (parent == null) throw new ArgumentNullException("parent"); if (otherImports == null) throw new ArgumentNullException("otherImports"); foreach (var import in otherImports) if (import == null) throw new ArgumentNullException("otherImports"); if (parent != null) imports.Add(parent); imports.AddRange(otherImports); } #endregion #region Fields and properties public readonly KnowledgeBase Parent; public readonly GameObject GameObject; public readonly ELNode ELRoot; readonly SourceFileTracker sourceFiles = new SourceFileTracker(); /// /// The actual database of KnowledgeBaseEntry objects, indexed by functor /// readonly Dictionary db = new Dictionary(); /// /// List of other knowledge bases this KB should consult when trying to prove goals /// readonly List imports = new List(); /// /// Generate debugging output when set /// public bool Trace { get; set; } /// /// Name (for debugging purposes) /// public string Name { get; private set; } #endregion public override string ToString() { return string.Format("", Name); } #region Proving goals /// /// Attempts to prove the specified goal. /// WARNING: THIS WILL LEAK A PROLOG CONTEXT UNLESS ENUMERATED TO COMPLETION. /// public IEnumerable Prove(Structure t) { using (var prologContext = PrologContext.Allocate(this, null)) { var enumerator = Prove(t.Functor, t.Arguments, prologContext, 0).GetEnumerator(); bool done = false; while (!done) { try { done = !enumerator.MoveNext() || enumerator.Current == CutState.ForceFail; } catch { PrologContext.LastExceptionContext = prologContext; throw; } if (!done) yield return false; } } } /// /// True if the specified goal is provable within this KnowledgeBase. /// /// Goal to attempt to prove /// The value to give ot the $this indexical while running the goal /// Success public bool IsTrue(object goal, object thisValue=null) { var t = Term.Structurify(goal, "Argument to IsTrue() should be a valid Prolog goal."); bool result; using (var prologContext = PrologContext.Allocate(this, thisValue)) { try { result = Prove(t.Functor, t.Arguments, prologContext, 0).GetEnumerator().MoveNext(); } catch (InferenceStepsExceededException) { throw; } catch (Exception e) { throw new PrologError( e, prologContext.StackTrace( Prolog.CurrentSourceFile, Prolog.CurrentSourceLineNumber, "IsTrue()", false) + e.StackTrace); } } return result; } /// /// True if the specified goal is provable within this KnowledgeBase. /// /// Value of variable to return /// Goal to attempt to prove /// If true, SolveFor will throw a GoalException if the goal fails /// Value to give ot the indexical $this during execution /// Success public object SolveFor(LogicVariable result, object goal, object thisValue, bool throwOnFailure = true) { if (this.IsTrue(Term.Structurify(goal, "Argument to SolveFor() should be a valid Prolog goal."), thisValue)) return Term.CopyInstantiation(result); if (throwOnFailure) throw new GoalException(goal, "Goal is unsatisfiable"); return null; } /// /// Attempts to prove the specified goal. /// internal IEnumerable Prove(Symbol functor, object[] args, PrologContext context, ushort parentFrame) { context.PushGoalStack(functor, args, parentFrame); context.NewStep(); PrologPrimitives.PrimitiveImplementation prim; if (PrologPrimitives.Implementations.TryGetValue(functor, out prim)) { return CallPrimitive(functor, prim, args, context); } return ProveFromDB(functor, args, context); } IEnumerable CallPrimitive(Symbol functor, PrologPrimitives.PrimitiveImplementation handler, object[] args, PrologContext context) { if (Trace) context.TraceOutput("Goal: {0}", new Structure(functor, args)); foreach (var state in handler(args, context)) { if (Trace) context.TraceOutput((state == CutState.Continue) ? "Succeed: {0}" : "Cut: {0}", new Structure(functor, args)); yield return state; if (Trace) context.TraceOutput("Retry: {0}", new Structure(functor, args)); } if (Trace) context.TraceOutput("Fail: {0}", new Structure(functor, args)); context.PopGoalStack(); } internal static bool ErrorOnUndefined = true; // ReSharper disable once InconsistentNaming IEnumerable ProveFromDB(Symbol functor, object[] args, PrologContext context) { PredicateInfo info = GetPredicateInfo(this, new PredicateIndicator(functor, args.Length)); if (info == null) { if (ErrorOnUndefined) throw new UndefinedPredicateException(functor, args.Length); return PrologPrimitives.FailImplementation; } return info.Prove(args, context); } #endregion #region Database search /// /// True if the specified functor/arity is undefined. /// public bool Undefined(PredicateIndicator p) { if (PrologPrimitives.IsDefined(p)) return false; if (CheckForPredicateInfoInThisKB(p) != null) return false; foreach (KnowledgeBase import in imports) if (!import.Undefined(p)) return false; return true; } static PredicateInfo GetPredicateInfo(KnowledgeBase kb, PredicateIndicator p) { PredicateInfo result; if ((result = kb.CheckForPredicateInfoInThisKB(p)) != null) return result; foreach (KnowledgeBase import in kb.imports) if ((result = GetPredicateInfo(import, p)) != null) return result; return null; } PredicateInfo CheckForPredicateInfoInThisKB(Symbol functor, int arity) { return CheckForPredicateInfoInThisKB(new PredicateIndicator(functor, arity)); } PredicateInfo CheckForPredicateInfoInThisKB(PredicateIndicator p) { PredicateInfo entry; if (!db.TryGetValue(p, out entry)) return null; return entry; } internal PredicateInfo CheckForPredicateInfo(PredicateIndicator p) { PredicateInfo info = CheckForPredicateInfoInThisKB(p); if (info != null) return info; foreach (KnowledgeBase import in imports) if ((info = import.CheckForPredicateInfo(p)) != null) return info; return null; } internal PredicateInfo EntryForStoring(PredicateIndicator p) { PredicateInfo entry; if (!db.TryGetValue(p, out entry)) { db[p] = entry = new PredicateInfo(p.Functor, p.Arity, this); } return entry; } internal List EntryListForStoring(PredicateIndicator p) { return EntryForStoring(p).Entries; } internal IEnumerable FindClauses(Structure head, object body) { PredicateInfo i = CheckForPredicateInfo(head.PredicateIndicator); return (i==null)?CutStateSequencer.Fail():i.FindClauses(head, body); } /// /// Walk through all the rules of all the predicates defined in this KB. /// internal IEnumerable Rules { get { foreach (var pair in db) { PredicateInfo info = pair.Value; if (info != null && info.Entries != null) foreach (var kbEntry in info.Entries) { var rule = kbEntry as KnowledgeBaseRule; if (rule != null) yield return rule; } } } } /// /// Walk through all the predicates defined in this KB. /// internal IEnumerable Predicates { get { foreach (var pair in db) yield return pair.Value; } } #endregion #region Database modification /// /// Add a term (fact or rule) to the KB. /// public void Assert(Structure structure, bool atEnd, bool checkSingletons) { if (structure == null) throw new ArgumentNullException("structure", "Term to add to KB may not be null."); //structure = structure.Expand(); if (structure == null) throw new ArgumentNullException("structure"); Structure head = structure.IsFunctor(Symbol.Implication, 2) ? Term.Structurify(structure.Argument(0), "Head of :- must be a valid proposition or predicate.") : structure; if (head.IsFunctor(Symbol.ColonColon, 2)) { var argument = head.Argument(0); var kb = argument as KnowledgeBase; if (kb == null) { var o = argument as GameObject; if (o != null) kb = o.KnowledgeBase(); else { var c = argument as Component; if (c != null) kb = c.KnowledgeBase(); else throw new ArgumentTypeException( "assert", "knowledgebase", argument, typeof(KnowledgeBase)); } } if (structure.IsFunctor(Symbol.Implication, 2)) kb.Assert( new Structure(Symbol.Implication, head.Argument(1), structure.Argument(1)), atEnd, checkSingletons); else { kb.Assert(structure.Argument(1), atEnd, checkSingletons); } } else { if (PrologPrimitives.Implementations.ContainsKey(head.Functor)) throw new PrologException( new Structure( "error", new Structure( "permission_error", Symbol.Intern("modify"), Symbol.Intern("static_procedure"), Term.PredicateIndicatorExpression(head)))); KnowledgeBaseRule assertion = KnowledgeBaseRule.FromTerm( structure, checkSingletons, Prolog.CurrentSourceFile, Prolog.CurrentSourceLineNumber); PredicateInfo info = EntryForStoring(head.PredicateIndicator); PredicateInfo parentInfo; if (!info.Shadow && this.Parent != null && (parentInfo = this.Parent.CheckForPredicateInfoInThisKB(head.PredicateIndicator)) != null && !parentInfo.External) throw new PrologException( new Structure( "error", new Structure( "permission_error", Symbol.Intern("shadow"), Term.PredicateIndicatorExpression(head)))); info.Assert(assertion, atEnd); } } /// /// Add a term (fact or rule) to the KB. /// public void Assert(object term, bool atEnd, bool checkSingletons) { if (term == null) throw new ArgumentNullException("term", "Term to assert in KB cannot be null."); if (ELProlog.IsELTerm(term)) ELProlog.Update(term, this); else { Assert( Term.Structurify(term, "Assertion is not a valid proposition or predicate."), atEnd, checkSingletons); } } /// /// Add a term (fact or rule) to the KB. /// public void AssertZ(Structure structure) { Assert(structure, true, false); } /// /// Add a term (fact or rule) to the KB. /// public void AssertZ(object term) { Assert(term, true, false); } /// /// Add a term (fact or rule) to the KB. /// public void AssertA(Structure structure) { Assert(structure, false, false); } /// /// Add a term (fact or rule) to the KB. /// public void AssertA(object term) { Assert(term, false, false); } /// /// Remove all terms matching head /// public void RetractAll(Structure head) { EntryForStoring(head.PredicateIndicator).RetractAll(head); } /// /// Remove a term from the KB. /// public void RetractAll(object term) { if (term == null) throw new ArgumentNullException("term", "Term to retract cannot be null."); RetractAll(Term.Structurify(term, "Fact is not a valid proposition or predicate.")); } internal IEnumerable Retract(Structure head, object body) { return EntryForStoring(head.PredicateIndicator).Retract(head, body); } internal IEnumerable Retract(object term) { Structure head = Term.Structurify(term, "Argument to retract must be a valid term."); object body = Symbol.True; if (head.IsFunctor(Symbol.Implication, 2)) { body = head.Argument(1); head = Term.Structurify(head.Argument(0), "Invalid clause head."); } return Retract(head, body); } /// /// Erases the complete contents of the KB /// public void Clear() { db.Clear(); } /// /// Erases all entries for the specified predicate. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor")] public void Forget(PredicateIndicator p) { EntryListForStoring(p).Clear(); } /// /// Declares that this predicate is randomizable. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Randomizable")] public void DeclareRandomizable(PredicateIndicator p) { EntryForStoring(p).Randomizable = true; } /// /// Declares that rules for this predicate within this KB are allowed and override those in the parent KB. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Randomizable")] public void DeclareShadow(PredicateIndicator p) { EntryForStoring(p).Shadow = true; } /// /// Declares that this predicate is either optional or is defined in another KB. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Randomizable")] public void DeclareExternal(PredicateIndicator p) { EntryForStoring(p).External = true; } /// /// Declares that this predicate is called from outside, so don't generate unreferenced predicate warnings. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Randomizable")] public void DeclarePublic(PredicateIndicator p) { EntryForStoring(p).Public = true; } /// /// Declares that this predicate may call its arguments. Used by the static checker to help filter out unreferenced predicates. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Randomizable")] public void DeclareHigherOrderArguments(PredicateIndicator p, int[] arguments) { foreach (var i in arguments) if (i>=p.Arity) throw new ArgumentException("Argument index larger than arity of predicate: "+i); else if (i < 0) throw new ArgumentException("Argument index cannot be less than zero: " + i); EntryForStoring(p).HigherOrderArguments = arguments; } /// /// Declares that this predicate is randomizable. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor")] public void DeclareTraced(PredicateIndicator p) { if (CheckForPredicateInfoInThisKB(p) != null) EntryForStoring(p).Trace = true; else if (this.Parent != null) this.Parent.DeclareTraced(p); else throw new UndefinedPredicateException(p); } /// /// Declares that this predicate is randomizable. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor")] public void DeclareUntraced(PredicateIndicator p) { if (CheckForPredicateInfoInThisKB(p) != null) EntryForStoring(p).Trace = false; else if (this.Parent != null) this.Parent.DeclareTraced(p); else throw new UndefinedPredicateException(p); } #endregion #region Predicate compilation and disassembly /// /// Compiles the predicate /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Randomizable")] public void Compile(PredicateIndicator p) { EntryForStoring(p).Compile(); } /// /// Disassembles the copmiled code of the predicate /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Randomizable")] public void Disassemble(PredicateIndicator p) { EntryForStoring(p).Disassemble(); } #endregion #region Consult and reconsult /// /// Load assertions into the KB, erasing any previous assertions under their functors. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Reconsult")] public void ReconsultString(string text) { using (var sr = new StringReader(text)) { string saveFileName = Prolog.CurrentSourceFile; int savedLineNumber = Prolog.CurrentSourceLineNumber; try { Prolog.CurrentSourceFile = null; Prolog.CurrentSourceLineNumber = 0; Reconsult(sr); } finally { Prolog.CurrentSourceFile = saveFileName; Prolog.CurrentSourceLineNumber = savedLineNumber; } } } /// /// Load assertions into the KB, erasing any previous assertions under their functors. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Reconsult")] public void Reconsult(string path) { if (Path.GetExtension(path) == string.Empty && Directory.Exists(path)) { foreach (var file in Directory.GetFiles(Prolog.LoadDirectoryPath(path))) if (IsSourceFile(file)) Reconsult(file); } else { path = Prolog.LoadFilePath(path); using (var stream = File.OpenText(path)) { string savedFileName = Prolog.CurrentSourceFile; int savedLineNumber = Prolog.CurrentSourceLineNumber; try { Prolog.CurrentSourceFile = path; Prolog.CurrentSourceLineNumber = 0; Reconsult(new PositionTrackingTextReader(stream, path)); } finally { Prolog.CurrentSourceFile = savedFileName; Prolog.CurrentSourceLineNumber = savedLineNumber; } } } } /// /// Load assertions into the KB, erasing any previous assertions under their functors. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Reconsult")] public void Reconsult(Stream stream) { Reconsult(new StreamReader(stream)); } /// /// Load assertions into the KB, erasing any previous assertions under their functors. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Reconsult")] public void Reconsult(TextReader inStream) { if (sourceFiles.Contains(Prolog.CurrentSourceFile)) { // Remove any clauses asserted previously by this file. if (Prolog.CurrentSourceFile != null) foreach (var pair in db) { var info = pair.Value; if (info != null) info.Entries.RemoveAll(kbe => kbe.SourceFile == Prolog.CurrentSourceFile); } } Consult(inStream); } /// /// Load assertions into the KB /// public void ConsultString(string text) { using (var sr = new StringReader(text)) { string savedFileName = Prolog.CurrentSourceFile; int savedLineNumber = Prolog.CurrentSourceLineNumber; try { Prolog.CurrentSourceFile = null; Prolog.CurrentSourceLineNumber = 0; Consult(sr); } finally { Prolog.CurrentSourceFile = savedFileName; Prolog.CurrentSourceLineNumber = savedLineNumber; } } } string DefaultExtension(string path, string extension) { if (Path.GetExtension(path) == String.Empty) return Path.ChangeExtension(path, extension); return path; } /// /// Load assertions into the KB /// public void Consult(string path) { var directoryPath = Prolog.LoadDirectoryPath(path); if (Path.GetExtension(directoryPath) == string.Empty && Directory.Exists(directoryPath)) { foreach (var file in Directory.GetFiles(directoryPath)) if (IsSourceFile(file)) Consult(file); } else { path = DefaultExtension(Prolog.LoadFilePath(path), ".prolog"); using (var stream = File.OpenText(path)) { string savedFileName = Prolog.CurrentSourceFile; int savedLineNumber = Prolog.CurrentSourceLineNumber; try { Prolog.CurrentSourceFile = path; Prolog.CurrentSourceLineNumber = 0; var textReader = new PositionTrackingTextReader(stream, path); if (Path.GetExtension(path) == ".csv") { var functor = Symbol.Intern(Path.GetFileNameWithoutExtension(path)); this.IsTrue(new Structure("begin_csv_loading", functor)); // Ignore return value new CSVParser(functor, ',', textReader).Read(this.LoadCSVRow); this.IsTrue(new Structure("end_csv_loading", functor)); // Ignore return value } else Consult(textReader); } finally { Prolog.CurrentSourceFile = savedFileName; Prolog.CurrentSourceLineNumber = savedLineNumber; } } } } private static bool IsSourceFile(string file) { return file.EndsWith(".prolog") || file.EndsWith(".csv"); } // ReSharper disable once InconsistentNaming void LoadCSVRow(int rowNumber, Structure row) { if (!this.IsTrue(new Structure("load_csv_row", rowNumber, row))) throw new Exception(string.Format("Failed to load CSV row number {0} : {1}", rowNumber, Term.ToStringInPrologFormat(row))); } /// /// Load assertions into the KB /// public void Consult(Stream stream) { Consult(new StreamReader(stream)); } /// /// Load assertions into the KB /// [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "functor")] public void Consult(TextReader inStream) { sourceFiles.NoteFile(Prolog.CurrentSourceFile); var reader = new ISOPrologReader(inStream); reader.SkipLayout(); int lastLine = reader.LineNumber; using (var context = PrologContext.Allocate(this, this)) { try { object unexpanded; Prolog.CurrentSourceLineNumber = lastLine; while ((unexpanded = reader.ReadTerm()) != Symbol.EndOfFile) { // Perform user-level macroexpansion. object assertion = TermExpansion(unexpanded); if (ELProlog.IsELTerm(assertion)) // It's an EL term. ELProlog.Update(assertion, this); else { // It's a normal Prolog term var t = Term.Structurify( assertion, "Assertions in prolog files must be valid propositions or predicates."); // Perform built-in macroexpansion. t = t.Expand(); if (t.IsFunctor(Symbol.Implication, 1)) { context.Reset(); var goal = Term.Structurify( t.Argument(0), "Argument to a :- directive must be an atom or structure."); // Run t once, but don't backtrack for a second solution (since it's presumably an imperative anyway). Prove(goal.Functor, goal.Arguments, context, 0).GetEnumerator().MoveNext(); } else Assert(t, true, true); } reader.SkipLayout(); lastLine = reader.LineNumber; Prolog.CurrentSourceLineNumber = lastLine; } } catch (InferenceStepsExceededException e) { Repl.RecordExceptionSourceLocation(e, lastLine); throw; } catch (Exception e) { #if !DisableUnity Debug.LogException(e); Repl.RecordExceptionSourceLocation(e, lastLine); #endif throw new PrologError( e, context.StackTrace(Prolog.CurrentSourceFile, Prolog.CurrentSourceLineNumber, "consult/1", false)); } } } // ReSharper disable once InconsistentNaming private readonly Symbol term_expansion = Symbol.Intern("term_expansion"); private readonly Symbol expansion = Symbol.Intern("expansion"); private object TermExpansion(object unexpanded) { if (CheckForPredicateInfoInThisKB(term_expansion, 2) == null && Global.CheckForPredicateInfoInThisKB(term_expansion, 2) == null) // Don't bother if not defined. return unexpanded; // Attempt to expand it var expanded = new LogicVariable(expansion); // Try this KB if (CheckForPredicateInfoInThisKB(term_expansion, 2) != null) // ReSharper disable UnusedVariable #pragma warning disable 0168 foreach (var ignore in Prove(new Structure(term_expansion, unexpanded, expanded))) #pragma warning restore 0168 { return Term.CopyInstantiation(expanded); } // Try the global KB if (this != Global && Global.CheckForPredicateInfoInThisKB(term_expansion, 2) != null) #pragma warning disable 0168 foreach (var ignore in Global.Prove(new Structure(term_expansion, unexpanded, expanded))) #pragma warning restore 0168 // ReSharper restore UnusedVariable { return Term.CopyInstantiation(expanded); } // Expansion failed, so use unexpanded version. return unexpanded; } /// /// Reload any files that have been modified. /// [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String,System.Object,System.Object)")] public void ReloadModifiedSourceFiles() { foreach (var path in sourceFiles.OutOfDateFiles) { Console.WriteLine("Reloading {0} into {1} knowledge base", Path.GetFileName(path), Name); Reconsult(path); } } #endregion #region Source regeneration - listing out of all entries in the DB. /// /// All assertions in the database /// public IList Assertions { get { var terms = new List(); foreach (var pair in db) { var info = pair.Value; if (info != null) foreach (var entry in info.Entries) { var rule = entry as KnowledgeBaseRule; if (rule != null) { var head = new Structure(pair.Key.Functor, rule.HeadArgs); if (rule.BodyGoals == null || rule.BodyGoals.Length == 0) terms.Add(head); else terms.Add(new Structure(Symbol.Implication, head, Commafy(rule.BodyGoals))); } } } return terms; } } static Structure Commafy(Structure[] structures) { Structure result = structures[structures.Length - 1]; for (int i = structures.Length - 2; i >= 0; i--) result = new Structure(Symbol.Comma, structures[i], result); return result; } /// /// Reconstructs source code for assertions in database. /// public string Source { get { var s = new StringWriter(); var writer = new ISOPrologWriter(s); // ReSharper disable UnusedVariable foreach (var term in Assertions) // ReSharper restore UnusedVariable { writer.Write(term); writer.WriteString(".\n"); } return s.ToString(); } } /// /// Returns the source code for the current definition of functor. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor")] public string SourceFor(PredicateIndicator p) { // ReSharper disable once NotResolvedInText if (p.Functor == null) throw new ArgumentNullException("functor"); var s = new StringWriter(); var writer = new ISOPrologWriter(s); var predicateInfo = CheckForPredicateInfo(p); if (predicateInfo == null) throw new ArgumentException(string.Format("Unknown predicate: {0}.", p)); SourceFromPredicateInfo(p, predicateInfo, writer); return s.ToString(); } private static void SourceFromPredicateInfo(PredicateIndicator p, PredicateInfo predicateInfo, ISOPrologWriter writer) { foreach (var knowledgeBaseEntry in predicateInfo.Entries) { var rule = (KnowledgeBaseRule)knowledgeBaseEntry; var head = new Structure(p.Functor, rule.HeadArgs); Structure structure; if (rule.BodyGoals == null || rule.BodyGoals.Length == 0) { structure = head; } else { structure = new Structure(Symbol.Implication, head, Commafy(rule.BodyGoals)); } writer.Write(structure); writer.WriteString(".\n"); } } #endregion } }