diff --git a/Test.hx b/Test.hx index 61d29d0..a9b2ce2 100644 --- a/Test.hx +++ b/Test.hx @@ -13,6 +13,32 @@ class Test { interp.variables.set("Math",Math); // share the Math class interp.variables.set("angles",[0,1,2,3]); // set the angles list trace( interp.execute(program) ); + + var script = " + var sum = 0; + function foo(a) { + return Math.cos(a); + } + function bar(x) { + sum = sum + foo(x); + } + for( x in angles ) + bar(x); + sum; + "; + var program = p.parseString(script); + trace( interp.execute(program) ); + + var script = " + var angles = [0,1,2,3]; + var i = 0; + var sum = 0; + while( i < angles.length ) + sum = sum + Math.cos(angles[i++]); + sum; + "; + var program = p.parseString(script); + trace( interp.execute(program) ); } } \ No newline at end of file diff --git a/haxelib.xml b/haxelib.xml index ffdfd9d..0c4d5ae 100644 --- a/haxelib.xml +++ b/haxelib.xml @@ -1,5 +1,5 @@ haXe Script is a scripting engine for a subset of the haXe language - Initial Release + Added functions, return, arrays \ No newline at end of file diff --git a/hscript.hxml b/hscript.hxml index e1e6c22..8a80080 100644 --- a/hscript.hxml +++ b/hscript.hxml @@ -2,4 +2,5 @@ -swf hscript9.swf -swf-version 9 -main Test +-debug diff --git a/hscript.hxp b/hscript.hxp index b38ca34..fddc68f 100644 --- a/hscript.hxp +++ b/hscript.hxp @@ -1,5 +1,5 @@  - + diff --git a/hscript/Expr.hx b/hscript/Expr.hx index d382cf5..b2e7b3c 100644 --- a/hscript/Expr.hx +++ b/hscript/Expr.hx @@ -45,6 +45,10 @@ enum Expr { EFor( v : String, it : Expr, e : Expr ); EBreak; EContinue; + EFunction( params : Array, e : Expr, ?name : String ); + EReturn( ?e : Expr ); + EArray( e : Expr, index : Expr ); + EArrayDecl( e : Array ); } enum Error { diff --git a/hscript/Interp.hx b/hscript/Interp.hx index 8a26aff..9ae9482 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -28,15 +28,18 @@ import hscript.Expr; private enum Stop { SBreak; SContinue; + SReturn( v : Dynamic ); } class Interp { public var variables : Hash; + var locals : Hash<{ r : Dynamic }>; var binops : Hash Expr -> Dynamic>; - var declared : Array<{ n : String, old : Dynamic, exists : Bool }>; + var declared : Array<{ n : String, old : { r : Dynamic } }>; public function new() { + locals = new Hash(); variables = new Hash(); variables.set("null",null); variables.set("true",true); @@ -72,8 +75,16 @@ class Interp { function assign( e1 : Expr, e2 : Expr ) { var v = expr(e2); switch( e1 ) { - case EIdent(id): variables.set(id,v); - case EField(e,f): v = set(expr(e),f,v); + case EIdent(id): + var l = locals.get(id); + if( l == null ) + variables.set(id,v) + else + l.r = v; + case EField(e,f): + v = set(expr(e),f,v); + case EArray(e,index): + expr(e)[expr(index)] = v; default: throw Error.EInvalidOp("="); } return v; @@ -82,12 +93,13 @@ class Interp { function increment( e : Expr, prefix : Bool, delta : Int ) : Dynamic { switch(e) { case EIdent(id): - var v : Dynamic = variables.get(id); + var l = locals.get(id); + var v : Dynamic = (l == null) ? variables.get(id) : l.r; if( prefix ) { v += delta; - variables.set(id,v); + if( l == null ) variables.set(id,v) else l.r = v; } else - variables.set(id,v + delta); + if( l == null ) variables.set(id,v + delta) else l.r = v + delta; return v; case EField(e,f): var obj = expr(e); @@ -98,13 +110,44 @@ class Interp { } else set(obj,f,v + delta); return v; + case EArray(e,index): + var arr = expr(e); + var index = expr(index); + var v = arr[index]; + if( prefix ) { + v += delta; + arr[index] = v; + } else + arr[index] = v + delta; + return v; default: throw Error.EInvalidOp((delta > 0)?"++":"--"); } } - public function execute( program : Array ) { - return block(program); + public function execute( expr : Expr ) { + locals = new Hash(); + return exprReturn(expr); + } + + function exprReturn(e) : Dynamic { + try { + return expr(e); + } catch( e : Stop ) { + switch( e ) { + case SBreak: throw "Invalid break"; + case SContinue: throw "Invalid continue"; + case SReturn(v): return v; + } + } + return null; + } + + function duplicate( h : Hash ) { + var h2 = new Hash(); + for( k in h.keys() ) + h2.set(k,h.get(k)); + return h2; } function block( exprs : Array ) { @@ -114,10 +157,7 @@ class Interp { for( e in exprs ) v = expr(e); for( d in declared ) - if( d.exists ) - variables.set(d.n,d.old); - else - variables.remove(d.n); + locals.set(d.n,d.old); declared = old; return v; } @@ -131,13 +171,16 @@ class Interp { case CString(s): return s; } case EIdent(id): + var l = locals.get(id); + if( l != null ) + return l.r; var v = variables.get(id); if( v == null && !variables.exists(v) ) throw Error.EUnknownVariable(id); return v; case EVar(n,e): - declared.unshift({ n : n, old : variables.get(n), exists : variables.exists(n) }); - variables.set(n,(e == null)?null:expr(e)); + declared.unshift({ n : n, old : locals.get(n) }); + locals.set(n,{ r : (e == null)?null:expr(e) }); return null; case EParent(e): return expr(e); @@ -185,6 +228,32 @@ class Interp { throw SBreak; case EContinue: throw SContinue; + case EReturn(e): + throw SReturn((e == null)?null:expr(e)); + case EFunction(params,fexpr,name): + var capturedLocals = duplicate(locals); + var me = this; + var f = function(args:Array) { + if( args.length != params.length ) throw "Invalid number of parameters"; + var old = me.locals; + me.locals = me.duplicate(capturedLocals); + for( i in 0...params.length ) + me.locals.set(params[i],{ r : args[i] }); + var r = me.exprReturn(fexpr); + me.locals = old; + return r; + }; + var f = Reflect.makeVarArgs(f); + if( name != null ) + variables.set(name,f); + return f; + case EArrayDecl(arr): + var a = new Array(); + for( e in arr ) + a.push(expr(e)); + return a; + case EArray(e,index): + return expr(e)[expr(index)]; } return null; } @@ -194,28 +263,33 @@ class Interp { try { block([e]); } catch( err : Stop ) { - if( err == SBreak ) break; + switch(err) { + case SContinue: + case SBreak: break; + case SReturn(_): throw err; + } } } } function forLoop(v,it,e) { - var old = if( variables.exists(v) ) { v : variables.get(v) } else null; + var old = locals.get(v); var it : Dynamic = expr(it); if( it.iterator != null ) it = it.iterator(); if( it.hasNext == null || it.next == null ) throw Error.EInvalidIterator(v); while( it.hasNext() ) { - variables.set(v,it.next()); + locals.set(v,{ r : it.next() }); try { block([e]); } catch( err : Stop ) { - if( err == SBreak ) break; + switch( err ) { + case SContinue: + case SBreak: break; + case SReturn(_): throw err; + } } } - if( old == null ) - variables.remove(v) - else - variables.set(v,old.v); + locals.set(v,old); } function get( o : Dynamic, f : String ) { diff --git a/hscript/Parser.hx b/hscript/Parser.hx index b4aeec3..97a42da 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -37,6 +37,8 @@ enum Token { TDot; TComma; TSemicolon; + TBkOpen; + TBkClose; } class Parser { @@ -93,7 +95,7 @@ class Parser { tokens.add(tk); a.push(parseFullExpr(s)); } - return a; + return if( a.length == 1 ) a[0] else EBlock(a); } function unexpected( tk ) : Dynamic { @@ -107,6 +109,11 @@ class Parser { if( tk != TSemicolon && tk != TEof ) switch( e ) { case EBlock(_): tokens.add(tk); + case EFunction(_,e,_): + switch(e) { + case EBlock(_): tokens.add(tk); + default: unexpected(tk); + } default: unexpected(tk); } return e; @@ -118,8 +125,8 @@ class Parser { case TId(id): var e = parseStructure(s,id); if( e == null ) - return parseExprNext(s,EIdent(id)); - return e; + e = EIdent(id); + return parseExprNext(s,e); case TConst(c): return parseExprNext(s,EConst(c)); case TPOpen: @@ -143,6 +150,8 @@ class Parser { if( x == op ) return EUnop(op,true,parseExpr(s)); return unexpected(tk); + case TBkOpen: + return parseExprNext(s,EArrayDecl(parseExprList(s,TBkClose))); default: return unexpected(tk); } @@ -203,6 +212,36 @@ class Parser { case "break": EBreak; case "continue": EContinue; case "else": unexpected(TId(id)); + case "function": + var tk = token(s); + var name = null; + switch( tk ) { + case TId(id): name = id; tk = token(s); + default: + } + if( tk != TPOpen ) unexpected(tk); + var args = new Array(); + tk = token(s); + if( tk != TPClose ) { + while( true ) { + switch( tk ) { + case TId(id): args.push(id); + default: unexpected(tk); + } + tk = token(s); + switch( tk ) { + case TComma: + case TPClose: break; + default: unexpected(tk); + } + tk = token(s); + } + } + EFunction(args,parseExpr(s),name); + case "return": + var tk = token(s); + tokens.add(tk); + EReturn(if( tk == TSemicolon ) null else parseExpr(s)); default: null; } } @@ -238,27 +277,37 @@ class Parser { } return parseExprNext(s,EField(e1,field)); case TPOpen: - var args = new Array(); + return parseExprNext(s,ECall(e1,parseExprList(s,TPClose))); + case TBkOpen: + var e2 = parseExpr(s); tk = token(s); - if( tk != TPClose ) { - tokens.add(tk); - while( true ) { - args.push(parseExpr(s)); - tk = token(s); - switch( tk ) { - case TComma: - case TPClose: break; - default: unexpected(tk); - } - } - } - return ECall(e1,args); + if( tk != TBkClose ) unexpected(tk); + return parseExprNext(s,EArray(e1,e2)); default: tokens.add(tk); return e1; } } + function parseExprList( s : haxe.io.Input, etk ) { + var args = new Array(); + var tk = token(s); + if( tk == etk ) + return args; + tokens.add(tk); + while( true ) { + args.push(parseExpr(s)); + tk = token(s); + switch( tk ) { + case TComma: + default: + if( tk == etk ) break; + unexpected(tk); + } + } + return args; + } + function readChar( s : haxe.io.Input ) { return try s.readByte() catch( e : Dynamic ) 0; } @@ -334,6 +383,8 @@ class Parser { case 46: return TDot; case 123: return TBrOpen; case 125: return TBrClose; + case 91: return TBkOpen; + case 93: return TBkClose; case 39: return TConst( CString(readString(s,39)) ); case 34: return TConst( CString(readString(s,34)) ); default: @@ -387,6 +438,8 @@ class Parser { case TDot: "."; case TComma: ","; case TSemicolon: ";"; + case TBkOpen: "["; + case TBkClose: "]"; } }