Enhanced EmbeddedScript error messages

This commit is contained in:
2020-12-10 12:38:21 -07:00
parent 209f2c467e
commit 734a7f2142
5 changed files with 81 additions and 26 deletions

View File

@@ -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<Command> = null;
private var breakPoints:Map<Int, () -> 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 <T extends EmbeddedScript> 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;
}
}

View File

@@ -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<Interp> = [];
public static function runAtCompileTime(exp:ReaderExp, k:KissState, ?args:Map<String, Dynamic>):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');

View File

@@ -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');

View File

@@ -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;

View File

@@ -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)]);
}
}