510 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
			
		
		
	
	
			510 lines
		
	
	
		
			11 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();
 | 
						|
	}
 | 
						|
 | 
						|
}
 |