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 haxe.macro.PositionTools;
import sys.io.File; import sys.io.File;
import haxe.io.Path; import haxe.io.Path;
using haxe.io.Path;
import kiss.Helpers; import kiss.Helpers;
using kiss.Helpers; using kiss.Helpers;
using tink.MacroApi;
#end #end
import kiss.Kiss; import kiss.Kiss;
@@ -15,10 +18,32 @@ import kiss.ReaderExp;
import kiss.Prelude; import kiss.Prelude;
import kiss.cloner.Cloner; import kiss.cloner.Cloner;
using StringTools; using StringTools;
import hscript.Parser;
import hscript.Interp;
typedef Continuation = () -> Void; typedef Continuation = () -> Void;
typedef AsyncCommand = (AsyncEmbeddedScript, 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. Utility class for making statically typed, debuggable, ASYNC-BASED embedded Kiss-based DSLs.
Examples are in the hollywoo project. Examples are in the hollywoo project.
@@ -30,6 +55,12 @@ class AsyncEmbeddedScript {
private var lastInstructionPointer = -1; private var lastInstructionPointer = -1;
private var labels:Map<String,Int> = []; private var labels:Map<String,Int> = [];
private var noSkipInstructions:Map<Int,Bool> = []; 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) { public function setBreakHandler(handler:AsyncCommand) {
onBreak = handler; onBreak = handler;
@@ -46,7 +77,17 @@ class AsyncEmbeddedScript {
breakPoints.remove(instruction); 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() {} private function resetInstructions() {}
@@ -56,6 +97,17 @@ class AsyncEmbeddedScript {
return instructions.length; 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) { private function runInstruction(instructionPointer:Int, withBreakPoints = true) {
lastInstructionPointer = instructionPointer; lastInstructionPointer = instructionPointer;
if (instructions == null) if (instructions == null)
@@ -77,7 +129,11 @@ class AsyncEmbeddedScript {
} else { } else {
() -> {}; () -> {};
} }
instructions[instructionPointer](this, continuation); if (hscriptInstructions.exists(instructionPointer)) {
runHscriptInstruction(instructionPointer, continuation);
} else {
instructions[instructionPointer](this, continuation);
}
} }
public function run(withBreakPoints = true) { public function run(withBreakPoints = true) {
@@ -145,6 +201,17 @@ class AsyncEmbeddedScript {
var loadingDirectory = Path.directory(classPath); var loadingDirectory = Path.directory(classPath);
var classFields = []; // Kiss.build() will already include Context.getBuildFields() 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 commandList:Array<Expr> = [];
var labelsList:Array<Expr> = []; var labelsList:Array<Expr> = [];
var noSkipList: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 // 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)); 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]); scriptFile = Path.join([loadingDirectory, scriptFile]);
Context.registerModuleDependency(Context.getLocalModule(), scriptFile); Context.registerModuleDependency(Context.getLocalModule(), scriptFile);
@@ -183,6 +262,14 @@ class AsyncEmbeddedScript {
Kiss.measure('Compiling kiss: $scriptFile', () -> { Kiss.measure('Compiling kiss: $scriptFile', () -> {
#end #end
function process(nextExp) { 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); nextExp = Kiss.macroExpand(nextExp, k);
// Allow packing multiple commands into one exp with a (commands <...>) statement // Allow packing multiple commands into one exp with a (commands <...>) statement
@@ -208,6 +295,7 @@ class AsyncEmbeddedScript {
var c = macro function(self, cc) { var c = macro function(self, cc) {
$expr; $expr;
}; };
cache[cacheKey] = expr.toString();
commandList.push(c.expr.withMacroPosOf(nextExp)); 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; return classFields;
} }
#end #end

View File

@@ -1,3 +1,4 @@
// TODO make a better position reification scheme here // TODO make a better position reification scheme here
(defReaderMacro "goop" [stream] `(Assert.isTrue true)) (defReaderMacro "goop" [stream] `(Assert.isTrue true))
(defReaderMacro "gloop" [stream] `(Assert.isFalse false)) (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.Test;
import utest.Assert; import utest.Assert;
import kiss.EmbeddedScript; import kiss.EmbeddedScript;
import kiss.AsyncEmbeddedScript;
import kiss.Prelude; import kiss.Prelude;
class DSLTestCase extends Test { class DSLTestCase extends Test {
@@ -13,7 +14,29 @@ class DSLTestCase extends Test {
function testFork() { function testFork() {
new DSLScript().fork([(self) -> Assert.equals(5, 5), (self) -> Assert.equals(7, 7)]); 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")) @:build(kiss.EmbeddedScript.build("DSL.kiss", "DSLScript.dsl"))
class DSLScript extends EmbeddedScript {} 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 lix install haxelib:hxnodejs
fi fi
if [ -e DSLScript.cache.json ]; then
rm DSLScript*.json
fi
if [ ! -z "$2" ]; then 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 haxe -D cases=$2 build-scripts/common-args.hxml build-scripts/common-test-args.hxml build-scripts/$KISS_TARGET/test.hxml
else else