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