453 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			453 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
|  | package webidl; | ||
|  | import webidl.Data; | ||
|  | 
 | ||
|  | class Generate { | ||
|  | 
 | ||
|  | 	static var HEADER_EMSCRIPTEN = "
 | ||
|  | 
 | ||
|  | #include <emscripten.h> | ||
|  | #define HL_PRIM | ||
|  | #define HL_NAME(n)	EMSCRIPTEN_KEEPALIVE eb_##n | ||
|  | #define DEFINE_PRIM(ret, name, args) | ||
|  | #define _OPT(t) t* | ||
|  | #define _GET_OPT(value,t) *value | ||
|  | 
 | ||
|  | 
 | ||
|  | ";
 | ||
|  | 
 | ||
|  | 	static var HEADER_HL = "
 | ||
|  | 
 | ||
|  | #include <hl.h> | ||
|  | #define _IDL _BYTES | ||
|  | #define _OPT(t) vdynamic * | ||
|  | #define _GET_OPT(value,t) (value)->v.t | ||
|  | 
 | ||
|  | 
 | ||
|  | ";
 | ||
|  | 
 | ||
|  | 	static var HEADER_NO_GC = "
 | ||
|  | 
 | ||
|  | #define alloc_ref(r, _) r | ||
|  | #define alloc_ref_const(r,_) r | ||
|  | #define _ref(t)			t | ||
|  | #define _unref(v)		v | ||
|  | #define free_ref(v) delete (v) | ||
|  | #define HL_CONST const | ||
|  | 
 | ||
|  | 	";
 | ||
|  | 
 | ||
|  | 	static var HEADER_GC = "
 | ||
|  | 
 | ||
|  | template <typename T> struct pref { | ||
|  | 	void *finalize; | ||
|  | 	T *value; | ||
|  | }; | ||
|  | 
 | ||
|  | #define _ref(t) pref<t> | ||
|  | #define _unref(v) v->value | ||
|  | #define alloc_ref(r,t) _alloc_ref(r,finalize_##t) | ||
|  | #define alloc_ref_const(r, _) _alloc_const(r) | ||
|  | #define HL_CONST | ||
|  | 
 | ||
|  | template<typename T> void free_ref( pref<T> *r ) { | ||
|  | 	if( !r->finalize ) hl_error(\"delete() is not allowed on const value.\");
 | ||
|  | 	delete r->value; | ||
|  | 	r->value = NULL; | ||
|  | 	r->finalize = NULL; | ||
|  | } | ||
|  | 
 | ||
|  | template<typename T> pref<T> *_alloc_ref( T *value, void (*finalize)( pref<T> * ) ) { | ||
|  | 	pref<T> *r = (pref<T>*)hl_gc_alloc_finalizer(sizeof(pref<T>)); | ||
|  | 	r->finalize = finalize; | ||
|  | 	r->value = value; | ||
|  | 	return r; | ||
|  | } | ||
|  | 
 | ||
|  | template<typename T> pref<T> *_alloc_const( const T *value ) { | ||
|  | 	pref<T> *r = (pref<T>*)hl_gc_alloc_noptr(sizeof(pref<T>)); | ||
|  | 	r->finalize = NULL; | ||
|  | 	r->value = (T*)value; | ||
|  | 	return r; | ||
|  | } | ||
|  | 
 | ||
|  | ";
 | ||
|  | 
 | ||
|  | 	static function initOpts( opts : Options ) { | ||
|  | 		if( opts.outputDir == null ) | ||
|  | 			opts.outputDir = ""; | ||
|  | 		else if( !StringTools.endsWith(opts.outputDir,"/") ) | ||
|  | 			opts.outputDir += "/"; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function generateCpp( opts : Options ) { | ||
|  | 
 | ||
|  | 		initOpts(opts); | ||
|  | 
 | ||
|  | 		var file = opts.idlFile; | ||
|  | 		var content = sys.io.File.getBytes(file); | ||
|  | 		var parse = new webidl.Parser(); | ||
|  | 		var decls = null; | ||
|  | 		var gc = opts.autoGC; | ||
|  | 		try { | ||
|  | 			decls = parse.parseFile(file, new haxe.io.BytesInput(content)); | ||
|  | 		} catch( msg : String ) { | ||
|  | 			throw msg + "(" + file+" line " + parse.line+")"; | ||
|  | 		} | ||
|  | 		var output = new StringBuf(); | ||
|  | 		function add(str:String) { | ||
|  | 			output.add(str.split("\r\n").join("\n") + "\n"); | ||
|  | 		} | ||
|  | 		add("#ifdef EMSCRIPTEN"); | ||
|  | 		add(""); | ||
|  | 		add(StringTools.trim(HEADER_EMSCRIPTEN)); | ||
|  | 		add(StringTools.trim(HEADER_NO_GC)); | ||
|  | 		add(""); | ||
|  | 		add("#else"); | ||
|  | 		add(""); | ||
|  | 		add('#define HL_NAME(x) ${opts.nativeLib}_##x'); | ||
|  | 		add(StringTools.trim(HEADER_HL)); | ||
|  | 		add(StringTools.trim(gc ? HEADER_GC : HEADER_NO_GC)); | ||
|  | 		add(""); | ||
|  | 		add("#endif"); | ||
|  | 		if( opts.includeCode != null ) { | ||
|  | 			add(""); | ||
|  | 			add(StringTools.trim(opts.includeCode)); | ||
|  | 		} | ||
|  | 		add(""); | ||
|  | 		add('extern "C" {'); | ||
|  | 		add(""); | ||
|  | 
 | ||
|  | 		var typeNames = new Map(); | ||
|  | 		var enumNames = new Map(); | ||
|  | 
 | ||
|  | 		// ignore "JSImplementation" interfaces (?) | ||
|  | 		for( d in decls.copy() ) | ||
|  | 			switch( d.kind ) { | ||
|  | 			case DInterface(_, attrs, _): | ||
|  | 				for( a in attrs ) | ||
|  | 					switch( a ) { | ||
|  | 					case AJSImplementation(_): | ||
|  | 						decls.remove(d); | ||
|  | 						break; | ||
|  | 					default: | ||
|  | 					} | ||
|  | 			default: | ||
|  | 			} | ||
|  | 
 | ||
|  | 		for( d in decls ) { | ||
|  | 			switch( d.kind ) { | ||
|  | 			case DInterface(name, attrs, _): | ||
|  | 				var prefix = ""; | ||
|  | 				for( a in attrs ) | ||
|  | 					switch( a ) { | ||
|  | 					case APrefix(name): prefix = name; | ||
|  | 					default: | ||
|  | 					} | ||
|  | 				var fullName = "_ref(" + prefix + name+")*"; | ||
|  | 				typeNames.set(name, { full : fullName, constructor : prefix + name }); | ||
|  | 				if( attrs.indexOf(ANoDelete) >= 0 ) | ||
|  | 					continue; | ||
|  | 				add('static void finalize_$name( $fullName _this ) { free_ref(_this); }'); | ||
|  | 				add('HL_PRIM void HL_NAME(${name}_delete)( $fullName _this ) {\n\tfree_ref(_this);\n}'); | ||
|  | 				add('DEFINE_PRIM(_VOID, ${name}_delete, _IDL);'); | ||
|  | 			case DEnum(name, values): | ||
|  | 				enumNames.set(name, true); | ||
|  | 				typeNames.set(name, { full : "int", constructor : null }); | ||
|  | 				add('static $name ${name}__values[] = { ${values.join(",")} };'); | ||
|  | 			case DImplements(_): | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		function getEnumName( t : webidl.Data.Type ) { | ||
|  | 			return switch( t ) { | ||
|  | 			case TCustom(id): enumNames.exists(id) ? id : null; | ||
|  | 			default: null; | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		function makeType( t : webidl.Data.Type ) { | ||
|  | 			return switch( t ) { | ||
|  | 			case TFloat: "float"; | ||
|  | 			case TDouble: "double"; | ||
|  | 			case TShort: "short"; | ||
|  | 			case TInt: "int"; | ||
|  | 			case TVoid: "void"; | ||
|  | 			case TAny, TVoidPtr: "void*"; | ||
|  | 			case TArray(t): makeType(t) + "[]"; | ||
|  | 			case TBool: "bool"; | ||
|  | 			case TCustom(id): typeNames.get(id).full; | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		function defType( t ) { | ||
|  | 			return switch( t ) { | ||
|  | 			case TFloat: "_F32"; | ||
|  | 			case TDouble: "_F64"; | ||
|  | 			case TShort: "_I16"; | ||
|  | 			case TInt: "_I32"; | ||
|  | 			case TVoid: "_VOID"; | ||
|  | 			case TAny, TVoidPtr: "_BYTES"; | ||
|  | 			case TArray(t): "_BYTES"; | ||
|  | 			case TBool: "_BOOL"; | ||
|  | 			case TCustom(name): enumNames.exists(name) ? "_I32" : "_IDL"; | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		function dynamicAccess(t) { | ||
|  | 			return switch( t ) { | ||
|  | 			case TFloat: "f"; | ||
|  | 			case TDouble: "d"; | ||
|  | 			case TShort: "ui16"; | ||
|  | 			case TInt: "i"; | ||
|  | 			case TBool: "b"; | ||
|  | 			default: throw "assert"; | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		function makeTypeDecl( td : TypeAttr ) { | ||
|  | 			var prefix = ""; | ||
|  | 			for( a in td.attr ) { | ||
|  | 				switch( a ) { | ||
|  | 				case AConst: prefix += "HL_CONST "; | ||
|  | 				default: | ||
|  | 				} | ||
|  | 			} | ||
|  | 			return prefix + makeType(td.t); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		function isDyn( arg : { opt : Bool, t : TypeAttr } ) { | ||
|  | 			return arg.opt && !arg.t.t.match(TCustom(_)); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		for( d in decls ) { | ||
|  | 			switch( d.kind ) { | ||
|  | 			case DInterface(name, attrs, fields): | ||
|  | 				for( f in fields ) { | ||
|  | 					switch( f.kind ) { | ||
|  | 					case FMethod(margs, ret): | ||
|  | 						var isConstr = f.name == name; | ||
|  | 						var args = isConstr ? margs : [{ name : "_this", t : { t : TCustom(name), attr : [] }, opt : false }].concat(margs); | ||
|  | 						var tret = isConstr ? { t : TCustom(name), attr : [] } : ret; | ||
|  | 						var funName = name + "_" + (isConstr ? "new" + args.length : f.name + (args.length - 1)); | ||
|  | 						output.add('HL_PRIM ${makeTypeDecl(tret)} HL_NAME($funName)('); | ||
|  | 						var first = true; | ||
|  | 						for( a in args ) { | ||
|  | 							if( first ) first = false else output.add(", "); | ||
|  | 							switch( a.t.t ) { | ||
|  | 							case TArray(t): | ||
|  | 								output.add(makeType(t) + "*"); | ||
|  | 							default: | ||
|  | 								if( isDyn(a) ) | ||
|  | 									output.add("_OPT("+makeType(a.t.t)+")"); | ||
|  | 								else | ||
|  | 									output.add(makeType(a.t.t)); | ||
|  | 							} | ||
|  | 							output.add(" " + a.name); | ||
|  | 						} | ||
|  | 						add(') {'); | ||
|  | 
 | ||
|  | 
 | ||
|  | 						function addCall(margs : Array<{ name : String, opt : Bool, t : TypeAttr }> ) { | ||
|  | 							var refRet = null; | ||
|  | 							var enumName = getEnumName(tret.t); | ||
|  | 							if( isConstr ) { | ||
|  | 								refRet = name; | ||
|  | 								output.add('return alloc_ref((new ${typeNames.get(refRet).constructor}('); | ||
|  | 							} else { | ||
|  | 								if( tret.t != TVoid ) output.add("return "); | ||
|  | 								for( a in ret.attr ) { | ||
|  | 									switch( a ) { | ||
|  | 									case ARef, AValue: | ||
|  | 										refRet = switch(tret.t) { | ||
|  | 										case TCustom(id): id; | ||
|  | 										default: throw "assert"; | ||
|  | 										} | ||
|  | 										if( a == ARef && tret.attr.indexOf(AConst) >= 0 ) | ||
|  | 											output.add('alloc_ref_const(&('); // we shouldn't call delete() on this one ! | ||
|  | 										else | ||
|  | 											output.add('alloc_ref(new ${typeNames.get(refRet).constructor}('); | ||
|  | 									default: | ||
|  | 									} | ||
|  | 								} | ||
|  | 								if( enumName != null ) | ||
|  | 									output.add('make__$enumName('); | ||
|  | 								else if( refRet == null && ret.t.match(TCustom(_)) ) { | ||
|  | 									refRet = switch(tret.t) { | ||
|  | 									case TCustom(id): id; | ||
|  | 									default: throw "assert"; | ||
|  | 									} | ||
|  | 									if( tret.attr.indexOf(AConst) >= 0 ) | ||
|  | 										output.add('alloc_ref_const(('); | ||
|  | 									else | ||
|  | 										output.add('alloc_ref(('); | ||
|  | 								} | ||
|  | 
 | ||
|  | 								switch( f.name ) { | ||
|  | 								case "op_mul": | ||
|  | 									output.add("*_unref(_this) * ("); | ||
|  | 								case "op_add": | ||
|  | 									output.add("*_unref(_this) + ("); | ||
|  | 								case "op_sub": | ||
|  | 									output.add("*_unref(_this) - ("); | ||
|  | 								case "op_div": | ||
|  | 									output.add("*_unref(_this) / ("); | ||
|  | 								case "op_mulq": | ||
|  | 									output.add("*_unref(_this) *= ("); | ||
|  | 								default: | ||
|  | 									output.add("_unref(_this)->" + f.name+"("); | ||
|  | 								} | ||
|  | 							} | ||
|  | 
 | ||
|  | 							var first = true; | ||
|  | 							for( a in margs ) { | ||
|  | 								if( first ) first = false else output.add(", "); | ||
|  | 								for( a in a.t.attr ) { | ||
|  | 									switch( a ) { | ||
|  | 									case ARef: output.add("*"); // unref | ||
|  | 									default: | ||
|  | 									} | ||
|  | 								} | ||
|  | 								var e = getEnumName(a.t.t); | ||
|  | 								if( e != null ) | ||
|  | 									output.add('${e}__values[${a.name}]'); | ||
|  | 								else switch( a.t.t ) { | ||
|  | 								case TCustom(_): | ||
|  | 									output.add('_unref(${a.name})'); | ||
|  | 								default: | ||
|  | 									if( isDyn(a) ) { | ||
|  | 										output.add("_GET_OPT("+a.name+","+dynamicAccess(a.t.t)+")"); | ||
|  | 									} else | ||
|  | 										output.add(a.name); | ||
|  | 								} | ||
|  | 							} | ||
|  | 
 | ||
|  | 							if( enumName != null ) output.add(')'); | ||
|  | 							if( refRet != null ) output.add(')),$refRet'); | ||
|  | 							add(");"); | ||
|  | 						} | ||
|  | 
 | ||
|  | 						var hasOpt = false; | ||
|  | 						for( i in 0...margs.length ) | ||
|  | 							if( margs[i].opt ) { | ||
|  | 								hasOpt = true; | ||
|  | 								break; | ||
|  | 							} | ||
|  | 						if( hasOpt ) { | ||
|  | 
 | ||
|  | 							for( i in 0...margs.length ) | ||
|  | 								if( margs[i].opt ) { | ||
|  | 									add("\tif( !" + margs[i].name+" )"); | ||
|  | 									output.add("\t\t"); | ||
|  | 									addCall(margs.slice(0, i)); | ||
|  | 									add("\telse"); | ||
|  | 								} | ||
|  | 							output.add("\t\t"); | ||
|  | 							addCall(margs); | ||
|  | 
 | ||
|  | 						} else { | ||
|  | 							output.add("\t"); | ||
|  | 							addCall(margs); | ||
|  | 						} | ||
|  | 						add('}'); | ||
|  | 						output.add('DEFINE_PRIM(${defType(tret.t)}, $funName,'); | ||
|  | 						for( a in args ) | ||
|  | 							output.add(' ' + (isDyn(a) ? "_NULL(" + defType(a.t.t)+")" : defType(a.t.t))); | ||
|  | 						add(');'); | ||
|  | 						add(''); | ||
|  | 
 | ||
|  | 
 | ||
|  | 					case FAttribute(t): | ||
|  | 						var isVal = t.attr.indexOf(AValue) >= 0; | ||
|  | 						var tname = switch( t.t ) { case TCustom(id): id; default: null; }; | ||
|  | 						var isRef = tname != null; | ||
|  | 						var enumName = getEnumName(t.t); | ||
|  | 						var isConst = t.attr.indexOf(AConst) >= 0; | ||
|  | 
 | ||
|  | 						if( enumName != null ) throw "TODO : enum attribute"; | ||
|  | 
 | ||
|  | 						add('HL_PRIM ${makeTypeDecl(t)} HL_NAME(${name}_get_${f.name})( ${typeNames.get(name).full} _this ) {'); | ||
|  | 						if( isVal ) { | ||
|  | 							var fname = typeNames.get(tname).constructor; | ||
|  | 							add('\treturn alloc_ref(new $fname(_unref(_this)->${f.name}),$tname);'); | ||
|  | 						} else if( isRef ) | ||
|  | 							add('\treturn alloc_ref${isConst?'_const':''}(_unref(_this)->${f.name},$tname);'); | ||
|  | 						else | ||
|  | 							add('\treturn _unref(_this)->${f.name};'); | ||
|  | 						add('}'); | ||
|  | 
 | ||
|  | 						add('HL_PRIM ${makeTypeDecl(t)} HL_NAME(${name}_set_${f.name})( ${typeNames.get(name).full} _this, ${makeTypeDecl(t)} value ) {'); | ||
|  | 						add('\t_unref(_this)->${f.name} = ${isVal?"*":""}${isRef?"_unref":""}(value);'); | ||
|  | 						add('\treturn value;'); | ||
|  | 						add('}'); | ||
|  | 
 | ||
|  | 						var td = defType(t.t); | ||
|  | 						add('DEFINE_PRIM($td,${name}_get_${f.name},_IDL);'); | ||
|  | 						add('DEFINE_PRIM($td,${name}_set_${f.name},_IDL $td);'); | ||
|  | 						add(''); | ||
|  | 					case DConst(_, _, _): | ||
|  | 					} | ||
|  | 				} | ||
|  | 
 | ||
|  | 			case DEnum(_), DImplements(_): | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		add("}"); // extern C | ||
|  | 
 | ||
|  | 		sys.io.File.saveContent(opts.outputDir + opts.nativeLib+".cpp", output.toString()); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	static function command( cmd, args : Array<String> ) { | ||
|  | 		Sys.println("> " + cmd + " " + args.join(" ")); | ||
|  | 		var ret = Sys.command(cmd, args); | ||
|  | 		if( ret != 0 ) throw "Command '" + cmd + "' has exit with error code " + ret; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function generateJs( opts : Options, sources : Array<String>, ?params : Array<String> ) { | ||
|  | 		if( params == null ) | ||
|  | 			params = []; | ||
|  | 
 | ||
|  | 		initOpts(opts); | ||
|  | 
 | ||
|  | 		var hasOpt = false; | ||
|  | 		for( p in params ) | ||
|  | 			if( p.substr(0, 2) == "-O" ) | ||
|  | 				hasOpt = true; | ||
|  | 		if( !hasOpt ) | ||
|  | 			params.push("-O2"); | ||
|  | 
 | ||
|  | 		var lib = opts.nativeLib; | ||
|  | 
 | ||
|  | 		var emSdk = Sys.getEnv("EMSCRIPTEN"); | ||
|  | 		if( emSdk == null ) | ||
|  | 			throw "Missing EMSCRIPTEN environment variable. Install emscripten"; | ||
|  | 		var emcc = emSdk + "/emcc"; | ||
|  | 
 | ||
|  | 		// build sources BC files | ||
|  | 		var outFiles = []; | ||
|  | 		sources.push(lib+".cpp"); | ||
|  | 		for( cfile in sources ) { | ||
|  | 			var out = opts.outputDir + cfile.substr(0, -4) + ".bc"; | ||
|  | 			var args = params.concat(["-c", cfile, "-o", out]); | ||
|  | 			command( emcc, args); | ||
|  | 			outFiles.push(out); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// link : because too many files, generate Makefile | ||
|  | 		var tmp = opts.outputDir + "Makefile.tmp"; | ||
|  | 		var args = params.concat([ | ||
|  | 			"-s", 'EXPORT_NAME="\'$lib\'"', "-s", "MODULARIZE=1", | ||
|  | 			"--memory-init-file", "0", | ||
|  | 			"-o", '$lib.js' | ||
|  | 		]); | ||
|  | 		var output = "SOURCES = " + outFiles.join(" ") + "\n"; | ||
|  | 		output += "all:\n"; | ||
|  | 		output += "\t"+emcc+" $(SOURCES) " + args.join(" "); | ||
|  | 		sys.io.File.saveContent(tmp, output); | ||
|  | 		command("make", ["-f", tmp]); | ||
|  | 		sys.FileSystem.deleteFile(tmp); | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | } |