package webidl; import webidl.Data; private enum Token { TEof; TId( s : String ); TPOpen; TPClose; TBrOpen; TBrClose; TBkOpen; TBkClose; TSemicolon; TComma; TOp( op : String ); TString( str : String ); } class Parser { public var line : Int; var input : haxe.io.Input; var char : Int; var ops : Array; var idents : Array; var tokens : Array; var pos = 0; var fileName : String; public function new() { var opChars = "+*/-=!><&|^%~"; var identChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"; idents = new Array(); ops = new Array(); for( i in 0...identChars.length ) idents[identChars.charCodeAt(i)] = true; for( i in 0...opChars.length ) ops[opChars.charCodeAt(i)] = true; } public function parseFile( fileName : String, input : haxe.io.Input ) { this.fileName = fileName; pos = 0; line = 1; char = -1; tokens = []; this.input = input; var out = []; while( true ) { var tk = token(); if( tk == TEof ) break; push(tk); out.push(parseDecl()); } return out; } function parseDecl() { var attr = attributes(); var pmin = this.pos; switch( token() ) { case TId("interface"): var name = ident(); ensure(TBrOpen); var fields = []; while( true ) { var tk = token(); if( tk == TBrClose ) break; push(tk); fields.push(parseField()); } ensure(TSemicolon); return { pos : makePos(pmin), kind : DInterface(name, attr, fields) }; case TId("enum"): var name = ident(); ensure(TBrOpen); var values = []; if( !maybe(TBrClose) ) while( true ) { switch( token() ) { case TString(str): values.push(str); case var tk: unexpected(tk); } switch( token() ) { case TBrClose: break; case TComma: continue; case var tk: unexpected(tk); } } ensure(TSemicolon); return { pos : makePos(pmin), kind : DEnum(name, values) }; case TId(name): if( attr.length > 0 ) throw "assert"; ensure(TId("implements")); var intf = ident(); ensure(TSemicolon); return { pos : makePos(pmin), kind : DImplements(name, intf) }; case var tk: return unexpected(tk); } } function attributes() { if( !maybe(TBkOpen) ) return []; var attrs = []; while( true ) { var attr = switch( ident() ) { case "Value": AValue; case "Ref": ARef; case "Const": AConst; case "NoDelete": ANoDelete; case "Prefix": ensure(TOp("=")); APrefix(switch( token() ) { case TString(s): s; case var tk: unexpected(tk); }); case "JSImplementation": ensure(TOp("=")); AJSImplementation(switch( token() ) { case TString(s): s; case var tk: unexpected(tk); }); case "Operator": ensure(TOp("=")); AOperator(switch( token() ) { case TString(s): s; case var tk: unexpected(tk); }); case var attr: error("Unsupported attribute " + attr); null; } attrs.push(attr); if( !maybe(TComma) ) break; } ensure(TBkClose); return attrs; } function type() : Type { var id = ident(); var t = switch( id ) { case "void": TVoid; case "float": TFloat; case "double": TDouble; case "long", "int": TInt; // long ensures 32 bits case "short": TShort; case "boolean", "bool": TBool; case "any": TAny; case "VoidPtr": TVoidPtr; default: TCustom(id); }; if( maybe(TBkOpen) ) { ensure(TBkClose); t = TArray(t); } return t; } function makePos( pmin : Int ) { return { file : fileName, line : line, pos : pmin }; } function parseField() : Field { var attr = attributes(); var pmin = this.pos; if( maybe(TId("attribute")) ) { var t = type(); var name = ident(); ensure(TSemicolon); return { name : name, kind : FAttribute({ t : t, attr : attr }), pos : makePos(pmin) }; } if( maybe(TId("const")) ) { var type = type(); var name = ident(); ensure(TOp("=")); var value = tokenString(token()); ensure(TSemicolon); return { name: name, kind : DConst(name, type, value), pos : makePos(pmin) }; } var tret = type(); var name = ident(); ensure(TPOpen); var args = []; if( !maybe(TPClose) ) { while( true ) { var attr = attributes(); var opt = maybe(TId("optional")); var t = type(); var name = ident(); args.push({ name : name, t : { t : t, attr : attr }, opt : opt }); switch( token() ) { case TPClose: break; case TComma: continue; case var tk: unexpected(tk); } } } ensure(TSemicolon); return { name : name, kind : FMethod(args, { attr : attr, t : tret }), pos : makePos(pmin) }; } // --- Lexing function invalidChar(c:Int) { error("Invalid char "+c+"("+String.fromCharCode(c)+")"); } function error( msg : String ) { throw msg+" line "+line; } function unexpected( tk ) : Dynamic { error("Unexpected " + tokenString(tk)); return null; } function tokenString( tk ) { return switch( tk ) { case TEof: ""; case TId(id): id; case TPOpen: "("; case TPClose: ")"; case TBkOpen: "["; case TBkClose: "]"; case TBrOpen: "{"; case TBrClose: "}"; case TComma: ","; case TSemicolon: ";"; case TOp(op): op; case TString(str): '"' + str + '"'; } } inline function push(tk) { tokens.push(tk); } function ensure(tk) { var t = token(); if( t != tk && !std.Type.enumEq(t,tk) ) unexpected(t); } function maybe(tk) { var t = token(); if( t == tk || std.Type.enumEq(t,tk) ) return true; push(t); return false; } function ident() { var tk = token(); switch( tk ) { case TId(id): return id; default: unexpected(tk); return null; } } function readChar() { pos++; return try input.readByte() catch( e : Dynamic ) 0; } function token() : Token { if( tokens.length > 0 ) return tokens.shift(); var char; if( this.char < 0 ) char = readChar(); else { char = this.char; this.char = -1; } 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) * 1.0; var exp = 0.; while( true ) { char = readChar(); 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 ) { // in case of '...' if( exp == 10 && readChar() == 46 ) { push(TOp("...")); var i = Std.int(n); return TConst( (i == n) ? CInt(i) : CFloat(n) ); } invalidChar(char); } exp = 1.; case 120: // x if( n > 0 || exp > 0 ) invalidChar(char); // read hexa #if haxe3 var n = 0; while( true ) { char = readChar(); switch( char ) { case 48,49,50,51,52,53,54,55,56,57: // 0-9 n = (n << 4) + char - 48; case 65,66,67,68,69,70: // A-F n = (n << 4) + (char - 55); case 97,98,99,100,101,102: // a-f n = (n << 4) + (char - 87); default: this.char = char; return TConst(CInt(n)); } } #else var n = haxe.Int32.ofInt(0); while( true ) { char = readChar(); switch( char ) { case 48,49,50,51,52,53,54,55,56,57: // 0-9 n = haxe.Int32.add(haxe.Int32.shl(n,4), cast (char - 48)); case 65,66,67,68,69,70: // A-F n = haxe.Int32.add(haxe.Int32.shl(n,4), cast (char - 55)); case 97,98,99,100,101,102: // a-f n = haxe.Int32.add(haxe.Int32.shl(n,4), cast (char - 87)); default: this.char = char; // we allow to parse hexadecimal Int32 in Neko, but when the value will be // evaluated by Interpreter, a failure will occur if no Int32 operation is // performed var v = try CInt(haxe.Int32.toInt(n)) catch( e : Dynamic ) CInt32(n); return TConst(v); } } #end default: this.char = char; var i = Std.int(n); return TConst( (exp > 0) ? CFloat(n * 10 / exp) : ((i == n) ? CInt(i) : CFloat(n)) ); } }*/ case 59: return TSemicolon; case 40: return TPOpen; case 41: return TPClose; case 44: return TComma; /* case 46: char = readChar(); switch( char ) { case 48,49,50,51,52,53,54,55,56,57: var n = char - 48; var exp = 1; while( true ) { char = readChar(); exp *= 10; switch( char ) { case 48,49,50,51,52,53,54,55,56,57: n = n * 10 + (char - 48); default: this.char = char; return TConst( CFloat(n/exp) ); } } case 46: char = readChar(); if( char != 46 ) invalidChar(char); return TOp("..."); default: this.char = char; return TDot; }*/ case 123: return TBrOpen; case 125: return TBrClose; case 91: return TBkOpen; case 93: return TBkClose; case 39: return TString(readString(39)); case 34: return TString(readString(34)); // case 63: return TQuestion; // case 58: return TDoubleDot; case '='.code: char = readChar(); if( char == '='.code ) return TOp("=="); else if ( char == '>'.code ) return TOp("=>"); this.char = char; return TOp("="); default: if( ops[char] ) { var op = String.fromCharCode(char); var prev = -1; while( true ) { char = readChar(); if( !ops[char] || prev == '='.code ) { if( op.charCodeAt(0) == '/'.code ) return tokenComment(op,char); this.char = char; return TOp(op); } prev = char; op += String.fromCharCode(char); } } if( idents[char] ) { var id = String.fromCharCode(char); while( true ) { char = readChar(); if( !idents[char] ) { this.char = char; return TId(id); } id += String.fromCharCode(char); } } invalidChar(char); } char = readChar(); } return null; } function tokenComment( op : String, char : Int ) { var c = op.charCodeAt(1); var s = input; if( c == '/'.code ) { // comment try { while( char != '\r'.code && char != '\n'.code ) { pos++; char = s.readByte(); } this.char = char; } catch( e : Dynamic ) { } return token(); } if( c == '*'.code ) { /* comment */ var old = line; if( op == "/**/" ) { this.char = char; return token(); } try { while( true ) { while( char != '*'.code ) { if( char == '\n'.code ) line++; pos++; char = s.readByte(); } pos++; char = s.readByte(); if( char == '/'.code ) break; } } catch( e : Dynamic ) { line = old; error("Unterminated comment"); } return token(); } this.char = char; return TOp(op); } function readString( until ) { var c = 0; var b = new haxe.io.BytesOutput(); var esc = false; var old = line; var s = input; while( true ) { try { pos++; c = s.readByte(); } catch( e : Dynamic ) { line = old; error("Unterminated string"); } if( esc ) { esc = false; switch( c ) { case 'n'.code: b.writeByte(10); case 'r'.code: b.writeByte(13); case 't'.code: b.writeByte(9); case "'".code, '"'.code, '\\'.code: b.writeByte(c); case '/'.code: b.writeByte(c); default: invalidChar(c); } } else if( c == 92 ) esc = true; else if( c == until ) break; else { if( c == 10 ) line++; b.writeByte(c); } } return b.getBytes().toString(); } }