Files
LNXSDK/lib/hscript/hscript/JsInterp.hx

448 lines
13 KiB
Haxe

package hscript;
class JsInterp extends Interp {
/**
Variables declared in `ctx` are directly accessed without going through the `variables` map, which is faster
**/
public var ctx : {};
/**
If properties are defined, all calls to get/set are only done for fields accesses which are listed here.
**/
public var properties : Map<String,Bool>;
var localNames : Map<String,String>;
var hasBreakContinue : Int = 0;
var hasReturn : Bool;
override function execute( expr : Expr ) : Dynamic {
depth = 0;
localNames = new Map();
var str = '(($$i) => ${exprValue(expr)})';
var f : Dynamic -> Dynamic = js.Lib.eval(str);
return f(this);
}
public static function defineArrayExtensions() {
var arr : Dynamic = Array;
arr.prototype.copy = function() { var v : Array<Dynamic> = js.Lib.nativeThis; return [for( v in v ) v]; };
arr.prototype.contains = function(i) return js.Lib.nativeThis.indexOf(i) >= 0;
arr.prototype.remove = function(x) return @:privateAccess HxOverrides.remove(js.Lib.nativeThis, x);
arr.prototype.resize = function(len) js.Lib.nativeThis.length = len;
}
function escapeString(s:String) {
return s.split("\\").join("\\\\").split("\r").join("\\r").split("\n").join("\\n").split('"').join('\\"');
}
function handleRBC( e : Expr ) {
function mkRBC(code:Int,?value) {
var fields = [{ name : "$", e : Tools.mk(EConst(CInt(code)),e) }];
if( value != null ) fields.push({name:"$val",e:value});
return Tools.mk(EThrow(Tools.mk(EObject(fields),e)),e);
}
switch( Tools.expr(e) ) {
case EReturn(v):
hasReturn = true;
return mkRBC(1,v);
case EBreak if( hasBreakContinue >= 0 ):
hasBreakContinue |= 1;
return mkRBC(2);
case EContinue if( hasBreakContinue >= 0 ):
hasBreakContinue |= 2;
return mkRBC(3);
case EWhile(_), EFor(_), EDoWhile(_):
var prev = hasBreakContinue;
hasBreakContinue = -1;
e = Tools.map(e, handleRBC);
hasBreakContinue = prev;
return e;
case EFunction(_):
return e;
default:
return Tools.map(e, handleRBC);
}
}
function exprValue( expr : Expr ) {
switch( Tools.expr(expr) ) {
case EBlock([]):
return "null";
case EIf(cond,e1,e2):
return exprJS(Tools.mk(ETernary(cond,e1,e2),expr));
case EBlock(el):
var el = [for( e in el ) handleRBC(e)];
var last = el[el.length-1];
switch( Tools.expr(last) ) {
case EFunction(_,_,name,_) if( name != null ): // don't return latest named function
default:
el[el.length - 1] = Tools.mk(EReturn(last),last);
}
var ebl = Tools.mk(EBlock(el),expr);
return '(() => ${exprJS(ebl)})()';
case ETry(e,v,t,ecatch):
e = handleRBC(e);
ecatch = handleRBC(ecatch);
var expr = Tools.mk(ETry(Tools.mk(EReturn(e),e),v,t,Tools.mk(EReturn(ecatch),ecatch)),expr);
return '(() => { ${exprJS(expr)} })()';
case EVar(_,_,e):
return e == null ? "null" : '(${exprValue(e)},null)';
case EWhile(_), EFor(_), EDoWhile(_), EThrow(_):
expr = handleRBC(expr);
return '(() => {${exprJS(expr)}})()';
case EMeta(_,_,e), ECheckType(e,_):
return exprValue(e);
case EFunction(_,_,name,_) if( name != null ):
return '(() => {${exprJS(expr)}})()';
default:
return exprJS(expr);
}
}
function exprBlock( expr : Expr ) {
switch( Tools.expr(expr) ) {
case EBlock(_):
return exprJS(expr);
default:
return '{${exprJS(expr)};}';
}
}
function addPos( estr : String ) {
#if hscriptPos
var expr = curExpr;
var p = '{pmin:,pmax:,origin:"",line:}';
return '($$i._p(${expr.pmin},${expr.pmax},"${expr.origin}",${expr.line}),$estr)';
#else
return estr;
#end
}
function isContext(v:String) {
return ctx != null && Reflect.hasField(ctx,v);
}
function isProperty(f:String) {
return properties == null || properties.get(f);
}
function exprCond( e : Expr ) {
return switch( Tools.expr(e) ) {
case EBinop("=="|"!="|">="|">"|"<="|"<"|"&&"|"||",_): exprValue(e);
default: '(${exprOp(e)} == true)';
}
}
function exprOp( e : Expr ) {
return switch( Tools.expr(e) ) {
case EBinop(_), EUnop(_): '(${exprValue(e)})';
default: exprValue(e);
}
}
function declLocal( n : String ) {
if( !localNames.exists(n) ) {
localNames.set(n, n);
return n;
}
var c = 2;
while( localNames.exists(n+c) ) c++;
localNames.set(n, n+c);
return n+c;
}
function exprBreakContinue( e, needBlock=true ) {
var prevBC = hasBreakContinue;
hasBreakContinue = 0;
var estr = needBlock ? exprBlock(e) : exprJS(e);
if( hasBreakContinue != 0 ) {
var checks = [];
if( hasBreakContinue & 1 != 0 )
checks.push("if( $e.$ == 2 ) break;");
if( hasBreakContinue & 2 != 0 )
checks.push("if( $e.$ == 3 ) continue;");
if( !needBlock && !Tools.expr(e).match(EBlock(_)) )
estr = '{$estr;}';
estr = 'try $estr catch( $$e ) { ${checks.join('')} throw $$e; }';
}
hasBreakContinue = prevBC;
return estr;
}
function exprJS( expr : Expr ) : String {
#if hscriptPos
curExpr = expr;
var expr = expr.e;
#end
switch( expr ) {
case EConst(c):
return switch c {
case CInt(v): Std.string(v);
case CFloat(f): Std.string(f);
case CString(s): '"'+escapeString(s)+'"';
}
case EIdent(v):
var v2 = localNames.get(v);
if( v2 != null )
return v2;
if( isContext(v) )
return '$$i.ctx.$v';
switch( v ) {
case "null", "true", "false": return v;
default:
}
return '$$i.resolve("$v")';
case EVar(n, t, e):
n = declLocal(n);
return e == null ? 'let $n' : 'let $n = ${exprValue(e)}';
case EParent(e):
return '(${exprValue(e)})';
case EBlock(el):
var old = localNames.copy();
// pre define name functions
for( e in el )
switch( Tools.expr(e) ) {
case EFunction(_,_,name,_): declLocal(name);
default:
}
var buf = new StringBuf();
buf.add('{');
for( e in el ) {
buf.add(exprJS(e));
buf.add(";");
}
buf.add('}');
localNames = old;
return buf.toString();
case EField(e,f) if( !isProperty(f) ):
return exprValue(e)+"."+f;
case EField(e, f):
return '$$i.get(${exprValue(e)},"$f")';
case EBinop(op, e1, e2):
switch( op ) {
case "+","-","*","/","%","&","|","^",">>","<<",">>>","==","!=",">=","<=",">","<":
return '${exprOp(e1)} $op ${exprOp(e2)}';
case "||","&&":
return '(${exprCond(e1)} $op ${exprCond(e2)})';
case "=":
switch( Tools.expr(e1) ) {
case EIdent(id) if( localNames.exists(id) ):
return localNames.get(id)+" = "+exprValue(e2);
case EIdent(id) if( isContext(id) ):
return '$$i.ctx.$id = ${exprValue(e2)}';
case EIdent(id):
return '$$i.setVar("$id",${exprValue(e2)})';
case EField(e,f):
return addPos('$$i.set(${exprValue(e)},"$f")');
case EArray(e, index):
return '$$i.setArray(${exprValue(e)},${exprValue(index)},${exprValue(e2)})';
default:
error(EInvalidOp("="));
}
case "+=","-=","*=","/=","%=","|=","&=","^=","<<=",">>=",">>>=":
var aop = op.substr(0, op.length - 1);
switch( Tools.expr(e1) ) {
case EIdent(id) if( localNames.exists(id) ):
return localNames.get(id)+" "+op+" "+exprValue(e2);
case EIdent(id) if( isContext(id) ):
return '$$i.ctx.$id $op ${exprValue(e2)}';
case EIdent(id):
return '$$i.setVar("$id",$$i.resolve("$id") $aop (${exprValue(e2)}))';
case EField(e, f):
return '(($$o,$$v) => $$i.set($$o,"$f",$$i.get($$o,"$f") $aop $$v)(${exprValue(e)},${exprValue(e2)})';
case EArray(e, index):
return '(($$a,$$idx,$$v) => $$i.setArray($$a,$$idx,$$i.getArray($$a,$$idx) $aop $$v))(${exprValue(e)},${exprValue(index)},${exprValue(e2)})';
default:
error(EInvalidOp(op));
}
case "...":
return '$$i.intIterator(${exprValue(e1)},${exprValue(e2)})';
case "is":
return '$$i.isOfType(${exprValue(e1)},${exprValue(e2)})';
default:
error(EInvalidOp(op));
}
case EUnop(op, prefix, e):
switch( op ) {
case "!":
return '${exprValue(e)} != true';
case "-","~":
return op+exprOp(e);
case "++", "--":
switch( Tools.expr(e) ) {
case EIdent(id) if( localNames.exists(id) ):
id = localNames.get(id);
return prefix ? op + id : id + op;
case EIdent(id) if( isContext(id) ):
return prefix ? op + "$i.ctx."+id : "$i.ctx."+id + op;
case _ if( prefix ):
var one = Tools.mk(EConst(CInt(1)),e);
return exprJS(Tools.mk(EBinop(op.charAt(0)+"=",e,one),e));
case EIdent(id):
var op = op.charAt(1);
return '(($$v) => ($$i.setVar("$id",$$v $op 1),$$v))($$i.resolve("$id"))';
case EArray(e, index):
var op = op.charAt(0);
var v = declLocal("$v");
var str = '(($$a,$$idx) => { let $v = $$i.getArray($$a,$$idx); $$i.setArray($$a,$$idx,$v $op 1); return $v; })(${exprValue(e)},${exprValue(index)})';
localNames.remove(v);
return str;
case EField(e, f):
var v = declLocal("$v");
var str = '(($$o) => { let $v = $$i.get($$o,"$f"); $$i.set($$o,"$f",$$v $op 1); return $v; })(${exprValue(e)})';
localNames.remove(v);
return str;
default:
error(EInvalidOp(op));
}
default:
error(EInvalidOp(op));
}
case ECall(e, params):
var args = [for( p in params ) exprValue(p)];
switch( Tools.expr(e) ) {
case EField(eobj,f):
var isCtx = false;
var obj = eobj;
while( true ) {
switch( Tools.expr(obj) ) {
case EField(o,_): obj = o;
case EIdent(i) if( isContext(i) ): isCtx = true; break;
default: break;
}
}
if( isCtx )
return '${exprValue(e)}(${args.join(',')})';
return addPos('$$i.fcall2(${exprValue(eobj)},"$f",[${args.join(',')}])');
case EIdent(id) if( localNames.exists(id) ):
id = localNames.get(id);
return '$id(${args.join(',')})';
case EIdent(id) if( isContext(id) ):
return '$$i.ctx.$id(${args.join(',')})';
default:
return '$$i.call(null,${exprValue(e)},[${args.join(',')}])';
}
case EIf(cond,e1,e2):
return 'if( ${exprCond(cond)} ) ${exprJS(e1)}'+(e2 == null ? "" : 'else ${exprJS(e2)}');
case ETernary(cond, e1, e2):
return '(${exprCond(cond)} ? ${exprValue(e1)} : ${e2 == null ? 'undefined' : exprValue(e2)})';
case EWhile(cond, e):
return 'while( ${exprValue(cond)} ) ${exprBreakContinue(e)}';
case EDoWhile(cond, e):
return 'do ${exprBreakContinue(e)} while( ${exprCond(cond)} )';
case EFor(v, it, e):
v = declLocal(v);
var block = exprBreakContinue(e,false);
localNames.remove(v);
var iter = '$$i.makeIterator(${exprValue(it)})';
var it = declLocal("$it");
var str = '{ let $it = ${addPos(iter)}; while( $it.hasNext() ) { let $v = $it.next(); $block; } }';
localNames.remove(it);
return str;
case EBreak:
return 'break';
case EContinue:
return 'continue';
case EReturn(null):
return 'return';
case EReturn(e):
return 'return '+exprValue(e);
case EArray(e, index):
return '$$i.getArray(${exprValue(e)},${exprValue(index)})';
case EArrayDecl(arr):
if( arr.length > 0 && Tools.expr(arr[0]).match(EBinop("=>", _)) ) {
var keys = [], values = [];
for( e in arr ) {
switch(Tools.expr(e)) {
case EBinop("=>", eKey, eValue):
keys.push(exprValue(eKey));
values.push(exprValue(eValue));
default:
#if hscriptPos
curExpr = e;
#end
error(ECustom("Invalid map key=>value expression"));
}
}
return '$$i.makeMap([${keys.join(',')}],[${values.join(',')}])';
}
return '['+[for( e in arr ) exprValue(e)].join(',')+']';
case ENew(cl, params):
var args = [for( e in params ) exprValue(e)];
return '$$i.cnew("$cl",[${args.join(',')}])';
case EThrow(e):
return "throw "+exprValue(e);
case ETry(e, v, t, ecatch):
v = declLocal(v);
var ec = exprBlock(ecatch);
localNames.remove(v);
return 'try ${exprBlock(e)} catch( $v ) $ec';
case EObject(fl):
var fields = [for( f in fl ) f.name+":"+exprValue(f.e)];
return '{${fields.join(',')}}'; // do not use 'set' here
case EMeta(_, _, e), ECheckType(e,_):
return exprJS(e);
case EFunction(args, e, name, ret):
var prev = localNames.copy();
if( name != null && !localNames.exists(name) )
declLocal(name);
for( a in args )
localNames.set(a.name, a.name);
var prevReturn = hasReturn;
hasReturn = false;
var bl = exprBlock(e);
if( hasReturn )
bl = '{ try { $bl } catch( $$e ) { if( $$e.$$ == 1 ) return $$e.$$val; throw $$e; }}';
hasReturn = prevReturn;
localNames = prev;
var fstr = 'function(${[for( a in args ) a.name].join(",")}) $bl';
if( name != null )
fstr = 'let $name = $$i.setVar("$name",$fstr)';
return fstr;
case ESwitch(e, cases, defaultExpr):
var checks = [for( c in cases ) 'if( ${[for( v in c.values ) '$$v == ${exprValue(handleRBC(v))}'].join(" || ")} ) return ${exprValue(handleRBC(c.expr))};'];
if( defaultExpr != null )
checks.push('return '+exprValue(handleRBC(defaultExpr)));
return '(($$v) => { ${[for( c in checks ) c+";"].join(" ")} })(${exprValue(e)})';
default:
throw "TODO";
}
}
function fcall2( o : Dynamic, f : String, args : Array<Dynamic> ) : Dynamic {
if( o == null ) {
error(EInvalidAccess(f));
return null;
}
return fcall(o,f,args);
}
function getArray( arr : Dynamic, index : Dynamic ) {
return isMap(arr) ? getMapValue(arr,index) : arr[index];
}
function setArray( arr : Dynamic, index : Dynamic, v : Dynamic ) {
if(isMap(arr) )
setMapValue(arr, index, v);
else
arr[index] = v;
return v;
}
function intIterator(v1,v2) {
return new IntIterator(v1,v2);
}
function isOfType(v1,v2) {
return Std.isOfType(v1,v2);
}
#if hscriptPos
function _p( pmin, pmax, origin, line ) {
curExpr = { e : null, pmin : pmin, pmax: pmax, origin:origin, line:line };
}
#end
}