Experimental AsyncEmbeddedScript caching
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DSLScript*.json
|
@@ -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.
|
||||||
@@ -31,6 +56,12 @@ class AsyncEmbeddedScript {
|
|||||||
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,8 +129,12 @@ class AsyncEmbeddedScript {
|
|||||||
} else {
|
} else {
|
||||||
() -> {};
|
() -> {};
|
||||||
}
|
}
|
||||||
|
if (hscriptInstructions.exists(instructionPointer)) {
|
||||||
|
runHscriptInstruction(instructionPointer, continuation);
|
||||||
|
} else {
|
||||||
instructions[instructionPointer](this, continuation);
|
instructions[instructionPointer](this, continuation);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function run(withBreakPoints = true) {
|
public function run(withBreakPoints = true) {
|
||||||
runInstruction(0, withBreakPoints);
|
runInstruction(0, withBreakPoints);
|
||||||
@@ -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
|
||||||
|
@@ -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)
|
2
src/test/cases/DSLScriptThatWillCache.dsl
Normal file
2
src/test/cases/DSLScriptThatWillCache.dsl
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
goop
|
||||||
|
gloop
|
@@ -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 {}
|
4
test.sh
4
test.sh
@@ -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
|
||||||
|
Reference in New Issue
Block a user