diff --git a/Test.hx b/Test.hx new file mode 100644 index 0000000..61d29d0 --- /dev/null +++ b/Test.hx @@ -0,0 +1,18 @@ +class Test { + + static function main() { + var script = " + var sum = 0; + for( a in angles ) + sum = sum + Math.cos(a); + sum; + "; + var p = new hscript.Parser(); + var program = p.parseString(script); + var interp = new hscript.Interp(); + 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) ); + } + +} \ No newline at end of file diff --git a/haxelib.xml b/haxelib.xml new file mode 100644 index 0000000..ffdfd9d --- /dev/null +++ b/haxelib.xml @@ -0,0 +1,5 @@ + + + haXe Script is a scripting engine for a subset of the haXe language + Initial Release + \ No newline at end of file diff --git a/hscript.hxml b/hscript.hxml new file mode 100644 index 0000000..e1e6c22 --- /dev/null +++ b/hscript.hxml @@ -0,0 +1,5 @@ +# Neko +-swf hscript9.swf +-swf-version 9 +-main Test + diff --git a/hscript.hxp b/hscript.hxp new file mode 100644 index 0000000..b38ca34 --- /dev/null +++ b/hscript.hxp @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/hscript/Expr.hx b/hscript/Expr.hx new file mode 100644 index 0000000..d382cf5 --- /dev/null +++ b/hscript/Expr.hx @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2008, Nicolas Cannasse + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ +package hscript; + +enum Const { + CInt( v : Int ); + CFloat( f : Float ); + CString( s : String ); +} + +enum Expr { + EConst( c : Const ); + EIdent( v : String ); + EVar( n : String, ?e : Expr ); + EParent( e : Expr ); + EBlock( e : Array ); + EField( e : Expr, f : String ); + EBinop( op : String, e1 : Expr, e2 : Expr ); + EUnop( op : String, prefix : Bool, e : Expr ); + ECall( e : Expr, params : Array ); + EIf( cond : Expr, e1 : Expr, ?e2 : Expr ); + EWhile( cond : Expr, e : Expr ); + EFor( v : String, it : Expr, e : Expr ); + EBreak; + EContinue; +} + +enum Error { + EInvalidChar( c : Int ); + EUnexpected( s : String ); + EUnterminatedString; + EUnknownVariable( v : String ); + EInvalidIterator( v : String ); + EInvalidOp( op : String ); +} diff --git a/hscript/Interp.hx b/hscript/Interp.hx new file mode 100644 index 0000000..8a26aff --- /dev/null +++ b/hscript/Interp.hx @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2008, Nicolas Cannasse + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ +package hscript; +import hscript.Expr; + +private enum Stop { + SBreak; + SContinue; +} + +class Interp { + + public var variables : Hash; + var binops : Hash Expr -> Dynamic>; + var declared : Array<{ n : String, old : Dynamic, exists : Bool }>; + + public function new() { + variables = new Hash(); + variables.set("null",null); + variables.set("true",true); + variables.set("false",false); + initOps(); + } + + function initOps() { + var me = this; + binops = new Hash(); + binops.set("+",function(e1,e2) return me.expr(e1) + me.expr(e2)); + binops.set("-",function(e1,e2) return me.expr(e1) - me.expr(e2)); + binops.set("*",function(e1,e2) return me.expr(e1) * me.expr(e2)); + binops.set("/",function(e1,e2) return me.expr(e1) / me.expr(e2)); + binops.set("%",function(e1,e2) return me.expr(e1) % me.expr(e2)); + binops.set("&",function(e1,e2) return me.expr(e1) & me.expr(e2)); + binops.set("|",function(e1,e2) return me.expr(e1) | me.expr(e2)); + binops.set("^",function(e1,e2) return me.expr(e1) ^ me.expr(e2)); + binops.set("<<",function(e1,e2) return me.expr(e1) << me.expr(e2)); + binops.set(">>",function(e1,e2) return me.expr(e1) >> me.expr(e2)); + binops.set(">>>",function(e1,e2) return me.expr(e1) >>> me.expr(e2)); + binops.set("==",function(e1,e2) return me.expr(e1) == me.expr(e2)); + binops.set("!=",function(e1,e2) return me.expr(e1) != me.expr(e2)); + binops.set(">=",function(e1,e2) return me.expr(e1) >= me.expr(e2)); + binops.set("<=",function(e1,e2) return me.expr(e1) <= me.expr(e2)); + binops.set(">",function(e1,e2) return me.expr(e1) > me.expr(e2)); + binops.set("<",function(e1,e2) return me.expr(e1) < me.expr(e2)); + binops.set("||",function(e1,e2) return me.expr(e1) == true || me.expr(e2) == true); + binops.set("&&",function(e1,e2) return me.expr(e1) == true && me.expr(e2) == true); + binops.set("=",assign); + } + + 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); + default: throw Error.EInvalidOp("="); + } + return v; + } + + function increment( e : Expr, prefix : Bool, delta : Int ) : Dynamic { + switch(e) { + case EIdent(id): + var v : Dynamic = variables.get(id); + if( prefix ) { + v += delta; + variables.set(id,v); + } else + variables.set(id,v + delta); + return v; + case EField(e,f): + var obj = expr(e); + var v : Dynamic = get(obj,f); + if( prefix ) { + v += delta; + set(obj,f,v); + } else + set(obj,f,v + delta); + return v; + default: + throw Error.EInvalidOp((delta > 0)?"++":"--"); + } + } + + public function execute( program : Array ) { + return block(program); + } + + function block( exprs : Array ) { + var old = declared; + declared = new Array(); + var v = null; + 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); + declared = old; + return v; + } + + public function expr( e : Expr ) : Dynamic { + switch( e ) { + case EConst(c): + switch( c ) { + case CInt(v): return v; + case CFloat(f): return f; + case CString(s): return s; + } + case EIdent(id): + 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)); + return null; + case EParent(e): + return expr(e); + case EBlock(exprs): + return block(exprs); + case EField(e,f): + return get(expr(e),f); + case EBinop(op,e1,e2): + var fop = binops.get(op); + if( fop == null ) throw Error.EInvalidOp(op); + return fop(e1,e2); + case EUnop(op,prefix,e): + switch(op) { + case "!": + return expr(e) != true; + case "-": + return -expr(e); + case "++": + return increment(e,prefix,1); + case "--": + return increment(e,prefix,-1); + default: + throw Error.EInvalidOp(op); + } + case ECall(e,params): + var args = new Array(); + for( p in params ) + args.push(expr(p)); + switch(e) { + case EField(e,f): + var obj = expr(e); + return call(obj,Reflect.field(obj,f),args); + default: + return call(null,expr(e),args); + } + case EIf(econd,e1,e2): + return if( expr(econd) == true ) expr(e1) else if( e2 == null ) null else expr(e2); + case EWhile(econd,e): + whileLoop(econd,e); + return null; + case EFor(v,it,e): + forLoop(v,it,e); + return null; + case EBreak: + throw SBreak; + case EContinue: + throw SContinue; + } + return null; + } + + function whileLoop(econd,e) { + while( expr(econd) == true ) { + try { + block([e]); + } catch( err : Stop ) { + if( err == SBreak ) break; + } + } + } + + function forLoop(v,it,e) { + var old = if( variables.exists(v) ) { v : variables.get(v) } else null; + 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()); + try { + block([e]); + } catch( err : Stop ) { + if( err == SBreak ) break; + } + } + if( old == null ) + variables.remove(v) + else + variables.set(v,old.v); + } + + function get( o : Dynamic, f : String ) { + return Reflect.field(o,f); + } + + function set( o : Dynamic, f : String, v : Dynamic ) { + Reflect.setField(o,f,v); + return v; + } + + function call( o : Dynamic, f : Dynamic, args : Array ) { + return Reflect.callMethod(o,f,args); + } + +} \ No newline at end of file diff --git a/hscript/Parser.hx b/hscript/Parser.hx new file mode 100644 index 0000000..b4aeec3 --- /dev/null +++ b/hscript/Parser.hx @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2008, Nicolas Cannasse + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ +package hscript; +import hscript.Expr; + +enum Token { + TEof; + TConst( c : Const ); + TId( s : String ); + TOp( s : String ); + TPOpen; + TPClose; + TBrOpen; + TBrClose; + TDot; + TComma; + TSemicolon; +} + +class Parser { + + // config / variables + public var line : Int; + public var opChars : String; + public var identChars : String; + public var opPriority : Array; + public var unopsPrefix : Array; + public var unopsSuffix : Array; + + // implementation + var char : Null; + var ops : Array; + var idents : Array; + var tokens : haxe.FastList; + + public function new() { + line = 1; + opChars = "+*/-=!><&|^%"; + identChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"; + opPriority = [ + "=", + "||","&&", + "==","!=",">","<",">=","<=", + "|","&","^", + "<<",">>",">>>", + "+","-", + "*","/", + "%" + ]; + unopsPrefix = ["!","++","--","-"]; + unopsSuffix = ["++","--"]; + } + + public function parseString( s : String ) { + return parse( new haxe.io.StringInput(s) ); + } + + public function parse( s : haxe.io.Input ) { + char = null; + ops = new Array(); + idents = new Array(); + tokens = new haxe.FastList(); + for( i in 0...opChars.length ) + ops[opChars.charCodeAt(i)] = true; + for( i in 0...identChars.length ) + idents[identChars.charCodeAt(i)] = true; + var a = new Array(); + while( true ) { + var tk = token(s); + if( tk == TEof ) break; + tokens.add(tk); + a.push(parseFullExpr(s)); + } + return a; + } + + function unexpected( tk ) : Dynamic { + throw Error.EUnexpected(tokenString(tk)); + return null; + } + + function parseFullExpr(s) { + var e = parseExpr(s); + var tk = token(s); + if( tk != TSemicolon && tk != TEof ) + switch( e ) { + case EBlock(_): tokens.add(tk); + default: unexpected(tk); + } + return e; + } + + function parseExpr( s : haxe.io.Input ) { + var tk = token(s); + switch( tk ) { + case TId(id): + var e = parseStructure(s,id); + if( e == null ) + return parseExprNext(s,EIdent(id)); + return e; + case TConst(c): + return parseExprNext(s,EConst(c)); + case TPOpen: + var e = parseExpr(s); + tk = token(s); + if( tk != TPClose ) unexpected(tk); + return parseExprNext(s,EParent(e)); + case TBrOpen: + var a = new Array(); + while( true ) { + tk = token(s); + if( tk == TBrClose ) + break; + tokens.add(tk); + a.push(parseFullExpr(s)); + } + return EBlock(a); + case TOp(op): + var found; + for( x in unopsPrefix ) + if( x == op ) + return EUnop(op,true,parseExpr(s)); + return unexpected(tk); + default: + return unexpected(tk); + } + } + + function parseStructure( s, id ) { + return switch( id ) { + case "if": + var cond = parseExpr(s); + var e1 = parseExpr(s); + var e2 = null; + var semic = false; + var tk = token(s); + if( tk == TSemicolon ) { + semic = true; + tk = token(s); + } + if( Type.enumEq(tk,TId("else")) ) + e2 = parseExpr(s); + else { + tokens.add(tk); + if( semic ) tokens.add(TSemicolon); + } + EIf(cond,e1,e2); + case "var": + var tk = token(s); + var ident; + switch(tk) { + case TId(id): ident = id; + default: unexpected(tk); + } + tk = token(s); + var e; + if( Type.enumEq(tk,TOp("=")) ) + e = parseExpr(s); + else + tokens.add(tk); + EVar(ident,e); + case "while": + var econd = parseExpr(s); + var e = parseExpr(s); + EWhile(econd,e); + case "for": + var tk = token(s); + if( tk != TPOpen ) unexpected(tk); + tk = token(s); + var vname; + switch( tk ) { + case TId(id): vname = id; + default: unexpected(tk); + } + tk = token(s); + if( !Type.enumEq(tk,TId("in")) ) unexpected(tk); + var eiter = parseExpr(s); + tk = token(s); + if( tk != TPClose ) unexpected(tk); + EFor(vname,eiter,parseExpr(s)); + case "break": EBreak; + case "continue": EContinue; + case "else": unexpected(TId(id)); + default: null; + } + } + + function priority(op) { + for( i in 0...opPriority.length ) + if( opPriority[i] == op ) + return i; + return -1; + } + + function parseExprNext( s : haxe.io.Input, e1 : Expr ) { + var tk = token(s); + switch( tk ) { + case TOp(op): + for( x in unopsSuffix ) + if( x == op ) + return EUnop(op,false,e1); + var e2 = parseExpr(s); + switch( e2 ) { + case EBinop(op2,e2,e3): + if( priority(op) > priority(op2) ) + return EBinop(op2,EBinop(op,e1,e2),e3); + default: + } + return EBinop(op,e1,e2); + case TDot: + tk = token(s); + var field; + switch(tk) { + case TId(id): field = id; + default: unexpected(tk); + } + return parseExprNext(s,EField(e1,field)); + case TPOpen: + var args = new Array(); + 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); + default: + tokens.add(tk); + return e1; + } + } + + function readChar( s : haxe.io.Input ) { + return try s.readByte() catch( e : Dynamic ) 0; + } + + function readString( s : haxe.io.Input, until ) { + var c; + var b = new StringBuf(); + var esc = false; + while( true ) { + try { + c = s.readByte(); + } catch( e : Dynamic ) { + throw Error.EUnterminatedString; + } + if( esc ) { + esc = false; + switch( c ) { + case 110: b.addChar(10); // \n + case 114: b.addChar(13); // \r + case 116: b.addChar(9); // \t + case 39: b.addChar(39); // \' + case 34: b.addChar(34); // \" + case 92: b.addChar(92); // \\ + default: throw Error.EInvalidChar(c); + } + } else if( c == 92 ) + esc = true; + else if( c == until ) + break; + else + b.addChar(c); + } + return b.toString(); + } + + function token( s : haxe.io.Input ) { + if( !tokens.isEmpty() ) + return tokens.pop(); + var char; + if( this.char == null ) + char = readChar(s); + else { + char = this.char; + this.char = null; + } + while( true ) { + switch( char ) { + case 0: return TEof; + case 32,9,13: // space, tab, CR + case 10: line++; // LF + case 48,49,50,51,52,53,54,55,56,57: // 0...9 + var n = char - 48; + var exp = 0; + while( true ) { + char = readChar(s); + exp *= 10; + switch( char ) { + case 48,49,50,51,52,53,54,55,56,57: + n = n * 10 + (char - 48); + case 46: + if( exp > 0 ) + throw Error.EInvalidChar(char); + exp = 1; + default: + this.char = char; + return TConst( (exp > 0) ? CFloat(n * 10 / exp) : CInt(n) ); + } + } + case 59: return TSemicolon; + case 40: return TPOpen; + case 41: return TPClose; + case 44: return TComma; + case 46: return TDot; + case 123: return TBrOpen; + case 125: return TBrClose; + case 39: return TConst( CString(readString(s,39)) ); + case 34: return TConst( CString(readString(s,34)) ); + default: + if( ops[char] ) { + var op = Std.chr(char); + while( true ) { + char = readChar(s); + if( !ops[char] ) { + this.char = char; + return TOp(op); + } + op += Std.chr(char); + } + } + if( idents[char] ) { + var id = Std.chr(char); + while( true ) { + char = readChar(s); + if( !idents[char] ) { + this.char = char; + return TId(id); + } + id += Std.chr(char); + } + } + throw Error.EInvalidChar(char); + } + char = readChar(s); + } + return null; + } + + function constString( c ) { + return switch(c) { + case CInt(v): Std.string(v); + case CFloat(f): Std.string(f); + case CString(s): s; // TODO : escape + quote + } + } + + function tokenString( t ) { + return switch( t ) { + case TEof: ""; + case TConst(c): constString(c); + case TId(s): s; + case TOp(s): s; + case TPOpen: "("; + case TPClose: ")"; + case TBrOpen: "{"; + case TBrClose: "}"; + case TDot: "."; + case TComma: ","; + case TSemicolon: ";"; + } + } + +}