forked from LeenkxTeam/LNXSDK
510 lines
12 KiB
Haxe
510 lines
12 KiB
Haxe
|
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<Bool>;
|
||
|
var idents : Array<Bool>;
|
||
|
var tokens : Array<Token>;
|
||
|
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: "<eof>";
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
}
|