Delete original AsyncEmbeddedScript
This commit is contained in:
@@ -1,408 +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 haxe.io.Path;
|
||||
import kiss.Helpers;
|
||||
using kiss.Helpers;
|
||||
using tink.MacroApi;
|
||||
|
||||
#end
|
||||
|
||||
import kiss.Kiss;
|
||||
using kiss.Kiss;
|
||||
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;
|
||||
var fields:Map<String,Bool> = [];
|
||||
public function new(obj:T) {
|
||||
this.obj = obj;
|
||||
|
||||
for (field in Type.getInstanceFields(Type.getClass(obj))) {
|
||||
fields[field] = true;
|
||||
}
|
||||
|
||||
super();
|
||||
}
|
||||
|
||||
override function resolve(id:String):Dynamic {
|
||||
var fieldVal = Reflect.field(obj, id);
|
||||
if (fieldVal != null)
|
||||
return fieldVal;
|
||||
else
|
||||
return super.resolve(id);
|
||||
}
|
||||
|
||||
// TODO every method of setting variables should try to set them on the object,
|
||||
// but there are a lot of them and I might have missed some.
|
||||
|
||||
override function setVar(name:String, v:Dynamic) {
|
||||
if (Reflect.field(obj, name) != null) {
|
||||
Reflect.setField(obj, name, v);
|
||||
} else {
|
||||
super.setVar(name, v);
|
||||
}
|
||||
}
|
||||
|
||||
public override function expr( e : hscript.Expr ) : Dynamic {
|
||||
var curExpr = e;
|
||||
#if hscriptPos
|
||||
var e = e.e;
|
||||
#end
|
||||
switch( e ) {
|
||||
// Handle fuzzyMaps correctly:
|
||||
case EArray(e, index):
|
||||
var arr:Dynamic = expr(e);
|
||||
var index:Dynamic = expr(index);
|
||||
if (isMap(arr)) {
|
||||
if (kiss.FuzzyMapTools.isFuzzy(arr))
|
||||
return getMapValue(arr, kiss.FuzzyMapTools.bestMatch(arr, index));
|
||||
return getMapValue(arr, index);
|
||||
}
|
||||
else {
|
||||
return arr[index];
|
||||
}
|
||||
case ECall(e,params):
|
||||
switch( hscript.Tools.expr(e) ) {
|
||||
case EIdent(name) if (fields.exists(name)):
|
||||
var args = new Array();
|
||||
for( p in params )
|
||||
args.push(expr(p));
|
||||
return call(obj,expr(e),args);
|
||||
default:
|
||||
}
|
||||
default:
|
||||
}
|
||||
return super.expr(curExpr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Utility class for making statically typed, debuggable, ASYNC-BASED embedded Kiss-based DSLs.
|
||||
Examples are in the hollywoo project.
|
||||
**/
|
||||
class AsyncEmbeddedScript {
|
||||
private var instructions:Array<AsyncCommand> = null;
|
||||
private var breakPoints:Map<Int, () -> Bool> = [];
|
||||
private var onBreak:AsyncCommand = null;
|
||||
private var lastInstructionPointer = -1;
|
||||
private var labels:Map<String,Int> = [];
|
||||
private var noSkipInstructions:Map<Int,Bool> = [];
|
||||
|
||||
private var parser = new Parser();
|
||||
private var interp:ObjectInterp<AsyncEmbeddedScript>;
|
||||
public var interpVariables(get, null):Map<String,Dynamic>;
|
||||
private function get_interpVariables() {
|
||||
return interp.variables;
|
||||
}
|
||||
|
||||
private var hscriptInstructions:Map<Int,String> = [];
|
||||
private function hscriptInstructionFile() return "";
|
||||
|
||||
public function setBreakHandler(handler:AsyncCommand) {
|
||||
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() {
|
||||
interp = new ObjectInterp(this);
|
||||
kiss.KissInterp.prepare(interp);
|
||||
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() {}
|
||||
|
||||
public function instructionCount() {
|
||||
if (instructions == null)
|
||||
resetInstructions();
|
||||
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;
|
||||
if (printCurrentInstruction)
|
||||
Prelude.print(hscriptInstructions[instructionPointer]);
|
||||
interp.execute(parser.parseString(hscriptInstructions[instructionPointer]));
|
||||
}
|
||||
|
||||
private function runInstruction(instructionPointer:Int, withBreakPoints = true) {
|
||||
lastInstructionPointer = instructionPointer;
|
||||
if (instructions == null)
|
||||
resetInstructions();
|
||||
if (withBreakPoints && breakPoints.exists(instructionPointer) && breakPoints[instructionPointer]()) {
|
||||
if (onBreak != null) {
|
||||
onBreak(this, () -> runInstruction(instructionPointer, false));
|
||||
}
|
||||
}
|
||||
var continuation = if (instructionPointer < instructions.length - 1) {
|
||||
() -> {
|
||||
// runInstruction may be called externally to skip through the script.
|
||||
// When this happens, make sure other scheduled continuations are canceled
|
||||
// by verifying that lastInstructionPointer hasn't changed
|
||||
if (lastInstructionPointer == instructionPointer) {
|
||||
runInstruction(instructionPointer + 1);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
() -> {};
|
||||
}
|
||||
if (hscriptInstructions.exists(instructionPointer)) {
|
||||
runHscriptInstruction(instructionPointer, continuation);
|
||||
} else {
|
||||
instructions[instructionPointer](this, continuation);
|
||||
}
|
||||
}
|
||||
|
||||
public function run(withBreakPoints = true) {
|
||||
runInstruction(0, withBreakPoints);
|
||||
}
|
||||
|
||||
private function skipToInstruction(ip:Int) {
|
||||
var lastCC = ()->runInstruction(ip);
|
||||
// chain together the unskippable instructions prior to running the requested ip
|
||||
var noSkipList = [];
|
||||
for (cIdx in lastInstructionPointer+1... ip) {
|
||||
if (noSkipInstructions.exists(cIdx)) {
|
||||
noSkipList.push(cIdx);
|
||||
}
|
||||
}
|
||||
if (noSkipList.length > 0) {
|
||||
var cc = null;
|
||||
cc = ()->{
|
||||
if (noSkipList.length == 0) {
|
||||
lastCC();
|
||||
} else {
|
||||
var inst = noSkipList.shift();
|
||||
lastInstructionPointer = inst;
|
||||
instructions[inst](this, cc);
|
||||
}
|
||||
};
|
||||
cc();
|
||||
} else {
|
||||
lastCC();
|
||||
}
|
||||
|
||||
// TODO remember whether breakpoints were requested
|
||||
}
|
||||
|
||||
public function skipToNextLabel() {
|
||||
var labelPointers = [for (ip in labels) ip];
|
||||
labelPointers.sort(Reflect.compare);
|
||||
for (ip in labelPointers) {
|
||||
if (ip > lastInstructionPointer) {
|
||||
skipToInstruction(ip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function skipToLabel(name:String) {
|
||||
var ip = labels[name];
|
||||
if (lastInstructionPointer > ip) {
|
||||
throw "Rewinding AsyncEmbeddedScript is not implemented";
|
||||
}
|
||||
skipToInstruction(ip);
|
||||
}
|
||||
|
||||
public function labelRunners():Map<String,Void->Void> {
|
||||
return [for (label => ip in labels) label => () -> skipToInstruction(ip)];
|
||||
}
|
||||
|
||||
public var printCurrentInstruction = true;
|
||||
|
||||
#if macro
|
||||
public static function build(dslHaxelib:String, dslFile:String, scriptFile:String):Array<Field> {
|
||||
// trace('AsyncEmbeddedScript.build $dslHaxelib $dslFile $scriptFile');
|
||||
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 hscriptInstructions:Map<String,String> = [];
|
||||
var cache:Map<String,String> = [];
|
||||
#if kissCache
|
||||
var cacheFile = scriptFile.withoutExtension().withoutDirectory() + ".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;
|
||||
}
|
||||
#end
|
||||
|
||||
var hscriptInstructionFile = scriptFile.withoutExtension().withoutDirectory() + ".hscript.json";
|
||||
|
||||
var commandList:Array<Expr> = [];
|
||||
var labelsList:Array<Expr> = [];
|
||||
var noSkipList:Array<Expr> = [];
|
||||
|
||||
var labelNum = 0;
|
||||
k.macros["label"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
k.stateChanged = true;
|
||||
wholeExp.checkNumArgs(1, 1, '(label <label>)');
|
||||
var label = Prelude.symbolNameValue(args[0]);
|
||||
label = '${++labelNum}. '.lpad("0", 5) + label;
|
||||
labelsList.push(macro labels[$v{label}] = $v{commandList.length});
|
||||
|
||||
wholeExp.expBuilder().callSymbol("cc", []);
|
||||
};
|
||||
|
||||
k.macros["noSkip"] = (wholeExp:ReaderExp, args:Array<ReaderExp>, k:KissState) -> {
|
||||
k.stateChanged = true;
|
||||
wholeExp.checkNumArgs(1, null, '(noSkip <body...>)');
|
||||
noSkipList.push(macro noSkipInstructions[$v{commandList.length}] = true);
|
||||
|
||||
wholeExp.expBuilder().begin(args);
|
||||
}
|
||||
|
||||
if (dslHaxelib.length > 0) {
|
||||
dslFile = Path.join([Helpers.libPath(dslHaxelib), dslFile]);
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
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);
|
||||
k.fieldList = [];
|
||||
Kiss._try(() -> {
|
||||
#if profileKiss
|
||||
Kiss.measure('Compiling kiss: $scriptFile', () -> {
|
||||
#end
|
||||
function process(nextExp) {
|
||||
#if kissCache
|
||||
var cacheKey = Reader.toString(nextExp.def);
|
||||
if (cache.exists(cacheKey)) {
|
||||
hscriptInstructions[Std.string(commandList.length)] = cache[cacheKey];
|
||||
commandList.push(macro null);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
nextExp = Kiss.macroExpand(nextExp, k);
|
||||
var stateChanged = k.stateChanged;
|
||||
|
||||
// Allow packing multiple commands into one exp with a (commands <...>) statement
|
||||
switch (nextExp.def) {
|
||||
case CallExp({pos: _, def: Symbol("commands")},
|
||||
commands):
|
||||
for (exp in commands) {
|
||||
process(exp);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
}
|
||||
|
||||
var exprString = Reader.toString(nextExp.def);
|
||||
var fieldCount = k.fieldList.length;
|
||||
var expr = Kiss.readerExpToHaxeExpr(nextExp, k);
|
||||
if (expr == null || Kiss.isEmpty(expr))
|
||||
return;
|
||||
expr = macro { if (printCurrentInstruction) Prelude.print($v{exprString}); $expr; };
|
||||
expr = expr.expr.withMacroPosOf(nextExp);
|
||||
if (expr != null) {
|
||||
var c = macro function(self, cc) {
|
||||
$expr;
|
||||
};
|
||||
// If the expression didn't change the KissState when macroExpanding, it can be cached
|
||||
#if kissCache
|
||||
if (!stateChanged) {
|
||||
var expr = Kiss.readerExpToHaxeExpr(nextExp, k.forHScript());
|
||||
cache[cacheKey] = expr.toString();
|
||||
}
|
||||
#end
|
||||
|
||||
commandList.push(c.expr.withMacroPosOf(nextExp));
|
||||
}
|
||||
|
||||
// This return is essential for type unification of concat() and push() above... ugh.
|
||||
return;
|
||||
}
|
||||
Reader.readAndProcess(Stream.fromFile(scriptFile), k, process);
|
||||
null;
|
||||
#if profileKiss
|
||||
});
|
||||
#end
|
||||
});
|
||||
|
||||
classFields = classFields.concat(k.fieldList);
|
||||
|
||||
classFields.push({
|
||||
pos: PositionTools.make({
|
||||
min: 0,
|
||||
max: File.getContent(scriptFile).length,
|
||||
file: scriptFile
|
||||
}),
|
||||
name: "resetInstructions",
|
||||
access: [APrivate, AOverride],
|
||||
kind: FFun({
|
||||
ret: null,
|
||||
args: [],
|
||||
expr: macro {
|
||||
this.instructions = [$a{commandList}];
|
||||
$b{labelsList};
|
||||
$b{noSkipList};
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
#if kissCache
|
||||
sys.io.File.saveContent(cacheFile, haxe.Json.stringify(cache));
|
||||
sys.io.File.saveContent(hscriptInstructionFile, haxe.Json.stringify(hscriptInstructions));
|
||||
#end
|
||||
|
||||
return classFields;
|
||||
}
|
||||
#end
|
||||
}
|
Reference in New Issue
Block a user