diff --git a/hscript/Expr.hx b/hscript/Expr.hx index b4382f4..e25529e 100644 --- a/hscript/Expr.hx +++ b/hscript/Expr.hx @@ -31,7 +31,16 @@ enum Const { CInt32( v : haxe.Int32 ); } +#if hscriptPos +typedef Expr = { + var e : ExprDef; + var pmin : Int; + var pmax : Int; +} +enum ExprDef { +#else enum Expr { +#end EConst( c : Const ); EIdent( v : String ); EVar( n : String, ?t : CType, ?e : Expr ); @@ -63,7 +72,21 @@ enum CType { CTParent( t : CType ); } +#if hscriptPos +class Error { + public var e : ErrorDef; + public var pmin : Int; + public var pmax : Int; + public function new(e, pmin, pmax) { + this.e = e; + this.pmin = pmin; + this.pmax = pmax; + } +} +enum ErrorDef { +#else enum Error { +#end EInvalidChar( c : Int ); EUnexpected( s : String ); EUnterminatedString; diff --git a/hscript/Macro.hx b/hscript/Macro.hx new file mode 100644 index 0000000..d9e3230 --- /dev/null +++ b/hscript/Macro.hx @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2011, 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.Error; +#if hscriptPos +import hscript.Expr.ErrorDef; +#end +import haxe.macro.Expr; + +class Macro { + + var p : Position; + var binops : Hash; + var unops : Hash; + + public function new(pos) { + p = pos; + binops = new Hash(); + for( c in Type.getEnumConstructs(Binop) ) { + if( c == "OpAssignOp" ) continue; + var op = Type.createEnum(Binop, c); + var assign = false; + var str = switch( op ) { + case OpAdd: assign = true; "+"; + case OpMult: assign = true; "*"; + case OpDiv: assign = true; "/"; + case OpSub: assign = true; "-"; + case OpAssign: "="; + case OpEq: "=="; + case OpNotEq: "!="; + case OpGt: ">"; + case OpGte: ">="; + case OpLt: "<"; + case OpLte: "<="; + case OpAnd: assign = true; "&"; + case OpOr: assign = true; "|"; + case OpXor: assign = true; "^"; + case OpBoolAnd: "&&"; + case OpBoolOr: "||"; + case OpShl: assign = true; "<<"; + case OpShr: assign = true; ">>"; + case OpUShr: assign = true; ">>>"; + case OpMod: assign = true; "%"; + case OpAssignOp(_): ""; + case OpInterval: "..."; + }; + binops.set(str, op); + if( assign ) + binops.set(str + "=", OpAssignOp(op)); + } + unops = new Hash(); + for( c in Type.getEnumConstructs(Unop) ) { + var op = Type.createEnum(Unop, c); + var str = switch( op ) { + case OpNot: "!"; + case OpNeg: "-"; + case OpNegBits: "~"; + case OpIncrement: "++"; + case OpDecrement: "--"; + } + unops.set(str, op); + } + } + + function isType(v:String) { + var c0 = v.charCodeAt(0); + return c0 >= 'A'.code && c0 <= 'Z'.code; + } + + function map < T, R > ( a : Array, f : T -> R ) : Array { + var b = new Array(); + for( x in a ) + b.push(f(x)); + return b; + } + + function convertType( t : Expr.CType ) : ComplexType { + return switch( t ) { + case CTPath(pack, args): + var params = []; + if( args != null ) + for( t in args ) + params.push(TPType(convertType(t))); + TPath({ + pack : pack, + name : pack.pop(), + params : params, + sub : null, + }); + case CTParent(t): TParent(convertType(t)); + case CTFun(args, ret): + TFunction(map(args,convertType), convertType(ret)); + case CTAnon(fields): + var tf = []; + for( f in fields ) + tf.push( { name : f.name, isPublic : null, type : FVar(convertType(f.t)), pos : p } ); + TAnonymous(tf); + }; + } + + public function convert( e : hscript.Expr ) : Expr { + return { expr : switch( #if hscriptPos e.e #else e #end ) { + case EConst(c): + EConst(switch(c) { + case CInt(v): CInt(Std.string(v)); + case CFloat(f): CFloat(Std.string(f)); + case CString(s): CString(s); + case CInt32(v): CInt(Std.string(v)); + }); + case EIdent(v): + if( isType(v) ) + EConst(CType(v)); + else + EConst(CIdent(v)); + case EVar(n, t, e): + EVars([ { name : n, expr : if( e == null ) null else convert(e), type : if( t == null ) null else convertType(t) } ]); + case EParent(e): + EParenthesis(convert(e)); + case EBlock(el): + EBlock(map(el,convert)); + case EField(e, f): + if( isType(f) ) + EType(convert(e), f); + else + EField(convert(e), f); + case EBinop(op, e1, e2): + var b = binops.get(op); + if( b == null ) throw EInvalidOp(op); + EBinop(b, convert(e1), convert(e2)); + case EUnop(op, prefix, e): + var u = unops.get(op); + if( u == null ) throw EInvalidOp(op); + EUnop(u, !prefix, convert(e)); + case ECall(e, params): + ECall(convert(e), map(params, convert)); + case EIf(c, e1, e2): + EIf(convert(c), convert(e1), e2 == null ? null : convert(e2)); + case EWhile(c, e): + EWhile(convert(c), convert(e), true); + case EFor(v, it, e): + EFor(v, convert(it), convert(e)); + case EBreak: + EBreak; + case EContinue: + EContinue; + case EFunction(args, e, name, ret): + var targs = []; + for( a in args ) + targs.push( { + name : a.name, + type : a.t == null ? null : convertType(a.t), + opt : false, + value : null, + }); + EFunction( { + name : name, + args : targs, + expr : convert(e), + ret : ret == null ? null : convertType(ret), + }); + case EReturn(e): + EReturn(e == null ? null : convert(e)); + case EArray(e, index): + EArray(convert(e), convert(index)); + case EArrayDecl(el): + EArrayDecl(map(el,convert)); + case ENew(cl, params): + var pack = cl.split("."); + ENew( { pack : pack, name : pack.pop(), params : [], sub : null }, map(params, convert)); + case EThrow(e): + EThrow(convert(e)); + case ETry(e, v, t, ec): + ETry(convert(e), [ { type : convertType(t), name : v, expr : convert(ec) } ]); + case EObject(fields): + var tf = []; + for( f in fields ) + tf.push( { field : f.name, expr : convert(f.e) } ); + EObjectDecl(tf); + }, pos : #if hscriptPos { file : p.file, min : e.pmin, max : e.pmax } #else p #end } + } + +} \ No newline at end of file diff --git a/hscript/Parser.hx b/hscript/Parser.hx index fec8c19..e712bba 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -65,10 +65,25 @@ class Parser { // implementation var input : haxe.io.Input; - var char : Null; + var char : Int; var ops : Array; var idents : Array; + + + // only used for hscriptPos, but needs to be defined + var readPos : Int; + var tokenMin : Int; + var tokenMax : Int; + + + #if hscriptPos + var oldTokenMin : Int; + var oldTokenMax : Int; + var tokens : List<{ min : Int, max : Int, t : Token }>; + #else var tokens : haxe.FastList; + #end + public function new() { line = 1; @@ -86,7 +101,19 @@ class Parser { "%" ]; unopsPrefix = ["!","++","--","-","~"]; - unopsSuffix = ["++","--"]; + unopsSuffix = ["++", "--"]; + } + + public inline function error( err, pmin, pmax ) { + #if hscriptPos + throw new Error(err, pmin, pmax); + #else + throw err; + #end + } + + public function invalidChar(c) { + error(EInvalidChar(c), readPos, readPos); } public function parseString( s : String ) { @@ -95,11 +122,18 @@ class Parser { } public function parse( s : haxe.io.Input ) { - char = null; + #if hscriptPos + readPos = 0; + tokenMin = oldTokenMin = 0; + tokenMax = oldTokenMax = 0; + tokens = new List(); + #else + tokens = new haxe.FastList(); + #end + char = -1; input = s; 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 ) @@ -111,25 +145,65 @@ class Parser { push(tk); a.push(parseFullExpr()); } - return if( a.length == 1 ) a[0] else EBlock(a); + return if( a.length == 1 ) a[0] else mk(EBlock(a),0); } function unexpected( tk ) : Dynamic { - throw Error.EUnexpected(tokenString(tk)); + error(EUnexpected(tokenString(tk)),tokenMin,tokenMax); return null; } inline function push(tk) { + #if hscriptPos + tokens.push( { t : tk, min : tokenMin, max : tokenMax } ); + tokenMin = oldTokenMin; + tokenMax = oldTokenMax; + #else tokens.add(tk); + #end } inline function ensure(tk) { var t = token(); if( t != tk ) unexpected(t); } + + inline function expr(e:Expr) { + #if hscriptPos + return e.e; + #else + return e; + #end + } + + inline function pmin(e:Expr) { + #if hscriptPos + return e.pmin; + #else + return 0; + #end + } + + inline function pmax(e:Expr) { + #if hscriptPos + return e.pmax; + #else + return 0; + #end + } + + inline function mk(e,?pmin,?pmax) : Expr { + #if hscriptPos + if( pmin == null ) pmin = tokenMin; + if( pmax == null ) pmax = tokenMax; + return { e : e, pmin : pmin, pmax : pmax }; + #else + return e; + #end + } function isBlock(e) { - return switch( e ) { + return switch( expr(e) ) { case EBlock(_), EObject(_): true; case EFunction(_,e,_,_): isBlock(e); case EVar(_,_,e): e != null && isBlock(e); @@ -155,7 +229,7 @@ class Parser { return e; } - function parseObject() { + function parseObject(p1) { // parse object var fl = new Array(); while( true ) { @@ -186,35 +260,36 @@ class Parser { unexpected(tk); } } - return parseExprNext(EObject(fl)); + return parseExprNext(mk(EObject(fl),p1)); } function parseExpr() { var tk = token(); + var p1 = tokenMin; switch( tk ) { case TId(id): var e = parseStructure(id); if( e == null ) - e = EIdent(id); + e = mk(EIdent(id)); return parseExprNext(e); case TConst(c): - return parseExprNext(EConst(c)); + return parseExprNext(mk(EConst(c))); case TPOpen: var e = parseExpr(); ensure(TPClose); - return parseExprNext(EParent(e)); + return parseExprNext(mk(EParent(e),p1,pmax(e))); case TBrOpen: tk = token(); switch( tk ) { case TBrClose: - return parseExprNext(EObject([])); + return parseExprNext(mk(EObject([]),p1)); case TId(id): var tk2 = token(); push(tk2); push(tk); switch( tk2 ) { case TDoubleDot: - return parseExprNext(parseObject()); + return parseExprNext(parseObject(p1)); default: } case TConst(c): @@ -226,7 +301,7 @@ class Parser { push(tk); switch( tk2 ) { case TDoubleDot: - return parseExprNext(parseObject()); + return parseExprNext(parseObject(p1)); default: } default: @@ -245,7 +320,7 @@ class Parser { break; push(tk); } - return EBlock(a); + return mk(EBlock(a),p1); case TOp(op): var found; for( x in unopsPrefix ) @@ -262,7 +337,7 @@ class Parser { if( tk == TComma ) tk = token(); } - return parseExprNext(EArrayDecl(a)); + return parseExprNext(mk(EArrayDecl(a),p1)); default: return unexpected(tk); } @@ -276,24 +351,26 @@ class Parser { } function makeUnop( op, e ) { - return switch( e ) { - case EBinop(bop,e1,e2): EBinop(bop,makeUnop(op,e1),e2); - default: EUnop(op,true,e); + return switch( expr(e) ) { + case EBinop(bop, e1, e2): mk(EBinop(bop, makeUnop(op, e1), e2),pmin(e1),pmax(e2)); + default: mk(EUnop(op,true,e),pmin(e),pmax(e)); } } function makeBinop( op, e1, e ) { - return switch( e ) { + return switch( expr(e) ) { case EBinop(op2,e2,e3): if( priority(op) > priority(op2) ) - EBinop(op2,makeBinop(op,e1,e2),e3); + mk(EBinop(op2,makeBinop(op,e1,e2),e3),pmin(e1),pmax(e3)); else - EBinop(op,e1,e); - default: EBinop(op,e1,e); + mk(EBinop(op, e1, e),pmin(e1),pmax(e)); + default: + mk(EBinop(op,e1,e),pmin(e1),pmax(e)); } } function parseStructure(id) { + var p1 = tokenMin; return switch( id ) { case "if": var cond = parseExpr(); @@ -311,7 +388,7 @@ class Parser { push(tk); if( semic ) push(TSemicolon); } - EIf(cond,e1,e2); + mk(EIf(cond,e1,e2),p1,(e2 == null) ? tokenMax : pmax(e2)); case "var": var tk = token(); var ident = null; @@ -330,11 +407,11 @@ class Parser { e = parseExpr(); else push(tk); - EVar(ident,t,e); + mk(EVar(ident,t,e),p1,(e == null) ? tokenMax : pmax(e)); case "while": var econd = parseExpr(); var e = parseExpr(); - EWhile(econd,e); + mk(EWhile(econd,e),p1,pmax(e)); case "for": ensure(TPOpen); var tk = token(); @@ -347,9 +424,10 @@ class Parser { if( !Type.enumEq(tk,TId("in")) ) unexpected(tk); var eiter = parseExpr(); ensure(TPClose); - EFor(vname,eiter,parseExpr()); - case "break": EBreak; - case "continue": EContinue; + var e = parseExpr(); + mk(EFor(vname,eiter,e),p1,pmax(e)); + case "break": mk(EBreak); + case "continue": mk(EContinue); case "else": unexpected(TId(id)); case "function": var tk = token(); @@ -391,11 +469,13 @@ class Parser { else ret = parseType(); } - EFunction(args, parseExpr(), name, ret); + var body = parseExpr(); + mk(EFunction(args, body, name, ret),p1,pmax(body)); case "return": var tk = token(); push(tk); - EReturn(if( tk == TSemicolon ) null else parseExpr()); + var e = if( tk == TSemicolon ) null else parseExpr(); + mk(EReturn(e),p1,if( e == null ) tokenMax else pmax(e)); case "new": var a = new Array(); var tk = token(); @@ -418,9 +498,11 @@ class Parser { unexpected(tk); } } - ENew(a.join("."),parseExprList(TPClose)); + var args = parseExprList(TPClose); + mk(ENew(a.join("."),args),p1); case "throw": - EThrow( parseExpr() ); + var e = parseExpr(); + mk(EThrow(e),p1,pmax(e)); case "try": var e = parseExpr(); var tk = token(); @@ -440,7 +522,8 @@ class Parser { if( !Type.enumEq(tk, TId("Dynamic")) ) unexpected(tk); } ensure(TPClose); - ETry(e,vname,t,parseExpr()); + var ec = parseExpr(); + mk(ETry(e,vname,t,ec),p1,pmax(ec)); default: null; } @@ -452,11 +535,11 @@ class Parser { case TOp(op): for( x in unopsSuffix ) if( x == op ) { - if( isBlock(e1) || switch(e1) { case EParent(_): true; default: false; } ) { + if( isBlock(e1) || switch(expr(e1)) { case EParent(_): true; default: false; } ) { push(tk); return e1; } - return parseExprNext(EUnop(op,false,e1)); + return parseExprNext(mk(EUnop(op,false,e1),pmin(e1))); } return makeBinop(op,e1,parseExpr()); case TDot: @@ -466,18 +549,18 @@ class Parser { case TId(id): field = id; default: unexpected(tk); } - return parseExprNext(EField(e1,field)); + return parseExprNext(mk(EField(e1,field),pmin(e1))); case TPOpen: - return parseExprNext(ECall(e1,parseExprList(TPClose))); + return parseExprNext(mk(ECall(e1,parseExprList(TPClose)),pmin(e1))); case TBkOpen: var e2 = parseExpr(); ensure(TBkClose); - return parseExprNext(EArray(e1,e2)); + return parseExprNext(mk(EArray(e1,e2),pmin(e1))); case TQuestion: var e2 = parseExpr(); ensure(TDoubleDot); var e3 = parseExpr(); - return EIf(e1,e2,e3); + return mk(EIf(e1,e2,e3),pmin(e1),pmax(e3)); default: push(tk); return e1; @@ -577,7 +660,7 @@ class Parser { var tk = token(); if( tk == etk ) return args; - tokens.add(tk); + push(tk); while( true ) { args.push(parseExpr()); tk = token(); @@ -590,8 +673,15 @@ class Parser { } return args; } + + inline function incPos() { + #if hscriptPos + readPos++; + #end + } function readChar() { + incPos(); return try input.readByte() catch( e : Dynamic ) 0; } @@ -601,12 +691,14 @@ class Parser { var esc = false; var old = line; var s = input; + var p1 = readPos - 1; while( true ) { try { + incPos(); c = s.readByte(); } catch( e : Dynamic ) { line = old; - throw Error.EUnterminatedString; + error(EUnterminatedString, p1, p1); } if( esc ) { esc = false; @@ -615,15 +707,19 @@ class Parser { case 'r'.code: b.writeByte(13); case 't'.code: b.writeByte(9); case "'".code, '"'.code, '\\'.code: b.writeByte(c); - case '/'.code: if( allowJSON ) b.writeByte(c) else throw Error.EInvalidChar(c); + case '/'.code: if( allowJSON ) b.writeByte(c) else invalidChar(c); case "u".code: - if( !allowJSON ) throw Error.EInvalidChar(c); + if( !allowJSON ) throw invalidChar(c); var code; try { + incPos(); + incPos(); + incPos(); + incPos(); code = s.readString(4); } catch( e : Dynamic ) { line = old; - throw Error.EUnterminatedString; + error(EUnterminatedString, p1, p1); } var k = 0; for( i in 0...4 ) { @@ -637,7 +733,7 @@ class Parser { case 97,98,99,100,101,102: // a-f k += char - 87; default: - throw Error.EInvalidChar(char); + invalidChar(char); } } // encode k in UTF8 @@ -651,7 +747,7 @@ class Parser { b.writeByte( 0x80 | ((k >> 6) & 63) ); b.writeByte( 0x80 | (k & 63) ); } - default: throw Error.EInvalidChar(c); + default: invalidChar(c); } } else if( c == 92 ) esc = true; @@ -666,20 +762,44 @@ class Parser { } function token() { + #if hscriptPos + var t = tokens.pop(); + if( t != null ) { + tokenMin = t.min; + tokenMax = t.max; + return t.t; + } + oldTokenMin = tokenMin; + oldTokenMax = tokenMax; + tokenMin = (this.char < 0) ? readPos : readPos - 1; + var t = _token(); + tokenMax = (this.char < 0) ? readPos - 1 : readPos - 2; + trace(t + ":" + tokenMin + ":" + tokenMax); + return t; + } + function _token() { + #else if( !tokens.isEmpty() ) return tokens.pop(); + #end var char; - if( this.char == null ) + if( this.char < 0 ) char = readChar(); else { char = this.char; - this.char = null; + this.char = -1; } while( true ) { switch( char ) { case 0: return TEof; case 32,9,13: // space, tab, CR + #if hscriptPos + tokenMin++; + #end case 10: line++; // LF + #if hscriptPos + tokenMin++; + #end case 48,49,50,51,52,53,54,55,56,57: // 0...9 var n = (char - 48) * 1.0; var exp = 0; @@ -693,16 +813,16 @@ class Parser { if( exp > 0 ) { // in case of '...' if( exp == 10 && readChar() == 46 ) { - tokens.add(TOp("...")); + push(TOp("...")); var i = Std.int(n); return TConst( (i == n) ? CInt(i) : CFloat(n) ); } - throw Error.EInvalidChar(char); + invalidChar(char); } exp = 1; case 120: // x if( n > 0 || exp > 0 ) - throw Error.EInvalidChar(char); + invalidChar(char); // read hexa var n = haxe.Int32.ofInt(0); while( true ) { @@ -753,7 +873,7 @@ class Parser { case 46: char = readChar(); if( char != 46 ) - throw Error.EInvalidChar(char); + invalidChar(char); return TOp("..."); default: this.char = char; @@ -792,7 +912,7 @@ class Parser { id += String.fromCharCode(char); } } - throw Error.EInvalidChar(char); + invalidChar(char); } char = readChar(); } @@ -804,8 +924,10 @@ class Parser { var s = input; if( c == 47 ) { // comment try { - while( char != 10 && char != 13 ) + while( char != 10 && char != 13 ) { + incPos(); char = s.readByte(); + } this.char = char; } catch( e : Dynamic ) { } @@ -817,15 +939,17 @@ class Parser { while( true ) { while( char != 42 ) { if( char == 10 ) line++; + incPos(); char = s.readByte(); } + incPos(); char = s.readByte(); if( char == 47 ) break; } } catch( e : Dynamic ) { line = old; - throw Error.EUnterminatedComment; + error(EUnterminatedComment, tokenMin, tokenMin); } return token(); }