#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 { /// /// Implements rules in a bytecode language vaguely similar to the WAM. /// public class ByteCompiledRule : KnowledgeBaseRule { /// /// For testing purposes /// /// /// public static ByteCompiledRule FromCode(string code) { var term = (Structure)(new ISOPrologReader(code).ReadTerm()); Structure head; Structure[] body; if (term.IsFunctor(Symbol.Intern(":-"), 2)) { head = Term.Structurify(term.Argument(0), "Bad rule head."); var bodyList = new List(); UnwindCommaExpression(term.Argument(1), bodyList); body = bodyList.ToArray(); } else { head = Term.Structurify(term, "Bad rule head"); body = new Structure[0]; } return new ByteCompiledRule(KnowledgeBase.Global.EntryForStoring(head.PredicateIndicator), head, body, "none", 1); } /// /// Testing jig for byte compiler /// public void Test(string call) { var term = (Structure)(new ISOPrologReader(call).ReadTerm()); if (term.Arity != predicate.Arity) throw new Exception("Wrong number of arguments"); using (var c = PrologContext.Allocate(KnowledgeBase.Global, null)) { c.PushArguments(term.Arguments); int solutions = 0; foreach (var x in StackCall(c)) { foreach (var arg in term.Arguments) if (arg is LogicVariable) { var l = arg as LogicVariable; Console.WriteLine("{0}={1}", l.Name, ISOPrologWriter.WriteToString(l.Value)); } Console.WriteLine(x); if (solutions++ > 10) { Console.WriteLine("Max solutions found; terminating search"); c.PopFrame(0); goto abort; } } Console.WriteLine("fail"); abort: int tos = c.MakeFrame(0); if (tos != 0) Console.WriteLine("Error: tos is " + tos); } } internal ByteCompiledRule(PredicateInfo predicate, Structure head, Structure[] ruleBody, string source, int line) : base(head, ruleBody, false, source, line) { this.predicate = predicate; var c = new Compiler(head, ruleBody, KnowledgeBase); code = c.Code; frameSize = c.FrameSize; Disassemble(); Console.WriteLine(); } #region Fields and properties /// /// The largest allowable arity for an executable predicate. /// public const int MaxArity = 50; private readonly PredicateInfo predicate; private readonly byte[] code; private readonly int frameSize; /// /// The KB this rule is defined in. /// KnowledgeBase KnowledgeBase { get { return predicate.KnowledgeBase; } } #endregion #region Literal management class LiteralTable { private readonly List table = new List(); public T this[int index] { get { return table[index]; } } public ushort IndexOf(T item) { int position = table.IndexOf(item); if (position >= 0) return (ushort)position; table.Add(item); return (ushort)(table.Count - 1); } } private readonly static LiteralTable GlobalLiteralTable = new LiteralTable(); private readonly static LiteralTable PredicateTable = new LiteralTable(); private readonly static LiteralTable SymbolTable = new LiteralTable(); private readonly static LiteralTable PrimitiveTable = new LiteralTable(); #endregion #region Opcode definitions // In the following, OP means an addressing mode of the following form: // REG // Operand is register number REG // REG ARG // Register number REG holds, a structure, operand is its ARG'th argument // REG ARG STORE // Same as above, but store said argument into register STORE // // Length is determined by MSB of byte. If set, there's another byte. enum Opcode { MatchStructure, // functor, arity, endOfMatch, OP MatchVarFirst, // Symbol/16, OP MatchVar, // varReg, OP MatchLiteral, // literal/16, OP BuildStructure, // functor/16, arity, OP BuildVar, // symbol/16, OP BuildLiteral, // literal/16, OP BuildReg, // reg, OP CallWokenGoals, // reg, failAddress/16, successAddress/16 Call, // reg, failAddress/16, successAddress/16, predicateInfo, registerOrLiteral ... CallPrimitive, // reg, failAddress/16, successAddress/16, implementation, arity, registerOrLiteral ... } // Magic continuation addresses for use in call instructions // ReSharper disable InconsistentNaming private const ushort SuccessContinuationPC = 0x8000; // This is the final call of the rule; return success private const ushort FailContinuationPC = 0x8001; // This was the first call of the rule; return failure private const ushort CutContinuationPC = 0x8002; // This call was preceded by a cut; return CutState.ForceFail. // ReSharper restore InconsistentNaming // Other opcode-related constants private const int CallTargetOffset = 6; // Offset between Call/CallPrimitive opcode and its target private const byte NoRegister = 0xff; // Target register meaning not to write to register // So here's append([], X, X). : // MatchLiteral([], 0) // MatchVarFirst(1) % This is actually a no-op // MatchVar(1, 2) // Return // // And here's append([H|T], X, [H|T1]) :- append(T, X, T1). // MatchStructure(dot, 2, L1, 0) // MatchVarFirst(0>0>3) % H is at 3 // MatchVarFirst(0>1>4) % T is at 4 // L1 MatchVarFirst(1) % Actually a no-op // MatchStructure(dot, 2, L2, 2) // MatchVar(3, 2>0) // MatchVarFirst(5, 2>1) % T1 is at 5 // L2 Call(6, Fail, Succeed, append/3, register(4), register(2), register(5)) #endregion #region Interpreter internal IEnumerable StackCall(PrologContext context) { int traceMark = context.MarkTrail(); // On entry, the stack pointer points at the base of our arguments. int framePointer = context.MakeFrame(frameSize); ushort pc = 0; ushort writeModeEndAddress = 0; // We're in write mode if pc < writeModeEndAddress. // ReSharper disable TooWideLocalVariableScope // Resharper is confused - can't move this inside the while loop, or its lifetime changes. ushort endOfBuilds; // PC of last call instruction whose arguments have been built. // ReSharper restore TooWideLocalVariableScope while (true) { switch ((Opcode) code[pc++]) { #region Head opcodes case Opcode.MatchLiteral: { object literal = GetLiteral(ref pc); if (pc < writeModeEndAddress) { // Write mode SetOperand(context, framePointer, ref pc, literal); } else { // Read mode if (!Term.Unify(DecodeOperand(context, framePointer, ref pc), literal, context)) goto fail; } } break; case Opcode.MatchVarFirst: { if (pc < writeModeEndAddress) { // Write mode Symbol name = GetSymbol(ref pc); int register = code[pc++]; var l = new LogicVariable(name); SetOperand(context, framePointer, ref pc, l); if (register != NoRegister) context.SetStack(framePointer, register, l); } else { pc += 2; // Skip over variable name. int register = code[pc++]; // Read mode object operand = DecodeOperand(context, framePointer, ref pc); if (register != NoRegister) context.SetStack(framePointer, register, operand); } } break; case Opcode.MatchVar: { object register = Register(context, framePointer, pc++); if (pc < writeModeEndAddress) { // Write mode SetOperand(context, framePointer, ref pc, register); } else { // Read mode if (!Term.Unify(DecodeOperand(context, framePointer, ref pc), register, context)) goto fail; } } break; case Opcode.MatchStructure: { Symbol functor = GetSymbol(ref pc); int arity = code[pc++]; ushort endAddress = GetUShort(ref pc); if (pc < writeModeEndAddress) { // Write mode SetOperand(context, framePointer, ref pc, new Structure(functor, new object[arity])); } else { // Read mode object arg = DecodeOperand(context, framePointer, ref pc); var s = arg as Structure; if (s == null || !s.IsFunctor(functor, arity)) { var l = arg as LogicVariable; if (l == null) goto fail; l.UnifyWithStructure(new Structure(functor, new object[arity])); writeModeEndAddress = endAddress; } } } break; #endregion #region Body opcodes: structure building case Opcode.BuildStructure: { Symbol functor = GetSymbol(ref pc); int arity = code[pc++]; SetOperand(context, framePointer, ref pc, new Structure(functor, new object[arity])); } break; case Opcode.BuildLiteral: SetOperand(context, framePointer, ref pc, GetLiteral(ref pc)); break; case Opcode.BuildVar: { Symbol name = GetSymbol(ref pc); SetOperand(context, framePointer, ref pc, new LogicVariable(name)); } break; case Opcode.BuildReg: object registerValue = Register(context, framePointer, pc++); SetOperand(context, framePointer, ref pc, registerValue); break; #endregion #region Body: control flow case Opcode.CallWokenGoals: case Opcode.Call: case Opcode.CallPrimitive: endOfBuilds = pc; // ReSharper disable once InconsistentNaming ushort succeedPC; IEnumerator iterator; var failPC = StartCallInstruction(context, framePointer, ref pc, out succeedPC, out iterator); while (true) { if (iterator == null || iterator.MoveNext()) { // Call succeeds if (succeedPC == SuccessContinuationPC) { yield return CutState.Continue; // If we get here, then our caller is requesting a redo // Fall through and continue this iterator. if (iterator == null) goto fail; // Kluge: special case to handle CallWokenGoals instruction where there are no woken goals. } else { if (succeedPC > endOfBuilds) // Haven't run the build instructions for next goal, so break out and run them. break; pc = (ushort)(succeedPC+1); // StartCallInstruction assumes we've skipped over the opcode. // succeedPC is address of call/callprimitive instruction; next byte is iterator register failPC = this.StartCallInstruction(context, framePointer, ref pc, out succeedPC, out iterator); // Continue while loop } } else { // Call fails switch (failPC) { case FailContinuationPC: goto fail; case CutContinuationPC: goto cut; default: // failPC is address of call/callprimitive instruction; next byte is iterator register pc = (ushort) (failPC + 1); // +1 because we're skipping over the opcode iterator = (IEnumerator) context.GetStack(framePointer, code[pc++]); if (iterator == null) // Kluge: special case to handle CallWokenGoals instruction where there are no woken goals. goto fail; failPC = GetUShort(ref pc); succeedPC = GetUShort(ref pc); break; } } } break; #endregion default: Debug.Assert(false, "Bad opcode in byte compiled Prolog rule, pc="+(pc-1)+", opcode="+((Opcode)code[pc-1])); break; } } // The while loop above never terminates normally; it only yeilds or branches to one of the following labels cut: context.PopFrame(framePointer); context.RestoreVariables(traceMark); yield return CutState.ForceFail; fail: context.PopFrame(framePointer); context.RestoreVariables(traceMark); } private ushort StartCallInstruction(PrologContext context, int framePointer, ref ushort pc, out ushort succeedPC, out IEnumerator iterator) { int iteratorRegister = code[pc++]; ushort failPC = GetUShort(ref pc); succeedPC = GetUShort(ref pc); iterator = null; // Make the iterator. How we do this depends on the opcode, so re-fetch it. switch ((Opcode) code[pc - CallTargetOffset]) { case Opcode.CallWokenGoals: { if (context.GoalsHaveWoken) iterator = context.ProveAllWokenGoals().GetEnumerator(); } break; case Opcode.Call: { // It's a user-defined predicate call PredicateInfo calledPredicate = GetPredicate(ref pc); PushCallArgs(context, framePointer, predicate.Arity, ref pc); iterator = calledPredicate.StackCall(context).GetEnumerator(); } break; case Opcode.CallPrimitive: { // It's a primitive call PrologPrimitives.PrimitiveImplementation implementation = GetPrimitive(ref pc); int arity = code[pc++]; PushCallArgs(context, framePointer, arity, ref pc); iterator = PrologPrimitives.StackCall(implementation, arity, context).GetEnumerator(); } break; default: Debug.Assert(false, "Bad call opcode"); break; } context.SetStack(framePointer, iteratorRegister, iterator); return failPC; } private void PushCallArgs(PrologContext context, int framePointer, int arity, ref ushort pc) { for (int i = 0; i < arity; i++) { byte maybeReg = code[pc++]; object argumentValue = (maybeReg < 0x80) ? context.GetStack(framePointer, maybeReg & 0x7f) : GlobalLiteralTable[((maybeReg & 0x7f) << 8) + code[pc++]]; context.SetCallArg(i, argumentValue); } } private object Register(PrologContext context, int framePointer, ushort pc) { return Term.Deref(context.GetStack(framePointer, code[pc])); } private object DecodeOperand(PrologContext context, int framePointer, ref ushort pc) { int registerNumber = code[pc++]; object value = Term.Deref(context.GetStack(framePointer, registerNumber & 0x7f)); if (registerNumber < 0x80) return value; int argNumber = code[pc++]; value = ((Structure)value).Argument(argNumber&0x7f); if (argNumber>=0x80) context.SetStack(framePointer, code[pc++], value); return value; } private void SetOperand(PrologContext context, int framePointer, ref ushort pc, object newValue) { int registerNumber = code[pc++]; if (registerNumber < 0x80) context.SetStack(framePointer, registerNumber, newValue); else { object s = Term.Deref(context.GetStack(framePointer, registerNumber & 0x7f)); int argNumber = code[pc++]; object value = ((Structure) s).Arguments[argNumber & 0x7f] = newValue; if (argNumber >= 0x80) context.SetStack(framePointer, code[pc++], value); } } private object GetLiteral(ref ushort pc) { object reg = GlobalLiteralTable[GetUShort(ref pc)]; return reg; } private Symbol GetSymbol(ref ushort pc) { Symbol sym = SymbolTable[GetUShort(ref pc)]; return sym; } private PredicateInfo GetPredicate(ref ushort pc) { PredicateInfo pred = PredicateTable[GetUShort(ref pc)]; return pred; } PrologPrimitives.PrimitiveImplementation GetPrimitive(ref ushort pc) { return PrimitiveTable[GetUShort(ref pc)]; } private ushort GetUShort(ref ushort pc) { int firstByte = code[pc++]<<8; int secondByte = code[pc++]; int value = firstByte+secondByte; return (ushort)value; } #endregion #region Compiler private class Compiler { public Compiler(Structure head, Structure[] body, KnowledgeBase kb) { this.head = head; bodyGoals = body; this.knowledgeBase = kb; env = new CompileTimeEnvironment(head.Arity); Compile(); } #region Fields private readonly Structure head; private readonly Structure[] bodyGoals; private readonly List code = new List(); private readonly CompileTimeEnvironment env; private readonly KnowledgeBase knowledgeBase; #endregion /// /// PC of the next instruction to be emitted. /// private ushort CurrentPC { get { return (ushort)code.Count; } } public byte[] Code { get { return code.ToArray(); } } public int FrameSize { get { return env.MaxRegister; } } private void Compile() { CountVariableReferences(); //env.PreallocateRegisters(); CompileHead(); CompileBody(); } private void CompileHead() { object[] args = head.Arguments; for (byte i = 0; i < args.Length; i++) { CompileMatcher(NoRegister, i, args[i]); } } private void CompileMatcher(byte register, byte argument, object obj) { obj = Term.Deref(obj); var lv = obj as LogicVariable; if (lv != null) { var info = env[lv]; if (register != NoRegister || info.register != argument) // If top level and first occurance, don't bother. { env.InsureAllocated(info); if (info.AlreadyStored) { Emit(Opcode.MatchVar, info.register); } else { Emit(Opcode.MatchVarFirst, SymbolTable.IndexOf(lv.Name), info.register); } EmitOperand(register, argument); } env.OneUseCompiled(info); } else { var structure = obj as Structure; if (structure != null) { Structure s = structure; byte destinationRegister = (register==NoRegister)?argument:this.env.GetRegister(); this.Emit(Opcode.MatchStructure, SymbolTable.IndexOf(s.Functor), (byte)s.Arity); ushort backPatchAddress = this.CurrentPC; this.EmitUShort(0); this.EmitOperand(register, argument, destinationRegister); for (byte i = 0; i < s.Arity; i++) this.CompileMatcher(destinationRegister, i, s.Argument(i)); this.BackPatch(backPatchAddress, this.CurrentPC); if (register != NoRegister) this.env.FreeRegister(destinationRegister); } else { this.Emit(Opcode.MatchLiteral, GlobalLiteralTable.IndexOf(obj)); this.EmitOperand(register, argument); } } } private void CompileBody() { // Generate "neck" (link from head code to body) // This is just one instruction: CallWokenGoals ushort failAddress = CurrentPC; Emit(Opcode.CallWokenGoals, env.GetRegister()); EmitUShort(FailContinuationPC); ushort backPatchAddress = CurrentPC; EmitUShort(SuccessContinuationPC); foreach (var goal in bodyGoals) { switch (goal.Functor.Name) { case "true": // Don't bother to generate code break; case "!": failAddress = CutContinuationPC; break; default: CompileGoal(goal, ref failAddress, ref backPatchAddress); break; } } BackPatch(backPatchAddress, SuccessContinuationPC); } private void CompileGoal(Structure goal, ref ushort failAddress, ref ushort backPatchAddress) { byte continuationRegister = env.GetRegister(); // Allocate registers to goal arguments. var argRegisters = new byte[goal.Arity]; for (int i=0; i 1) { // Multiple use; need to stash it in a register env.InsureAllocated(variableInfo); Emit(Opcode.BuildVar, SymbolTable.IndexOf(logicVariable.Name)); EmitOperand(argRegister, argument, variableInfo.register); } else { // Single use. Don't bother with another register. Emit(Opcode.BuildVar, SymbolTable.IndexOf(logicVariable.Name)); EmitOperand(argRegister, argument); } } env.OneUseCompiled(variableInfo); } private void CompileBuildStructure(object term, byte argRegister, byte argument) { var s = (Structure) term; byte destinationRegister = (argRegister == NoRegister) ? argument : env.GetRegister(); Emit(Opcode.BuildStructure, SymbolTable.IndexOf(s.Functor), (byte) s.Arity); EmitOperand(argRegister, argument); for (byte i = 0; i < s.Arity; i++) CompileBuild(s.Argument(i), destinationRegister, i); if (argRegister != NoRegister) env.FreeRegister(destinationRegister); } #region Analysis /// /// Count references to variables in the head and body /// private void CountVariableReferences() { for (byte i = 0; i < head.Arity; i++ ) CountVariableReferences(head.Argument(i), true, i); foreach (var goal in bodyGoals) foreach (var goalArg in goal.Arguments) CountVariableReferences(goalArg, false, NoRegister); } /// /// Count references to variables in ARG /// private void CountVariableReferences(object arg, bool isHead, byte argRegister) { arg = Term.Deref(arg); if (arg is LogicVariable) { var l = arg as LogicVariable; if (isHead) { VariableInfo variableInfo = env[l]; if (argRegister != NoRegister && variableInfo.register == NoRegister) { variableInfo.register = argRegister; variableInfo.LockInRegister = true; } env.MarkHeadReference(l); } else env.MarkBodyReference(l); } else { var structure = arg as Structure; if (structure != null) foreach (var structureArg in structure.Arguments) this.CountVariableReferences(structureArg, isHead, NoRegister); } } #endregion #region Emit procedures void Emit(Opcode op, byte arg1) { code.Add((byte)op); code.Add(arg1); } void Emit(Opcode op, ushort arg1) { code.Add((byte)op); EmitUShort(arg1); } void Emit(Opcode op, ushort arg1, byte arg2) { code.Add((byte)op); EmitUShort(arg1); code.Add(arg2); } private void EmitUShort(ushort arg1) { code.Add((byte)(arg1>>8)); code.Add((byte)(arg1 & 0xff)); } private void EmitByte(byte arg) { code.Add(arg); } private void EmitOperand(byte register, byte argument) { if (register == NoRegister) code.Add(argument); else { code.Add((byte)(register+0x80)); code.Add(argument); } } private void EmitOperand(byte register, byte argument, byte destination) { if (register == NoRegister) { Debug.Assert(argument == destination, "Three address EmitOperand called with register=NoRegister but improper destination"); code.Add(argument); } else { code.Add((byte)(register + 0x80)); code.Add((byte)(argument + 0x80)); code.Add(destination); } } private void BackPatch(ushort pc, ushort address) { code[pc] = (byte)(address >> 8); code[pc+1] = (byte)(address & 0xff); } #endregion #region Compile time environment class VariableInfo { public int References; public int ReferencesCompiled; // ReSharper disable once InconsistentNaming public byte register = NoRegister; public bool AlreadyStored { get { return this.ReferencesCompiled > 0; } } // Never free this register. public bool LockInRegister; } class CompileTimeEnvironment { readonly Dictionary database = new Dictionary(); private byte nextRegister; readonly Stack freeRegisters = new Stack(); public int MaxRegister { get { return nextRegister; } } public CompileTimeEnvironment(int headArity) { nextRegister = (byte)headArity; } public VariableInfo this[LogicVariable variable] { get { VariableInfo result; if (!database.TryGetValue(variable, out result)) { database[variable] = result = new VariableInfo(); } return result; } } public void MarkHeadReference(LogicVariable variable) { this[variable].References++; } public void MarkBodyReference(LogicVariable variable) { this[variable].References++; } /// /// Allocates a register to the specified variable. /// private void AllocateRegister(VariableInfo info) { info.register = GetRegister(); } public byte InsureRegisterAndLock(LogicVariable var) { var info = this[var]; if (info.register == NoRegister) AllocateRegister(info); info.LockInRegister = true; return info.register; } //public void PreallocateRegisters() //{ // foreach (var pair in database) // PreallocateHeadRegisterIfNeeded(pair.Value); //} ///// ///// Allocates a register to the specified variable, if it isn't a singleton. ///// //private void PreallocateHeadRegisterIfNeeded(VariableInfo info) //{ // if (info.register == NoRegister // && (info.headReferences > 1 || info.bodyReferences > 0)) // info.register = nextRegister++; //} internal byte GetRegister() { if (freeRegisters.Count > 0) return freeRegisters.Pop(); return this.nextRegister++; } internal void FreeRegister(byte reg) { freeRegisters.Push(reg); } internal void InsureAllocated(VariableInfo info) { if (info.register == NoRegister) info.register = GetRegister(); } internal void OneUseCompiled(VariableInfo info) { info.ReferencesCompiled++; if (!info.LockInRegister && info.ReferencesCompiled == info.References && info.register != NoRegister) FreeRegister(info.register); } } #endregion } #endregion #region Disassembler /// /// Prints the byte code for the rule to System.Console. /// public void Disassemble() { ushort pc = 0; while (pc < code.Length) { var opcode = (Opcode) code[pc++]; Console.Write("L{0}: {1} ", pc - 1, opcode.ToString()); switch (opcode) { case Opcode.MatchStructure: { Symbol functor = GetSymbol(ref pc); int arity = code[pc++]; int endofMatch = GetUShort(ref pc); Console.Write("{0}/{1}, L{2}, ", functor.Name, arity, endofMatch); DisassembleOperand(ref pc); } break; case Opcode.MatchVar: case Opcode.MatchVarFirst: { if (opcode == Opcode.MatchVarFirst) Console.Write("{0}, ", GetSymbol(ref pc)); byte register = code[pc++]; Console.Write("{0}, ", FormatRegister(register)); DisassembleOperand(ref pc); } break; case Opcode.MatchLiteral: { Console.Write(GetLiteral(ref pc)); Console.Write(", "); DisassembleOperand(ref pc); } break; case Opcode.BuildStructure: { Console.Write(GetSymbol(ref pc)); Console.Write("/{0}, ", code[pc++]); DisassembleOperand(ref pc); } break; case Opcode.BuildVar: { Console.Write(GetSymbol(ref pc)); Console.Write(", "); DisassembleOperand(ref pc); } break; case Opcode.BuildReg: { byte reg = code[pc++]; Console.Write("{0}, ", FormatRegister(reg)); DisassembleOperand(ref pc); } break; case Opcode.BuildLiteral: { Console.Write(GetLiteral(ref pc)); DisassembleOperand(ref pc); } break; case Opcode.Call: case Opcode.CallPrimitive: case Opcode.CallWokenGoals: { Console.Write("{0}", FormatRegister(code[pc++])); Console.Write(", {0}", FormatLabel(GetUShort(ref pc))); Console.Write(", {0}", FormatLabel(GetUShort(ref pc))); if (opcode != Opcode.CallWokenGoals) { int arity; if (opcode == Opcode.Call) { PredicateInfo p = GetPredicate(ref pc); Console.Write(", {0}/{1}", p.Name, p.Arity); arity = p.Arity; } else { PrologPrimitives.PrimitiveImplementation impl = GetPrimitive(ref pc); arity = code[pc++]; Console.Write(", {0}/{1}", PrologPrimitives.PrimitiveName(impl), arity); } while (arity-- > 0) { Console.Write(", "); int arg = code[pc++]; if (arg < 0x80) Console.Write("{0}", FormatRegister(arg)); // It's a register else { // It's a literal arg = ((arg & 0x7f)<<8) + code[pc++]; Console.Write(GlobalLiteralTable[arg]); } } } } break; default: throw new Exception("Unknown opcode " + opcode); } Console.WriteLine(); } } private string FormatLabel(ushort label) { switch (label) { case SuccessContinuationPC: return "Succeed"; case FailContinuationPC: return "Fail"; case CutContinuationPC: return "Cut"; default: return "L" + label; } } private string FormatRegister(int r) { if (r == NoRegister) return "NoRegister"; if (r < this.predicate.Arity) return "a" + r; return "t" + (r-this.predicate.Arity); } private void DisassembleOperand(ref ushort pc) { int registerNumber = code[pc++]; Console.Write("{0}", FormatRegister(registerNumber&0x7f)); if (registerNumber < 0x80) return; int argNumber = code[pc++]; Console.Write("({0})", argNumber&0x7f); if (argNumber >= 0x80) Console.Write("=>{0}", FormatRegister(code[pc++])); } #endregion internal override IEnumerable Prove(object[] args, PrologContext context, ushort parentFrame) { throw new NotImplementedException(); } } }