Add class building helpers.
This commit is contained in:
155
src/tink/macro/ClassBuilder.hx
Normal file
155
src/tink/macro/ClassBuilder.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
93
src/tink/macro/Constructor.hx
Normal file
93
src/tink/macro/Constructor.hx
Normal 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
181
src/tink/macro/Member.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
12
tests/Run.hx
12
tests/Run.hx
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user