Last active
August 29, 2015 14:04
-
-
Save porfirioribeiro/4202abd244e78c8217cf to your computer and use it in GitHub Desktop.
Advanced Custom JS generator.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ; | |
import haxe.macro.Compiler; | |
import haxe.macro.Context; | |
import haxe.macro.Type; | |
import haxe.macro.Expr; | |
import haxe.macro.JSGenApi; | |
using haxe.macro.Tools; | |
using Lambda; | |
using StringTools; | |
class Generator { | |
var api : JSGenApi; | |
var buf : StringBuf; | |
var inits : List<TypedExpr>; | |
var statics : List<{ c : ClassType, f : ClassField }>; | |
var packages : haxe.ds.StringMap<Bool>; | |
var forbidden : haxe.ds.StringMap<Bool>; | |
var jsModern:Bool; | |
var jsFlatten:Bool; | |
var dce:Bool; | |
var indentLevel=0; | |
var genExtend=false; | |
var genExpose=false; | |
var iRE=~/^(.*)$/gm; | |
public function new(api) { | |
this.api = api; | |
buf = new StringBuf(); | |
inits = new List(); | |
statics = new List(); | |
packages = new haxe.ds.StringMap(); | |
forbidden = new haxe.ds.StringMap(); | |
jsModern=!Context.defined("js-classic"); | |
jsFlatten=Context.defined("js-flatten"); | |
dce=Context.definedValue("dce")!="no"; | |
for( x in ["prototype", "__proto__", "constructor"] ) | |
forbidden.set(x, true); | |
api.setTypeAccessor(getType); | |
for (t in api.types){ | |
switch (t) { | |
case TInst(c,_): | |
if (!c.get().isExtern && c.get().superClass!=null) | |
genExtend=true; | |
if (c.get().meta.has(":expose")) | |
genExpose=true; | |
if (genExtend && genExpose) break; | |
case _: | |
} | |
} | |
} | |
function getType( t : Type ) { | |
return switch(t) { | |
case TInst(c, _): getPath(c.get()); | |
case TEnum(e, _): getPath(e.get()); | |
case TAbstract(a, _): getPath(a.get()); | |
default: throw "assert"; | |
}; | |
} | |
inline function indent(str:String,level=0,notFirst=false):String{ | |
var first=true; | |
var lines=str.split("\n"); | |
var nstr=""; | |
for (i in 0...lines.length){ | |
var s=lines[i]; | |
if (first && notFirst){ | |
first=false; | |
nstr+=s; | |
}else{ | |
for (i in 0...level){ | |
nstr+='\t'; | |
} | |
nstr+='$s'; | |
} | |
if (i<lines.length-1) | |
nstr+="\n"; | |
} | |
return nstr; | |
} | |
inline function print(str) { | |
printi(str,indentLevel); | |
} | |
inline function printi(str,level=0,notFirst=false) { | |
buf.add(indent(str,level,notFirst)); | |
} | |
inline function println(s:String=""){ | |
print(s); | |
newline(); | |
} | |
inline function printn(s:String="") | |
print('$s\n'); | |
inline function printin(str,level=0,notFirst=false) | |
buf.add(indent(str,level,notFirst)+"\n"); | |
function printif(f:String,s:String){ | |
if (api.hasFeature(f)) | |
println(s); | |
} | |
inline function newline() { | |
buf.add(";\n"); | |
} | |
function field(p, staticField=true) { | |
if (staticField) | |
return api.isKeyword(p) ? '["$p"]' : '.$p'; | |
else | |
return api.isKeyword(p) ? '\'$p\'' : p; | |
} | |
function genPackage( p : Array<String> ) { | |
if (p.length==0) | |
print("var "); | |
var full = null; | |
for( x in p ) { | |
var prev = full; | |
if( full == null ) full = x else full += "." + x; | |
if( packages.exists(full) ) | |
continue; | |
packages.set(full, true); | |
if( prev == null ) | |
println('var $x = '+(jsModern?"{}":'$x || {}') ); | |
else { | |
var p = prev + field(x); | |
println((jsModern?'':'if(!$p) ')+'$p = {}'); | |
} | |
} | |
} | |
function getPath( t : BaseType ) { | |
return packClass(t.pack,t.name); | |
} | |
function getDotPath( t : BaseType ) { | |
return (t.pack.length == 0) ? t.name : t.pack.join('.') + '.' + t.name; | |
} | |
function packClass(p:Array<String>,name:String){ | |
if (jsFlatten){ | |
var r=''; | |
for (i in p){ | |
r+=i.replace("_","_$")+'_'; | |
} | |
return r+name.replace("_","_$"); | |
} | |
return (p.length == 0) ? name : p.join('.') + '.' + name; | |
} | |
function checkFieldName( c : ClassType, f : ClassField ) { | |
if( forbidden.exists(f.name) ) | |
Context.error("The field " + f.name + " is not allowed in JS", c.pos); | |
} | |
function genClassField( c : ClassType, p : String, f : ClassField , first:Bool) { | |
checkFieldName(c, f); | |
var field = field(f.name,false); | |
var e=f.expr(); | |
if (e!=null){ | |
printin((first?"":",")+'$field: '+api.generateValue(e) ,indentLevel,false); | |
return false; | |
}else if (!dce && (f.kind.match(FVar(AccNormal, AccNormal) | FMethod(_)) || f.meta.has(":isVar"))){ | |
printin((first?"":",")+'$field: null' ,indentLevel,false); | |
return false; | |
} | |
return first; | |
} | |
function genStaticField( c : ClassType, p : String, f : ClassField ) { | |
checkFieldName(c, f); | |
var field = field(f.name); | |
var e = f.expr(); | |
if( e != null ) { | |
switch( f.kind ) { | |
case FMethod(_): | |
print('$p$field = '); | |
println(api.generateValue(e)); | |
default: | |
statics.add( { c : c, f : f } ); | |
} | |
} else{ | |
if (!dce && (f.kind.match(FVar(AccNormal, AccNormal) | FMethod(_)) || f.meta.has(":isVar"))) | |
println('$p$field = null'); | |
} | |
} | |
function getProperties(fields:Array<ClassField>):String{ | |
var properties=[]; | |
for (f in fields){ | |
switch (f.kind) { | |
case FVar(g,s):{ | |
if (g==AccCall) | |
properties.push('get_${f.name}:"get_${f.name}"'); | |
if (s==AccCall) | |
properties.push('set_${f.name}:"set_${f.name}"'); | |
} | |
case _: | |
} | |
} | |
return (properties.length>0)?('{'+properties.join(",")+'}'):""; | |
} | |
function genClass( c : ClassType ) { | |
api.setCurrentClass(c); | |
var hxClasses=api.hasFeature("Type.resolveClass"); | |
var p = getPath(c); | |
var pn= getDotPath(c); | |
if (jsFlatten) | |
print("var "); | |
else | |
genPackage(c.pack); | |
if (hxClasses && !jsModern) | |
print('$p = $$hxClasses["$pn"] = '); | |
else | |
print('$p = '+(c.meta.has(":expose")?'$$hx_exports.$p = ':'')); | |
if( c.constructor != null ) | |
print(api.generateValue(c.constructor.get().expr())); | |
else | |
print("function() { }"); | |
newline(); | |
if (hxClasses && jsModern) | |
println('$$hxClasses["$pn"] = $p'); | |
var name = pn.split(".").map(api.quoteString).join(","); | |
if (api.hasFeature("js.Boot.isClass")){ | |
if (api.hasFeature("Type.getClassName")) | |
println('$p.__name__ = [$name]'); | |
else | |
println('$p.__name__ = true'); | |
} | |
if( c.interfaces.length > 0 ) { | |
var me = this; | |
var inter = c.interfaces.map(function(i) return me.getPath(i.t.get())).join(","); | |
print('$p.__interfaces__ = [$inter]'); | |
newline(); | |
} | |
var has_property_reflection =api.hasFeature("Reflect.getProperty") || api.hasFeature("Reflect.setProperty"); | |
if (has_property_reflection){ | |
var staticProperties=getProperties(c.statics.get()); | |
if (staticProperties.length>0) | |
printn('$p.__properties__ = $staticProperties'); | |
} | |
for( f in c.statics.get() ) | |
genStaticField(c, p, f); | |
var has_class = api.hasFeature("js.Boot.getClass") && (c.superClass != null || c.fields.get().length > 0 || c.constructor != null); | |
var has_prototype = has_class || c.superClass != null || c.fields.get().length > 0; | |
if (has_prototype){ | |
if( c.superClass != null ) { | |
var psup = getPath(c.superClass.t.get()); | |
println('$p.__super__ = $psup'); | |
printn('$p.prototype = $$extend($psup.prototype,{'); | |
}else{ | |
printn('$p.prototype = {'); | |
} | |
indentLevel++; | |
var first=true; | |
for( f in c.fields.get() ) { | |
switch( f.kind ) { | |
case FVar(r, _): | |
if( r == AccResolve ) continue; | |
default: | |
} | |
first=genClassField(c, p, f, first); | |
} | |
if (has_class){ | |
if(!first) | |
print(","); | |
printi('__class__: $p\n'); | |
} | |
if (has_property_reflection){ | |
var properties=getProperties(c.fields.get()); | |
if (properties.length>0) | |
if( c.superClass != null ) { | |
var psup = getPath(c.superClass.t.get()); | |
printn((first?"":",")+'__properties__: $$extend($psup.prototype.__properties__,$properties)'); | |
} else | |
printn((first?"":",")+'__properties__: $properties'); | |
} | |
indentLevel--; | |
if( c.superClass != null ) | |
printin("});",0); | |
else | |
printin("};",0); | |
} | |
} | |
function genEnum( e : EnumType ) { | |
var p = getPath(e); | |
var pn= getDotPath(e); | |
var names = pn.split(".").map(api.quoteString).join(","); | |
var constructs = e.names.map(api.quoteString).join(","); | |
var hxClasses=api.hasFeature("Type.resolveEnum"); | |
if (jsFlatten) | |
print("var "); | |
else | |
genPackage(e.pack); | |
if (hxClasses) | |
print('$p = $$hxClasses["$pn"] = {'); | |
else | |
print('$p = {'); | |
if (api.hasFeature("js.Boot.isEnum")) | |
if (api.hasFeature("Type.getEnumName")) | |
print(' __ename__ : [$names],'); | |
else | |
print(' __ename__ : true,'); | |
println(' __constructs__ : [$constructs] }'); | |
for( c in e.constructs.keys() ) { | |
var c = e.constructs.get(c); | |
var f = field(c.name); | |
print('$p$f = '); | |
switch( c.type ) { | |
case TFun(args, _): | |
var sargs = args.map(function(a) return a.name).join(","); | |
print('function($sargs) { var $$x = ["${c.name}",${c.index},$sargs]; $$x.__enum__ = $p; $$x.toString = $$estr; return $$x; }'); | |
default: | |
println("[" + api.quoteString(c.name) + "," + c.index + "]"); | |
if (api.hasFeature("may_print_enum")) | |
println('$p$f.toString = $$estr'); | |
print('$p$f.__enum__ = $p'); | |
} | |
newline(); | |
} | |
if (api.hasFeature("Type.allEnums")){ | |
var ec=Lambda.fold(e.constructs, function(c:EnumField, r:Array<String>) { | |
if (!c.type.match(TFun(_,_))) | |
r.push('$p.${c.name}'); | |
return r; | |
}, []); | |
if (ec.length>0) | |
println('$p.__empty_constructs__ = ['+ec.join(",")+']'); | |
} | |
var meta = api.buildMetaData(e); | |
if( meta != null ) { | |
print('$p.__meta__ = '); | |
println(api.generateValue(meta)); | |
} | |
} | |
function genType( t : Type ) { | |
switch( t ) { | |
case TInst(c, _): | |
var c = c.get(); | |
if( c.init != null ) | |
inits.add(c.init); | |
if( !c.isExtern ) genClass(c); | |
case TEnum(r, _): | |
var e = r.get(); | |
if( !e.isExtern ) genEnum(e); | |
default: | |
} | |
} | |
public function generate() { | |
if (jsModern) | |
println('(function ('+(genExpose?"$hx_exports":"")+') { "use strict"'); | |
var vars = []; | |
if (api.hasFeature("Type.resolveClass") || api.hasFeature("Type.resolveEnum")) | |
vars.push("$hxClasses = " + (jsModern? "{}" : "$hxClasses || {}")); | |
if (api.hasFeature("may_print_enum")) | |
vars.push("$estr = function() { return "+packClass(["js"],"Boot")+".__string_rec(this,''); }"); | |
if (vars.length>0) | |
println("var "+vars.join(",")); | |
if(genExtend) | |
print("function $extend(from, fields) { | |
function Inherit() {} Inherit.prototype = from; var proto = new Inherit(); | |
for (var name in fields) proto[name] = fields[name]; | |
if( fields.toString !== Object.prototype.toString ) proto.toString = fields.toString; | |
return proto; | |
}\n"); | |
for( t in api.types ) | |
genType(t); | |
if (api.hasFeature("use.$iterator")){ | |
api.addFeature("use.$bind"); | |
printn("function $iterator(o) { if( o instanceof Array ) return function() { return HxOverrides.iter(o); }; return typeof(o.iterator) == 'function' ? $bind(o,o.iterator) : o.iterator; }"); | |
} | |
if (api.hasFeature("use.$bind")){ | |
println("var $_, $fid = 0"); | |
printn("function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $fid++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = function(){ return f.method.apply(f.scope, arguments); }; f.scope = o; f.method = m; o.hx__closures__[m.__id__] = f; } return f; }"); | |
} | |
for( e in inits ) | |
genInit(e); | |
for( s in statics ) | |
println(getPath(s.c)+field(s.f.name)+' = '+api.generateValue(s.f.expr())); | |
if( api.main != null ) | |
println(api.generateValue(api.main)); | |
if (jsModern) | |
print('})('+(genExpose?'typeof window != "undefined" ? window : exports':'')+');\n'); | |
sys.io.File.saveContent(api.outputFile, buf.toString()); | |
} | |
function genInit(e:TypedExpr){ | |
var code=api.generateStatement(e); | |
//cosmetic only | |
var colon=';'; | |
for (l in code.split('\n')){ | |
if (l=="{" || l=="}") { | |
colon=''; | |
continue; | |
} | |
printn(l.replace("\t","")+colon); | |
} | |
} | |
static function use() | |
Compiler.setCustomJSGenerator(function(api) new Generator(api).generate()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment