Add class building helpers.

This commit is contained in:
back2dos
2013-07-16 16:03:11 +02:00
parent 56cc32f3a9
commit 3d8e52fcf0
4 changed files with 437 additions and 4 deletions

View File

@@ -0,0 +1,155 @@
package tink.macro;
import haxe.macro.Expr;
import haxe.macro.Type;
import haxe.macro.Context;
import haxe.macro.Printer;
using tink.macro.Tools;
using Lambda;
class ClassBuilder {
var memberMap:Map<String,Member>;
var memberList:Array<Member>;
var macros:Map<String,Field>;
var constructor:Null<Constructor>;
var localClass:ClassType;
var superFields:Map<String,Bool>;
public function new() {
this.memberMap = new Map();
this.memberList = [];
this.macros = new Map();
this.localClass = Context.getLocalClass().get();
switch (localClass.kind) {
case KAbstractImpl(a):
//TODO: remove this whole workaround
var meta = localClass.meta;
for (tag in a.get().meta.get())
if (!meta.has(tag.name))
meta.add(tag.name, tag.params, tag.pos);
default:
}
for (field in Context.getBuildFields())
if (field.access.has(AMacro))
macros.set(field.name, field)
else if (field.name == 'new') {
var m:Member = field;
this.constructor = new Constructor(m.getFunction().sure(), m.isPublic, m.pos);
}
else
addMember(field);
}
public function getConstructor():Constructor {
if (constructor == null)
if (localClass.superClass != null && localClass.superClass.t.get().constructor != null) {
try {
var ctor = Context.getLocalClass().get().superClass.t.get().constructor.get();
var func = Context.getTypedExpr(ctor.expr()).getFunction().sure();
//TODO: Make sure the code below is no longer necessary
// for (arg in func.args)
// arg.type = null;
func.expr = "super".resolve().call(func.getArgIdents());
constructor = new Constructor(func);
if (ctor.isPublic)
constructor.publish();
}
catch (e:Dynamic) {//fails for unknown reason
if (e == 'assert')
neko.Lib.rethrow(e);
constructor = new Constructor(null);
}
}
else
constructor = new Constructor(null);
return constructor;
}
public function hasConstructor():Bool
return this.constructor == null;
public function export(?verbose):Array<Field> {
var ret = (constructor == null || localClass.isInterface) ? [] : [constructor.toHaxe()];
for (member in memberList) {
if (member.isBound)
switch (member.kind) {//TODO: this seems like an awful place for a cleanup. If all else fails, this should go into a separate plugin (?)
case FVar(_, _): if (!member.isStatic) member.isBound = null;
case FProp(_, _, _, _): member.isBound = null;
default:
}
ret.push(member);
}
for (m in macros)
ret.push(m);
if (verbose)
for (field in ret)
Context.warning(new Printer().printField(field), field.pos);
return ret;
}
public function iterator():Iterator<Member>
return this.memberList.copy().iterator();
public function getOwnMember(name:String):Member
return
memberMap.get(name);
public function hasOwnMember(name:String):Bool
return
macros.exists(name) || (memberMap.exists(name));
public function hasSuperField(name:String):Bool {
if (superFields == null) {
superFields = new Map();
var cl = localClass.superClass;
while (cl != null) {
var c = cl.t.get();
for (f in c.fields.get())
superFields.set(f.name, true);
cl = c.superClass;
}
}
return superFields.get(name);
}
public function removeMember(member:Member):Bool
return
member != null
&&
memberMap.get(member.name) == member
&&
memberMap.remove(member.name)
&&
memberList.remove(member);
public function hasMember(name:String):Bool
return hasOwnMember(name) || hasSuperField(name);
public function addMember(m:Member, ?front:Bool = false):Member {
if (m.name == 'new')
throw 'Constructor must not be registered as ordinary member';
if (hasOwnMember(m.name))
m.pos.error('duplicate member declaration ' + m.name);
memberMap.set(m.name, m);
if (front)
memberList.unshift(m);
else
memberList.push(m);
return m;
}
static public function run(plugins:Array<ClassBuilder->Void>, ?verbose) {
var builder = new ClassBuilder();
for (p in plugins)
p(builder);
return builder.export(verbose);
}
}

View File

@@ -0,0 +1,93 @@
package tink.macro;
import haxe.macro.Expr;
using tink.macro.Tools;
class Constructor {
var oldStatements:Array<Expr>;
var nuStatements:Array<Expr>;
var beforeArgs:Array<FunctionArg>;
var args:Array<FunctionArg>;
var afterArgs:Array<FunctionArg>;
var pos:Position;
var onGenerateHooks:Array<Function->Void>;
public var isPublic:Null<Bool>;
public function new(f:Function, ?isPublic:Null<Bool> = null, ?pos:Position) {
this.nuStatements = [];
this.isPublic = isPublic;
this.pos = pos.getPos();
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];
}
}
}
public function addStatement(e:Expr, ?prepend)
if (prepend)
this.nuStatements.unshift(e)
else
this.nuStatements.push(e);
public function init(name:String, pos:Position, ?e:Expr, ?def:Expr, ?prepend:Bool, ?t:ComplexType) {
if (e == null) {
e = name.resolve(pos);
args.push( { name : name, opt : def != null, type : t, value : def } );
if (isPublic == null)
isPublic = true;
}
if (t != null)
e = ECheckType(e, t).at(e.pos);
var s = EUntyped('this'.resolve(pos)).at(pos).field(name, pos).assign(e, pos);
addStatement(s, prepend);
}
public inline function publish()
if (isPublic == null)
isPublic = true;
function toBlock()
return 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);
return {
name: 'new',
doc : null,
access : isPublic ? [APublic] : [],
kind : FFun(f),
pos : pos,
meta : []
}
}
}

181
src/tink/macro/Member.hx Normal file
View File

@@ -0,0 +1,181 @@
package tink.macro;
import haxe.macro.Expr;
using tink.core.Outcome;
using tink.macro.Tools;
abstract Member(Field) from Field to Field {
static public function prop(name:String, t:ComplexType, pos, ?noread = false, ?nowrite = false):Member {
var ret:Field = {
name: name,
pos: pos,
access: [APublic],
kind: FProp(noread ? 'null' : 'get_' + name, nowrite ? 'null' : ('set_' + name), t),
}
return ret;
}
static public function getter(field:String, ?pos, e:Expr, ?t:ComplexType)
return method('get_' + field, pos, false, e.func(t));
static public function setter(field:String, ?param = 'param', ?pos, e:Expr, ?t:ComplexType)
return method('set_' + field, pos, false, [e, param.resolve(pos)].toBlock(pos).func([param.toArg(t)], t));
static public function method(name:String, ?pos, ?isPublic = true, f:Function) {
var f:Field = {
name: name,
pos: if (pos == null) f.expr.pos else pos,
kind: FFun(f)
};
var ret:Member = f;
ret.isPublic = isPublic;
return ret;
}
public var name(get, set):String;
public var doc(get, set):Null<String>;
public var kind(get, set):FieldType;
public var pos(get, set):Position;
public var overrides(get, set):Bool;
public var isStatic(get, set):Bool;
public var isPublic(get, set):Null<Bool>;
public var isBound(get, set):Null<Bool>;
public function getFunction()
return
switch kind {
case FFun(f): Success(f);
default: pos.makeFailure('Field should be function');
}
public function addMeta(name, ?pos, ?params) {
if (this.meta == null)
this.meta = [];
this.meta.push({
name: name,
pos: if (pos == null) this.pos else pos,
params: if (params == null) [] else params
});
}
public function extractMeta(name) {
if (this.meta != null)
for (tag in this.meta) {
if (tag.name == name) {
this.meta.remove(tag);
return Success(tag);
}
}
return Failure('missing @$name');
}
public function publish()
if (this.access == null) this.access = [APublic];
else {
for (a in this.access)
if (a == APrivate || a == APublic) return;
this.access.push(APublic);
}
inline function get_name() return this.name;
inline function set_name(param) return this.name = param;
inline function get_doc() return this.doc;
inline function set_doc(param) return this.doc = param;
inline function get_kind() return this.kind;
inline function set_kind(param) return this.kind = param;
inline function get_pos() return this.pos;
inline function set_pos(param) return this.pos = param;
inline function get_overrides() return hasAccess(AOverride);
inline function set_overrides(param) {
changeAccess(
param ? AOverride : null,
param ? null : AOverride
);
return param;
}
inline function get_isStatic() return hasAccess(AStatic);
inline function set_isStatic(param) {
changeAccess(
param ? AStatic : null,
param ? null : AStatic
);
return param;
}
function get_isPublic() {
if (this.access != null)
for (a in this.access)
switch a {
case APublic: return true;
case APrivate: return false;
default:
}
return null;
}
inline function set_isPublic(param) {
if (param == null) {
changeAccess(null, APublic);
changeAccess(null, APrivate);
}
else if (param)
changeAccess(APublic, APrivate);
else
changeAccess(APrivate, APublic);
return param;
}
function get_isBound() {
if (this.access != null)
for (a in this.access)
switch a {
case AInline: return true;
case ADynamic: return false;
default:
}
return null;
}
inline function set_isBound(param) {
if (param == null) {
changeAccess(null, AInline);
changeAccess(null, ADynamic);
}
else if (param)
changeAccess(AInline, ADynamic);
else
changeAccess(ADynamic, AInline);
return param;
}
//TODO: add some sanitazation stuff to normalize / report invalid access combinations
function hasAccess(a:Access) {
if (this.access != null)
for (x in this.access)
if (x == a) return true;
return false;
}
function changeAccess(add:Access, remove:Access) {
var i = 0;
if (this.access == null)
this.access = [];
while (i < this.access.length) {
var a = this.access[i];
if (a == remove) {
this.access.splice(i, 1);
if (add == null) return;
remove = null;
}
else {
i++;
if (a == add) {
add = null;
if (remove == null) return;
}
}
}
this.access.push(add);
}
}

View File

@@ -5,14 +5,18 @@ package ;
import haxe.unit.TestRunner;
import neko.Lib;
#else
import tink.macro.Member;
import tink.macro.Constructor;
import tink.macro.ClassBuilder;
using tink.macro.Tools;
#end
class Run {
#if !macro
static var tests:Array<TestCase> = [];
static function main() {
test();//it compiles!!!
}
static var tests:Array<TestCase> = [];
static function main() {
test();//it compiles!!!
}
#end
macro static function test() {
return macro null;