forked from LeenkxTeam/LNXSDK
		
	
		
			
	
	
		
			460 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			460 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
|  | package webidl; | ||
|  | 
 | ||
|  | #if macro | ||
|  | import webidl.Data; | ||
|  | import haxe.macro.Context; | ||
|  | import haxe.macro.Expr; | ||
|  | 
 | ||
|  | class Module { | ||
|  | 	var p : Position; | ||
|  | 	var hl : Bool; | ||
|  | 	var pack : Array<String>; | ||
|  | 	var opts : Options; | ||
|  | 	var types : Array<TypeDefinition> = []; | ||
|  | 
 | ||
|  | 	function new(p, pack, hl, opts) { | ||
|  | 		this.p = p; | ||
|  | 		this.pack = pack; | ||
|  | 		this.hl = hl; | ||
|  | 		this.opts = opts; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function makeName( name : String ) { | ||
|  | 		if( opts.chopPrefix != null && StringTools.startsWith(name, opts.chopPrefix) ) name = name.substr(opts.chopPrefix.length); | ||
|  | 		return capitalize(name); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function buildModule( decls : Array<Definition> ) { | ||
|  | 		for( d in decls ) | ||
|  | 			buildDecl(d); | ||
|  | 		return types; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function makeType( t : TypeAttr ) : ComplexType { | ||
|  | 		return switch( t.t ) { | ||
|  | 		case TVoid: macro : Void; | ||
|  | 		case TInt: macro : Int; | ||
|  | 		case TShort: hl ? macro : hl.UI16 : macro : Int; | ||
|  | 		case TFloat: hl ? macro : Single : macro : Float; | ||
|  | 		case TDouble: macro : Float; | ||
|  | 		case TBool: macro : Bool; | ||
|  | 		case TAny: macro : webidl.Types.Any; | ||
|  | 		case TArray(t): | ||
|  | 			var tt = makeType({ t : t, attr : [] }); | ||
|  | 			macro : webidl.Types.NativePtr<$tt>; | ||
|  | 		case TVoidPtr: macro : webidl.Types.VoidPtr; | ||
|  | 		case TCustom(id): TPath({ pack : [], name : makeName(id) }); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function defVal( t : TypeAttr ) : Expr { | ||
|  | 		return switch( t.t ) { | ||
|  | 		case TVoid: throw "assert"; | ||
|  | 		case TInt, TShort: { expr : EConst(CInt("0")), pos : p }; | ||
|  | 		case TFloat, TDouble: { expr : EConst(CFloat("0.")), pos : p }; | ||
|  | 		case TBool: { expr : EConst(CIdent("false")), pos : p }; | ||
|  | 		default: { expr : EConst(CIdent("null")), pos : p }; | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function makeNative( name : String ) : MetadataEntry { | ||
|  | 		return { name : ":hlNative", params : [{ expr : EConst(CString(opts.nativeLib)), pos : p },{ expr : EConst(CString(name)), pos : p }], pos : p }; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function makeEither( arr : Array<ComplexType> ) { | ||
|  | 		var i = 0; | ||
|  | 		var t = arr[i++]; | ||
|  | 		while( i < arr.length ) { | ||
|  | 			var t2 = arr[i++]; | ||
|  | 			t = TPath({ pack : ["haxe", "extern"], name : "EitherType", params : [TPType(t), TPType(t2)] }); | ||
|  | 		} | ||
|  | 		return t; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function makePosition( pos : webidl.Data.Position ) { | ||
|  | 		if( pos == null ) | ||
|  | 			return p; | ||
|  | 		return Context.makePosition({ min : pos.pos, max : pos.pos + 1, file : pos.file }); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function makeNativeField( iname : String, f : webidl.Data.Field, args : Array<FArg>, ret : TypeAttr, pub : Bool ) : Field { | ||
|  | 		var name = f.name; | ||
|  | 		var isConstr = name == iname; | ||
|  | 		if( isConstr ) { | ||
|  | 			name = "new"; | ||
|  | 			ret = { t : TCustom(iname), attr : [] }; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		var expr = if( ret.t == TVoid ) | ||
|  | 			{ expr : EBlock([]), pos : p }; | ||
|  | 		else | ||
|  | 			{ expr : EReturn(defVal(ret)), pos : p }; | ||
|  | 
 | ||
|  | 		var access : Array<Access> = []; | ||
|  | 		if( !hl ) access.push(AInline); | ||
|  | 		if( pub ) access.push(APublic); | ||
|  | 		if( isConstr ) access.push(AStatic); | ||
|  | 
 | ||
|  | 		return { | ||
|  | 			pos : makePosition(f.pos), | ||
|  | 			name : pub ? name : name + args.length, | ||
|  | 			meta : [makeNative(iname+"_" + name + (name == "delete" ? "" : ""+args.length))], | ||
|  | 			access : access, | ||
|  | 			kind : FFun({ | ||
|  | 				ret : makeType(ret), | ||
|  | 				expr : expr, | ||
|  | 				args : [for( a in args ) { name : a.name, opt : a.opt, type : makeType(a.t) }], | ||
|  | 			}), | ||
|  | 		}; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function buildDecl( d : Definition ) { | ||
|  | 		var p = makePosition(d.pos); | ||
|  | 		switch( d.kind ) { | ||
|  | 		case DInterface(iname, attrs, fields): | ||
|  | 			var dfields : Array<Field> = []; | ||
|  | 
 | ||
|  | 			var variants = new Map(); | ||
|  | 			function getVariants( name : String ) { | ||
|  | 				if( variants.exists(name) ) | ||
|  | 					return null; | ||
|  | 				variants.set(name, true); | ||
|  | 				var fl = []; | ||
|  | 				for( f in fields ) | ||
|  | 					if( f.name == name ) | ||
|  | 						switch( f.kind ) { | ||
|  | 						case FMethod(args, ret): | ||
|  | 							fl.push({args:args, ret:ret,pos:f.pos}); | ||
|  | 						default: | ||
|  | 						} | ||
|  | 				return fl; | ||
|  | 			} | ||
|  | 
 | ||
|  | 
 | ||
|  | 			for( f in fields ) { | ||
|  | 				switch( f.kind ) { | ||
|  | 				case FMethod(_): | ||
|  | 
 | ||
|  | 					var vars = getVariants(f.name); | ||
|  | 					if( vars == null ) continue; | ||
|  | 
 | ||
|  | 					var isConstr = f.name == iname; | ||
|  | 
 | ||
|  | 					if( vars.length == 1 && !isConstr ) { | ||
|  | 
 | ||
|  | 						var f = makeNativeField(iname, f, vars[0].args, vars[0].ret, true); | ||
|  | 						dfields.push(f); | ||
|  | 
 | ||
|  | 
 | ||
|  | 					} else { | ||
|  | 
 | ||
|  | 						// create dispatching code | ||
|  | 						var maxArgs = 0; | ||
|  | 						for( v in vars ) if( v.args.length > maxArgs ) maxArgs = v.args.length; | ||
|  | 
 | ||
|  | 						if( vars.length > 1 && maxArgs == 0 ) | ||
|  | 							Context.error("Duplicate method declaration", makePosition(vars.pop().pos)); | ||
|  | 
 | ||
|  | 						var targs : Array<FunctionArg> = []; | ||
|  | 						var argsTypes = []; | ||
|  | 						for( i in 0...maxArgs ) { | ||
|  | 							var types : Array<{t:TypeAttr,sign:String}>= []; | ||
|  | 							var names = []; | ||
|  | 							var opt = false; | ||
|  | 							for( v in vars ) { | ||
|  | 								var a = v.args[i]; | ||
|  | 								if( a == null ) { | ||
|  | 									opt = true; | ||
|  | 									continue; | ||
|  | 								} | ||
|  | 								var sign = haxe.Serializer.run(a.t); | ||
|  | 								var found = false; | ||
|  | 								for( t in types ) | ||
|  | 									if( t.sign == sign ) { | ||
|  | 										found = true; | ||
|  | 										break; | ||
|  | 									} | ||
|  | 								if( !found ) types.push({ t : a.t, sign : sign }); | ||
|  | 								if( names.indexOf(a.name) < 0 ) | ||
|  | 									names.push(a.name); | ||
|  | 								if( a.opt ) | ||
|  | 									opt = true; | ||
|  | 							} | ||
|  | 							argsTypes.push(types); | ||
|  | 							targs.push({ | ||
|  | 								name : names.join("_"), | ||
|  | 								opt : opt, | ||
|  | 								type : makeEither([for( t in types ) makeType(t.t)]), | ||
|  | 							}); | ||
|  | 						} | ||
|  | 
 | ||
|  | 						// native impls | ||
|  | 						var retTypes : Array<{t:TypeAttr,sign:String}> = []; | ||
|  | 						for( v in vars ) { | ||
|  | 							var f = makeNativeField(iname, f, v.args, v.ret, false ); | ||
|  | 
 | ||
|  | 							var sign = haxe.Serializer.run(v.ret); | ||
|  | 							var found = false; | ||
|  | 							for( t in retTypes ) | ||
|  | 								if( t.sign == sign ) { | ||
|  | 									found = true; | ||
|  | 									break; | ||
|  | 								} | ||
|  | 							if( !found ) retTypes.push({ t : v.ret, sign : sign }); | ||
|  | 
 | ||
|  | 							dfields.push(f); | ||
|  | 						} | ||
|  | 
 | ||
|  | 						vars.sort(function(v1, v2) return v1.args.length - v2.args.length); | ||
|  | 
 | ||
|  | 						// dispatch only on args count | ||
|  | 						function makeCall( v : { args : Array<FArg>, ret : TypeAttr } ) : Expr { | ||
|  | 							var ident = { expr : EConst(CIdent( (f.name == iname ? "new" : f.name) + v.args.length )), pos : p}; | ||
|  | 							var e : Expr = { expr : ECall(ident, [for( i in 0...v.args.length ) { expr : ECast({ expr : EConst(CIdent(targs[i].name)), pos : p }, null), pos : p }]), pos : p }; | ||
|  | 							if( v.ret.t != TVoid ) | ||
|  | 								e = { expr : EReturn(e), pos : p }; | ||
|  | 							else if( isConstr ) | ||
|  | 								e = macro this = $e; | ||
|  | 							return e; | ||
|  | 						} | ||
|  | 
 | ||
|  | 						var expr = makeCall(vars[vars.length - 1]); | ||
|  | 						for( i in 1...vars.length ) { | ||
|  | 							var v = vars[vars.length - 1 - i]; | ||
|  | 							var aname = targs[v.args.length].name; | ||
|  | 							var call = makeCall(v); | ||
|  | 							expr = macro if( $i{aname} == null ) $call else $expr; | ||
|  | 						} | ||
|  | 
 | ||
|  | 						dfields.push({ | ||
|  | 							name : isConstr ? "new" : f.name, | ||
|  | 							pos : makePosition(f.pos), | ||
|  | 							access : [APublic, AInline], | ||
|  | 							kind : FFun({ | ||
|  | 								expr : expr, | ||
|  | 								args : targs, | ||
|  | 								ret : makeEither([for( t in retTypes ) makeType(t.t)]), | ||
|  | 							}), | ||
|  | 						}); | ||
|  | 
 | ||
|  | 
 | ||
|  | 					} | ||
|  | 
 | ||
|  | 
 | ||
|  | 				case FAttribute(t): | ||
|  | 					var tt = makeType(t); | ||
|  | 					dfields.push({ | ||
|  | 						pos : p, | ||
|  | 						name : f.name, | ||
|  | 						kind : FProp("get", "set", tt), | ||
|  | 						access : [APublic], | ||
|  | 					}); | ||
|  | 					dfields.push({ | ||
|  | 						pos : p, | ||
|  | 						name : "get_" + f.name, | ||
|  | 						meta : [makeNative(iname+"_get_" + f.name)], | ||
|  | 						kind : FFun({ | ||
|  | 							ret : tt, | ||
|  | 							expr : macro return ${defVal(t)}, | ||
|  | 							args : [], | ||
|  | 						}), | ||
|  | 					}); | ||
|  | 					dfields.push({ | ||
|  | 						pos : p, | ||
|  | 						name : "set_" + f.name, | ||
|  | 						meta : [makeNative(iname+"_set_" + f.name)], | ||
|  | 						kind : FFun({ | ||
|  | 							ret : tt, | ||
|  | 							expr : macro return ${defVal(t)}, | ||
|  | 							args : [{ name : "_v", type : tt }], | ||
|  | 						}), | ||
|  | 					}); | ||
|  | 				case DConst(name, type, value): | ||
|  | 					dfields.push({ | ||
|  | 						pos : p, | ||
|  | 						name : name, | ||
|  | 						access : [APublic, AStatic, AInline], | ||
|  | 						kind : FVar( | ||
|  | 							makeType({t : type, attr : []}), | ||
|  | 							macro $i{value} | ||
|  | 						) | ||
|  | 					}); | ||
|  | 				} | ||
|  | 			} | ||
|  | 			var td : TypeDefinition = { | ||
|  | 				pos : p, | ||
|  | 				pack : pack, | ||
|  | 				name : makeName(iname), | ||
|  | 				meta : [], | ||
|  | 				kind : TDAbstract(macro : webidl.Types.Ref, [], [macro : webidl.Types.Ref]), | ||
|  | 				fields : dfields, | ||
|  | 			}; | ||
|  | 
 | ||
|  | 			if( attrs.indexOf(ANoDelete) < 0 ) | ||
|  | 				dfields.push(makeNativeField(iname, { name : "delete", pos : null, kind : null }, [], { t : TVoid, attr : [] }, true)); | ||
|  | 
 | ||
|  | 			if( !hl ) { | ||
|  | 				for( f in dfields ) | ||
|  | 					if( f.meta != null ) | ||
|  | 						for( m in f.meta ) | ||
|  | 							if( m.name == ":hlNative" ) { | ||
|  | 								if( f.access == null ) f.access = []; | ||
|  | 								switch( f.kind ) { | ||
|  | 								case FFun(df): | ||
|  | 									var call = opts.nativeLib + "._eb_" + switch( m.params[1].expr ) { case EConst(CString(name)): name; default: throw "!"; }; | ||
|  | 									var args : Array<Expr> = [for( a in df.args ) { expr : EConst(CIdent(a.name)), pos : p }]; | ||
|  | 									if( f.access.indexOf(AStatic) < 0 ) | ||
|  | 										args.unshift(macro this); | ||
|  | 									df.expr = macro return untyped $i{call}($a{args}); | ||
|  | 								default: throw "assert"; | ||
|  | 								} | ||
|  | 								if (f.access.indexOf(AInline) == -1) f.access.push(AInline); | ||
|  | 								f.meta.remove(m); | ||
|  | 								break; | ||
|  | 							} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			types.push(td); | ||
|  | 		case DImplements(name,intf): | ||
|  | 			var name = makeName(name); | ||
|  | 			var intf = makeName(intf); | ||
|  | 			var found = false; | ||
|  | 			for( t in types ) | ||
|  | 				if( t.name == name ) { | ||
|  | 					found = true; | ||
|  | 					switch( t.kind ) { | ||
|  | 					case TDAbstract(a, _): | ||
|  | 						t.fields.push({ | ||
|  | 							pos : p, | ||
|  | 							name : "_to" + intf, | ||
|  | 							meta : [{ name : ":to", pos : p }], | ||
|  | 							access : [AInline], | ||
|  | 							kind : FFun({ | ||
|  | 								args : [], | ||
|  | 								expr : macro return cast this, | ||
|  | 								ret : TPath({ pack : [], name : intf }), | ||
|  | 							}), | ||
|  | 						}); | ||
|  | 
 | ||
|  | 						var toImpl = [intf]; | ||
|  | 						while( toImpl.length > 0 ) { | ||
|  | 							var intf = toImpl.pop(); | ||
|  | 							var td = null; | ||
|  | 							for( t2 in types ) { | ||
|  | 								if( t2.name == intf ) | ||
|  | 									switch( t2.kind ) { | ||
|  | 									case TDAbstract(a2, _, to): | ||
|  | 										for ( inheritedField in t2.fields ) { | ||
|  | 											// Search for existing field | ||
|  | 											var fieldExists = false; | ||
|  | 											for ( existingField in t.fields ) { | ||
|  | 												if ( inheritedField.name == existingField.name ) { | ||
|  | 													fieldExists = true; | ||
|  | 													break; | ||
|  | 												} | ||
|  | 											} | ||
|  | 
 | ||
|  | 											if ( !fieldExists ) { | ||
|  | 												t.fields.push(inheritedField); | ||
|  | 											} | ||
|  | 										} | ||
|  | 									default: | ||
|  | 									} | ||
|  | 							} | ||
|  | 						} | ||
|  | 
 | ||
|  | 
 | ||
|  | 					default: | ||
|  | 						Context.warning("Cannot have " + name+" extends " + intf, p); | ||
|  | 					} | ||
|  | 					break; | ||
|  | 				} | ||
|  | 			if( !found ) | ||
|  | 				Context.warning("Class " + name+" not found for implements " + intf, p); | ||
|  | 		case DEnum(name, values): | ||
|  | 			var index = 0; | ||
|  | 			types.push({ | ||
|  | 				pos : p, | ||
|  | 				pack : pack, | ||
|  | 				name : makeName(name), | ||
|  | 				meta : [{ name : ":enum", pos : p }], | ||
|  | 				kind : TDAbstract(macro : Int), | ||
|  | 				fields : [for( v in values ) { pos : p, name : v, kind : FVar(null,{ expr : EConst(CInt(""+(index++))), pos : p }) }], | ||
|  | 			}); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function buildTypes( opts : Options, hl : Bool = false ):Array<TypeDefinition> { | ||
|  | 		var p = Context.currentPos(); | ||
|  | 		if( opts.nativeLib == null ) { | ||
|  | 			Context.error("Missing nativeLib option for HL", p); | ||
|  | 			return null; | ||
|  | 		} | ||
|  | 		// load IDL | ||
|  | 		var file = opts.idlFile; | ||
|  | 		var content = try { | ||
|  | 			file = Context.resolvePath(file); | ||
|  | 			sys.io.File.getBytes(file); | ||
|  | 		} catch( e : Dynamic ) { | ||
|  | 			Context.error("" + e, p); | ||
|  | 			return null; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// parse IDL | ||
|  | 		var parse = new webidl.Parser(); | ||
|  | 		var decls = null; | ||
|  | 		try { | ||
|  | 			decls = parse.parseFile(file,new haxe.io.BytesInput(content)); | ||
|  | 		} catch( msg : String ) { | ||
|  | 			var lines = content.toString().split("\n"); | ||
|  | 			var start = lines.slice(0, parse.line-1).join("\n").length + 1; | ||
|  | 			Context.error(msg, Context.makePosition({ min : start, max : start + lines[parse.line-1].length, file : file })); | ||
|  | 			return null; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		var module = Context.getLocalModule(); | ||
|  | 		var pack = module.split("."); | ||
|  | 		pack.pop(); | ||
|  | 		return new Module(p, pack, hl, opts).buildModule(decls); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function build( opts : Options ) { | ||
|  | 		var file = opts.idlFile; | ||
|  | 		var module = Context.getLocalModule(); | ||
|  | 		var types = buildTypes(opts, Context.defined("hl")); | ||
|  | 		if (types == null) return macro : Void; | ||
|  | 
 | ||
|  | 		// Add an init function for initializing the JS module | ||
|  | 		if (Context.defined("js")) { | ||
|  | 			types.push(macro class Init { | ||
|  | 				public static function init(onReady:Void->Void) { | ||
|  | 					untyped __js__('${opts.nativeLib} = ${opts.nativeLib}().then(onReady)'); | ||
|  | 				} | ||
|  | 			}); | ||
|  | 
 | ||
|  | 		// For HL no initialization is required so execute the callback immediately | ||
|  | 		} else if (Context.defined("hl")) { | ||
|  | 			types.push(macro class Init { | ||
|  | 				public static function init(onReady:Void->Void) { | ||
|  | 					onReady(); | ||
|  | 				} | ||
|  | 			}); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		Context.defineModule(module, types); | ||
|  | 		Context.registerModuleDependency(module, file); | ||
|  | 
 | ||
|  | 		return macro : Void; | ||
|  | 	} | ||
|  | 
 | ||
|  |     /** | ||
|  | 	 * Capitalize the first letter of a string | ||
|  | 	 * @param text The string to capitalize | ||
|  | 	 */ | ||
|  | 	private static function capitalize(text:String) { | ||
|  | 		return text.charAt(0).toUpperCase() + text.substring(1); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | #end |