From 734a7f2142613eeacf36b04e609e0eac817bd0f0 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Thu, 10 Dec 2020 12:38:21 -0700 Subject: [PATCH] Enhanced EmbeddedScript error messages --- src/kiss/EmbeddedScript.hx | 33 +++++++++++++++--- src/kiss/Helpers.hx | 63 +++++++++++++++++++++++++---------- src/kiss/Kiss.hx | 5 ++- src/kiss/Stream.hx | 3 +- src/test/cases/DSLTestCase.hx | 3 ++ 5 files changed, 81 insertions(+), 26 deletions(-) diff --git a/src/kiss/EmbeddedScript.hx b/src/kiss/EmbeddedScript.hx index b0655d3..2411c9b 100644 --- a/src/kiss/EmbeddedScript.hx +++ b/src/kiss/EmbeddedScript.hx @@ -18,14 +18,18 @@ typedef Command = () -> Void; projects/aoc/year2020/BootCode.hx **/ class EmbeddedScript { - var instructionPointer = 0; + public var instructionPointer(default, null) = 0; + var running = false; private var instructions:Array = null; private var breakPoints:Map Bool> = []; - private var onBreak:() -> Void = null; + // Break handlers accept a Dynamic argument because when fork() happens + // (1) the calling context can no longer assume the instance it constructed is the instance hitting the breakpoint. + // (2) I don't know how to put a generic parameter on a BreakHandler function type. + private var onBreak:(Dynamic) -> Void = null; - public function setBreakHandler(handler:() -> Void) { + public function setBreakHandler(handler:(Dynamic) -> Void) { onBreak = handler; } @@ -87,6 +91,25 @@ class EmbeddedScript { }) }); + classFields.push({ + pos: PositionTools.make({ + min: 0, + max: File.getContent(scriptFile).length, + file: scriptFile + }), + name: "instructionCount", + access: [APublic], + kind: FFun({ + ret: null, + args: [], + expr: macro { + if (instructions == null) + resetInstructions(); + return instructions.length; + } + }) + }); + classFields.push({ pos: PositionTools.make({ min: 0, @@ -106,9 +129,9 @@ class EmbeddedScript { if (breakPoints.exists(instructionPointer) && breakPoints[instructionPointer]()) { running = false; if (onBreak != null) { - onBreak(); + onBreak(this); } - } else if (instructionPointer >= instructions.length) { + } else if (instructionPointer < 0 || instructionPointer >= instructions.length) { running = false; } } diff --git a/src/kiss/Helpers.hx b/src/kiss/Helpers.hx index 0be308d..e8cdc40 100644 --- a/src/kiss/Helpers.hx +++ b/src/kiss/Helpers.hx @@ -187,32 +187,59 @@ class Helpers { } } + // This stack will contain multiple references to the same interp--to count how many layers deep it is. + // This stack is like top in Inception. When empty, it proves that we're not running at compiletime yet. + // When we ARE running at compiletime already, the pre-existing interp will be used + static var interps:kiss.List = []; + public static function runAtCompileTime(exp:ReaderExp, k:KissState, ?args:Map):Dynamic { var code = k.convert(exp).toString(); // tink_macro to the rescue #if test Prelude.print("Compile-time hscript: " + code); #end var parser = new Parser(); - var interp = new Interp(); - interp.variables.set("read", Reader.assertRead.bind(_, k.readTable)); - interp.variables.set("readExpArray", Reader.readExpArray.bind(_, _, k.readTable)); - interp.variables.set("ReaderExp", ReaderExpDef); - interp.variables.set("nextToken", Reader.nextToken.bind(_, "a token")); - interp.variables.set("kiss", { - Reader: { - ReaderExpDef: ReaderExpDef - } - }); - interp.variables.set("k", k); - interp.variables.set("args", args); // trippy - interp.variables.set("Helpers", Helpers); - interp.variables.set("Prelude", Prelude); + if (interps.length == 0) { + var interp = new Interp(); + interp.variables.set("read", Reader.assertRead.bind(_, k.readTable)); + interp.variables.set("readExpArray", Reader.readExpArray.bind(_, _, k.readTable)); + interp.variables.set("ReaderExp", ReaderExpDef); + interp.variables.set("nextToken", Reader.nextToken.bind(_, "a token")); + interp.variables.set("kiss", { + Reader: { + ReaderExpDef: ReaderExpDef + }, + Operand: { + fromDynamic: Operand.fromDynamic + } + }); + interp.variables.set("k", k.forCaseParsing()); + interp.variables.set("Helpers", Helpers); + interp.variables.set("Prelude", Prelude); + interp.variables.set("Std", Std); + + interps.push(interp); + } else { + interps.push(interps[-1]); + } + var parsed = parser.parseString(code); + + // TODO if an internal evaluation ever needs to end before its outer evaluation is done, + // this will cause problems because the old args will be overwritten and lost + interps[-1].variables.set("args", args); // trippy if (args != null) { for (arg => value in args) { - interp.variables.set(arg, value); + interps[-1].variables.set(arg, value); } } - var value = interp.execute(parser.parseString(code)); + var value = if (interps.length == 1) { + interps[-1].execute(parsed); + } else { + interps[-1].expr(parsed); + }; + interps.pop(); + if (value == null) { + throw CompileError.fromExp(exp, "compile-time evaluation returned null"); + } #if test Prelude.print("Compile-time value: " + Std.string(value)); #end @@ -254,8 +281,10 @@ class Helpers { throw CompileError.fromExp(innerExp, "unquote evaluated to null"); } else if (Std.isOfType(unquoteValue, ReaderExpDef)) { unquoteValue; - } else { + } else if (Reflect.getProperty(unquoteValue, "def") != null) { unquoteValue.def; + } else { + throw CompileError.fromExp(exp, "unquote didn't evaluate to a ReaderExp or ReaderExpDef"); }; default: throw CompileError.fromExp(exp, 'unquote evaluation not implemented'); diff --git a/src/kiss/Kiss.hx b/src/kiss/Kiss.hx index 2119c6c..cf7ce03 100644 --- a/src/kiss/Kiss.hx +++ b/src/kiss/Kiss.hx @@ -167,11 +167,10 @@ class Kiss { EField(convert(innerExp), field).withMacroPosOf(exp); case KeyValueExp(keyExp, valueExp): EBinop(OpArrow, convert(keyExp), convert(valueExp)).withMacroPosOf(exp); - case Quasiquote(exp): - // TODO pass args here (including the recursive args value) + case Quasiquote(innerExp): // This statement actually turns into an HScript expression before running macro { - Helpers.evalUnquotes($v{exp}, k, args).def; + Helpers.evalUnquotes($v{innerExp}, k, args).def; }; default: throw CompileError.fromExp(exp, 'conversion not implemented'); diff --git a/src/kiss/Stream.hx b/src/kiss/Stream.hx index 7fc3175..e24d51e 100644 --- a/src/kiss/Stream.hx +++ b/src/kiss/Stream.hx @@ -14,7 +14,8 @@ typedef Position = { }; class Stream { - var content:String; + public var content(default, null):String; + var file:String; var line:Int; var column:Int; diff --git a/src/test/cases/DSLTestCase.hx b/src/test/cases/DSLTestCase.hx index 5269981..fedeadb 100644 --- a/src/test/cases/DSLTestCase.hx +++ b/src/test/cases/DSLTestCase.hx @@ -8,6 +8,9 @@ import kiss.Prelude; class DSLTestCase extends Test { function testScript() { new DSLScript().run(); + } + + function testFork() { new DSLScript().fork([() -> Assert.equals(5, 5), () -> Assert.equals(7, 7)]); } }