Experimental AsyncEmbeddedScript caching

This commit is contained in:
2023-07-17 06:59:26 -06:00
parent 9fd25d94a5
commit bc6bbc9b97
6 changed files with 125 additions and 3 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
DSLScript*.json

View File

@@ -6,8 +6,11 @@ import haxe.macro.Context;
import haxe.macro.PositionTools;
import sys.io.File;
import haxe.io.Path;
using haxe.io.Path;
import kiss.Helpers;
using kiss.Helpers;
using tink.MacroApi;
#end
import kiss.Kiss;
@@ -15,10 +18,32 @@ import kiss.ReaderExp;
import kiss.Prelude;
import kiss.cloner.Cloner;
using StringTools;
import hscript.Parser;
import hscript.Interp;
typedef Continuation = () -> Void;
typedef AsyncCommand = (AsyncEmbeddedScript, Continuation) -> Void;
class ObjectInterp<T> extends Interp {
var obj:T;
public function new(obj:T) {
this.obj = obj;
super();
}
override function resolve(id:String):Dynamic {
var fieldVal = Reflect.field(obj, id);
if (fieldVal != null)
return fieldVal;
else
return super.resolve(id);
}
// TODO setting variables should try to set them on the object,
// but Interp.assign and Interp.evalAssignOp look very complicated to override
}
/**
Utility class for making statically typed, debuggable, ASYNC-BASED embedded Kiss-based DSLs.
Examples are in the hollywoo project.
@@ -31,6 +56,12 @@ class AsyncEmbeddedScript {
private var labels:Map<String,Int> = [];
private var noSkipInstructions:Map<Int,Bool> = [];
private var parser = new Parser();
private var interp:ObjectInterp<AsyncEmbeddedScript>;
private var hscriptInstructions:Map<Int,String> = [];
private function hscriptInstructionFile() return "";
public function setBreakHandler(handler:AsyncCommand) {
onBreak = handler;
}
@@ -46,7 +77,17 @@ class AsyncEmbeddedScript {
breakPoints.remove(instruction);
}
public function new() {}
public function new() {
interp = new ObjectInterp(this);
if (hscriptInstructionFile().length > 0) {
#if (sys || hxnodejs)
var cacheJson:haxe.DynamicAccess<String> = haxe.Json.parse(sys.io.File.getContent(hscriptInstructionFile()));
for (key => value in cacheJson) {
hscriptInstructions[Std.parseInt(key)] = value;
}
#end
}
}
private function resetInstructions() {}
@@ -56,6 +97,17 @@ class AsyncEmbeddedScript {
return instructions.length;
}
#if test
public var ranHscriptInstruction = false;
#end
private function runHscriptInstruction(instructionPointer:Int, cc:Continuation) {
#if test
ranHscriptInstruction = true;
#end
interp.variables['cc'] = cc;
interp.execute(parser.parseString(hscriptInstructions[instructionPointer]));
}
private function runInstruction(instructionPointer:Int, withBreakPoints = true) {
lastInstructionPointer = instructionPointer;
if (instructions == null)
@@ -77,8 +129,12 @@ class AsyncEmbeddedScript {
} else {
() -> {};
}
if (hscriptInstructions.exists(instructionPointer)) {
runHscriptInstruction(instructionPointer, continuation);
} else {
instructions[instructionPointer](this, continuation);
}
}
public function run(withBreakPoints = true) {
runInstruction(0, withBreakPoints);
@@ -145,6 +201,17 @@ class AsyncEmbeddedScript {
var loadingDirectory = Path.directory(classPath);
var classFields = []; // Kiss.build() will already include Context.getBuildFields()
var hscriptInstructions:Map<String,String> = [];
var cache:Map<String,String> = [];
var cacheFile = scriptFile.withoutExtension() + ".cache.json";
if (sys.FileSystem.exists(cacheFile)) {
var cacheJson:haxe.DynamicAccess<String> = haxe.Json.parse(sys.io.File.getContent(cacheFile));
for (key => value in cacheJson)
cache[key] = value;
}
var hscriptInstructionFile = scriptFile.withoutExtension() + ".hscript.json";
var commandList:Array<Expr> = [];
var labelsList:Array<Expr> = [];
var noSkipList:Array<Expr> = [];
@@ -174,6 +241,18 @@ class AsyncEmbeddedScript {
// 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));
if (Lambda.count(cache) > 0) {
classFields.push({
name: "hscriptInstructionFile",
access: [AOverride],
pos: Context.currentPos(),
kind: FFun({
args: [],
expr: macro return $v{hscriptInstructionFile}
})
});
}
scriptFile = Path.join([loadingDirectory, scriptFile]);
Context.registerModuleDependency(Context.getLocalModule(), scriptFile);
@@ -183,6 +262,14 @@ class AsyncEmbeddedScript {
Kiss.measure('Compiling kiss: $scriptFile', () -> {
#end
function process(nextExp) {
var cacheKey = Reader.toString(nextExp.def);
if (cache.exists(cacheKey)) {
hscriptInstructions[Std.string(commandList.length)] = cache[cacheKey];
trace(hscriptInstructions);
commandList.push(macro null);
return;
}
nextExp = Kiss.macroExpand(nextExp, k);
// Allow packing multiple commands into one exp with a (commands <...>) statement
@@ -208,6 +295,7 @@ class AsyncEmbeddedScript {
var c = macro function(self, cc) {
$expr;
};
cache[cacheKey] = expr.toString();
commandList.push(c.expr.withMacroPosOf(nextExp));
}
@@ -242,6 +330,9 @@ class AsyncEmbeddedScript {
})
});
sys.io.File.saveContent(cacheFile, haxe.Json.stringify(cache));
sys.io.File.saveContent(hscriptInstructionFile, haxe.Json.stringify(hscriptInstructions));
return classFields;
}
#end

View File

@@ -1,3 +1,4 @@
// TODO make a better position reification scheme here
(defReaderMacro "goop" [stream] `(Assert.isTrue true))
(defReaderMacro "gloop" [stream] `(Assert.isFalse false))
(prop Assert utest.Assert)

View File

@@ -0,0 +1,2 @@
goop
gloop

View File

@@ -3,6 +3,7 @@ package test.cases;
import utest.Test;
import utest.Assert;
import kiss.EmbeddedScript;
import kiss.AsyncEmbeddedScript;
import kiss.Prelude;
class DSLTestCase extends Test {
@@ -13,7 +14,29 @@ class DSLTestCase extends Test {
function testFork() {
new DSLScript().fork([(self) -> Assert.equals(5, 5), (self) -> Assert.equals(7, 7)]);
}
function testAsync() {
var script = new AsyncDSLScript();
script.run();
Assert.isFalse(script.ranHscriptInstruction);
}
function testAsyncFromCache() {
var script = new AsyncDSLScriptThatWillCache();
script.run();
var script2 = new AsyncDSLScriptThatWillCache2();
Assert.isTrue(script.ranHscriptInstruction || script2.ranHscriptInstruction);
}
}
@:build(kiss.EmbeddedScript.build("DSL.kiss", "DSLScript.dsl"))
class DSLScript extends EmbeddedScript {}
@:build(kiss.AsyncEmbeddedScript.build("", "DSL.kiss", "DSLScript.dsl"))
class AsyncDSLScript extends AsyncEmbeddedScript {}
@:build(kiss.AsyncEmbeddedScript.build("", "DSL.kiss", "DSLScriptThatWillCache.dsl"))
class AsyncDSLScriptThatWillCache extends AsyncEmbeddedScript {}
@:build(kiss.AsyncEmbeddedScript.build("", "DSL.kiss", "DSLScriptThatWillCache.dsl"))
class AsyncDSLScriptThatWillCache2 extends AsyncEmbeddedScript {}

View File

@@ -15,6 +15,10 @@ elif [ "$KISS_TARGET" = nodejs ]; then
lix install haxelib:hxnodejs
fi
if [ -e DSLScript.cache.json ]; then
rm DSLScript*.json
fi
if [ ! -z "$2" ]; then
haxe -D cases=$2 build-scripts/common-args.hxml build-scripts/common-test-args.hxml build-scripts/$KISS_TARGET/test.hxml
else