Delete EmbeddedScript and fix DSLTestCase

This commit is contained in:
2024-02-15 18:36:47 -07:00
parent 7e9dd825aa
commit 4a95750fed
2 changed files with 6 additions and 264 deletions

View File

@@ -1,243 +0,0 @@
package kiss;
#if macro
import haxe.macro.Expr;
import haxe.macro.Context;
import haxe.macro.PositionTools;
import sys.io.File;
import haxe.io.Path;
using kiss.Helpers;
#end
import kiss.Kiss;
import kiss.cloner.Cloner;
// Commands handlers accept a Dynamic argument because when fork() happens
// (1) the calling context can no longer assume the instance it constructed is the instance running the command.
// (2) I don't know how to put a generic parameter <T extends EmbeddedScript> on the Command typedef.
typedef Command = (Dynamic) -> Void;
/**
Utility class for making statically typed, debuggable, embedded Kiss-based DSLs.
Basic examples:
kiss/src/test/cases/DSLTestCase.hx
projects/aoc/year2020/BootCode.hx
**/
class EmbeddedScript {
public var instructionPointer(default, null) = 0;
var running = false;
private var instructions:Array<Command> = null;
private var breakPoints:Map<Int, () -> Bool> = [];
private var onBreak:Command = null;
public function setBreakHandler(handler:Command) {
onBreak = handler;
}
public function addBreakPoint(instruction:Int, ?condition:() -> Bool) {
if (condition == null) {
condition = () -> true;
}
breakPoints[instruction] = condition;
}
public function removeBreakPoint(instruction:Int) {
breakPoints.remove(instruction);
}
public function new() {}
#if macro
public static function build(dslFile:String, scriptFile:String):Array<Field> {
var k = Kiss.defaultKissState();
k.file = scriptFile;
var classPath = Context.getPosInfos(Context.currentPos()).file;
var loadingDirectory = Path.directory(classPath);
var classFields = []; // Kiss.build() will already include Context.getBuildFields()
var commandList:Array<Expr> = [];
// This brings in the DSL's functions and global variables.
// As a side-effect, it also fills the KissState with the macros and reader macros that make the DSL syntax
classFields = classFields.concat(Kiss.build(dslFile, k));
scriptFile = Path.join([loadingDirectory, scriptFile]);
Context.registerModuleDependency(Context.getLocalModule(), scriptFile);
Kiss._try(() -> {
#if profileKiss
Kiss.measure('Compiling kiss: $scriptFile', () -> {
#end
Reader.readAndProcess(Stream.fromFile(scriptFile), k, (nextExp) -> {
var expr = Kiss.readerExpToHaxeExpr(nextExp, k);
if (expr != null) {
var c = macro function(self) {
$expr;
};
commandList.push(c.expr.withMacroPosOf(nextExp));
}
// This return is essential for type unification of concat() and push() above... ugh.
return;
// TODO also allow label setting and multiple commands coming from the same expr?
// Multiple things could come from the same expr by returning begin, or a call to a function that does more stuff
// i.e. knot declarations need to end the previous knot, and BELOW that set a label for the new one, then increment the read count
// TODO test await
});
null;
#if profileKiss
});
#end
});
classFields.push({
pos: PositionTools.make({
min: 0,
max: File.getContent(scriptFile).length,
file: scriptFile
}),
name: "resetInstructions",
access: [APrivate],
kind: FFun({
ret: null,
args: [],
expr: macro this.instructions = [$a{commandList}]
})
});
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,
max: File.getContent(scriptFile).length,
file: scriptFile
}),
name: "step",
access: [APublic],
kind: FFun({
ret: null,
args: [],
expr: macro {
if (instructions == null)
resetInstructions();
instructions[instructionPointer](this);
++instructionPointer;
if (breakPoints.exists(instructionPointer) && breakPoints[instructionPointer]()) {
running = false;
if (onBreak != null) {
onBreak(this);
}
} else if (instructionPointer < 0 || instructionPointer >= instructions.length) {
running = false;
}
}
})
});
classFields.push({
pos: PositionTools.make({
min: 0,
max: File.getContent(scriptFile).length,
file: scriptFile
}),
name: "run",
access: [APublic],
kind: FFun({
ret: null,
args: [],
expr: macro {
running = true;
while (running) {
step();
}
}
})
});
// Start a process that needs to take control of the main thread, and will call back to resume the script
classFields.push({
pos: PositionTools.make({
min: 0,
max: File.getContent(scriptFile).length,
file: scriptFile
}),
name: "await",
access: [APublic],
kind: FFun({
ret: null,
args: [
{
type: Helpers.parseComplexType("(()->Void)->Void", k, null),
name: "c"
}
],
expr: macro {
running = false;
c(run);
}
})
});
// Fork the script down two or more different commands.
classFields.push({
pos: PositionTools.make({
min: 0,
max: File.getContent(scriptFile).length,
file: scriptFile
}),
name: "fork",
access: [APublic],
kind: FFun({
ret: Helpers.parseComplexType("Array<EmbeddedScript>", k, null),
args: [
{
type: Helpers.parseComplexType("Array<Command>", k, null),
name: "commands"
}
],
expr: macro {
if (instructions == null)
resetInstructions();
return [
for (command in commands) {
// a fork needs to be a thorough copy that includes all of the EmbeddedScript subclass's
// fields, otherwise DSL state will be lost when forking, which is unacceptable
var fork = new kiss.cloner.Cloner().clone(this);
fork.instructions[instructionPointer] = command;
if (fork.breakPoints == null) {
// This field is so much trouble to clone in C# because of its type
fork.breakPoints = [for (point => condition in this.breakPoints) point => condition];
}
// trace('running a fork from ' + Std.string(instructionPointer + 1));
fork.run();
// trace("fork finished");
fork;
}
];
}
})
});
return classFields;
}
#end
}

View File

@@ -2,24 +2,12 @@ package test.cases;
import utest.Test;
import utest.Assert;
import kiss.EmbeddedScript;
import kiss.AsyncEmbeddedScript;
import kiss.AsyncEmbeddedScript2;
import kiss.Prelude;
import kiss.FuzzyMap;
import kiss.FuzzyMapTools;
class DSLTestCase extends Test {
function testScript() {
var script = new DSLScript();
script.run();
Assert.isTrue(script.wholeScriptDone);
}
function testFork() {
new DSLScript().fork([(self) -> Assert.equals(5, 5), (self) -> Assert.equals(7, 7)]);
}
function testAsync() {
var script = new AsyncDSLScript();
script.run();
@@ -53,20 +41,17 @@ class DSLTestCase extends Test {
}
}
@:build(kiss.EmbeddedScript.build("DSL.kiss", "DSLScript.dsl"))
class DSLScript extends EmbeddedScript {}
@:build(kiss.AsyncEmbeddedScript.build("", "DSL.kiss", "AsyncDSLScript.dsl"))
class AsyncDSLScript extends AsyncEmbeddedScript {}
@:build(kiss.AsyncEmbeddedScript2.build("", "DSL.kiss", "AsyncDSLScript.dsl"))
class AsyncDSLScript extends AsyncEmbeddedScript2 {}
// One of these two classes will reuse instructions from the cache, but
// I can't guarantee which one compiles first:
@:build(kiss.AsyncEmbeddedScript.build("", "DSL.kiss", "AsyncDSLScriptThatWillCache.dsl"))
class AsyncDSLScriptThatWillCache extends AsyncEmbeddedScript {}
@:build(kiss.AsyncEmbeddedScript2.build("", "DSL.kiss", "AsyncDSLScriptThatWillCache.dsl"))
class AsyncDSLScriptThatWillCache extends AsyncEmbeddedScript2 {}
@:build(kiss.AsyncEmbeddedScript.build("", "DSL.kiss", "AsyncDSLScriptThatWillCache.dsl"))
class AsyncDSLScriptThatWillCache2 extends AsyncEmbeddedScript {}
@:build(kiss.AsyncEmbeddedScript2.build("", "DSL.kiss", "AsyncDSLScriptThatWillCache.dsl"))
class AsyncDSLScriptThatWillCache2 extends AsyncEmbeddedScript2 {}
// Auto-call cc when the scripter forgets to: