2025-01-22 16:18:30 +01:00

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();
}
}