package webidl; import webidl.Data; class Generate { static var HEADER_EMSCRIPTEN = " #include #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 #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 struct pref { void *finalize; T *value; }; #define _ref(t) pref #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 void free_ref( pref *r ) { if( !r->finalize ) hl_error(\"delete() is not allowed on const value.\"); delete r->value; r->value = NULL; r->finalize = NULL; } template pref *_alloc_ref( T *value, void (*finalize)( pref * ) ) { pref *r = (pref*)hl_gc_alloc_finalizer(sizeof(pref)); r->finalize = finalize; r->value = value; return r; } template pref *_alloc_const( const T *value ) { pref *r = (pref*)hl_gc_alloc_noptr(sizeof(pref)); 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 ) { 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, ?params : Array ) { 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); } }