package tink.macro; import haxe.macro.Expr; import tink.core.Pair; using tink.MacroApi; enum FieldInit { Value(e:Expr); Arg(?t:ComplexType, ?noPublish:Bool); OptArg(?e:Expr, ?t:ComplexType, ?noPublish:Bool); } class Constructor { var oldStatements:Array; var nuStatements:Array; var beforeArgs:Array; var args:Array; var afterArgs:Array; var pos:Position; var onGenerateHooks:ArrayVoid>; var superCall:Expr; public var isPublic:Null; public function new(f:Function, ?isPublic:Null = null, ?pos:Position, ?meta:Metadata) { this.nuStatements = []; this.isPublic = isPublic; this.pos = pos.sanitize(); this.onGenerateHooks = []; this.args = []; this.beforeArgs = []; this.afterArgs = []; this.oldStatements = if (f == null) []; else { for (i in 0...f.args.length) { var a = f.args[i]; if (a.name == '_') { afterArgs = f.args.slice(i + 1); break; } beforeArgs.push(a); } if (f.expr == null) []; else switch (f.expr.expr) { case EBlock(exprs): exprs; default: oldStatements = [f.expr]; } } superCall = if (oldStatements.length == 0) [].toBlock(); else switch oldStatements[0] { case macro super($a{_}): oldStatements.shift(); default: [].toBlock(); } } public function addStatement(e:Expr, ?prepend) if (prepend) this.nuStatements.unshift(e) else this.nuStatements.push(e); public function addArg(name:String, ?t:ComplexType, ?e:Expr, ?opt = false) args.push( { name : name, opt : opt || e != null, type : t, value: e } ); public function init(name:String, pos:Position, with:FieldInit, ?options:{ ?prepend:Bool, ?bypass:Bool }) { if (options == null) options = {}; var e = switch with { case Arg(t, noPublish): if (noPublish != true) publish(); args.push( { name : name, opt : false, type : t } ); name.resolve(pos); case OptArg(e, t, noPublish): if (noPublish != true) publish(); args.push( { name : name, opt : true, type : t, value: e } ); name.resolve(pos); case Value(e): e; } var tmp = MacroApi.tempName(); if (options.bypass) { if (haxe.macro.Context.defined('java')) { addStatement( macro @:pos(pos) if (Math.random() < .0) { //if this is false, then it gets thrown out before reaching the backend which will then generate invalid code var $tmp = this.$name; $i{tmp} = $e; this.$name = $i{tmp}; }, true ); addStatement(macro @:pos(pos) (cast this).$name = $e, options.prepend); } else { addStatement(macro @:pos(pos) if (false) { var $tmp = this.$name; $i{tmp} = $e; }, true); addStatement(macro @:pos(pos) (cast this).$name = $e, options.prepend); } } else addStatement(macro @:pos(pos) this.$name = $e, options.prepend); } public inline function publish() if (isPublic == null) isPublic = true; function toBlock() return [superCall].concat(nuStatements).concat(oldStatements).toBlock(pos); public function onGenerate(hook) this.onGenerateHooks.push(hook); public function toHaxe():Field { var f:Function = { args: this.beforeArgs.concat(this.args).concat(this.afterArgs), ret: 'Void'.asComplexType(), expr: toBlock(), params: [] }; for (hook in onGenerateHooks) hook(f); onGenerateHooks = []; return { name: 'new', doc : null, access : isPublic ? [APublic] : [], kind : FFun(f), pos : pos, meta : [] } } }