refactoring
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
package bindx.macro;
|
||||
|
||||
import bindx.macro.GenericError;
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
|
||||
using haxe.macro.Tools;
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
using bindx.macro.MetaUtils;
|
||||
|
||||
class BindableMacros {
|
||||
|
||||
static inline var OLD_VALUE = "__oldValue__";
|
||||
static inline var NEW_VALUE = "__newValue__";
|
||||
|
||||
/**
|
||||
* default value: false
|
||||
*/
|
||||
static public inline var INLINE_SETTER = "inlineSetter";
|
||||
/**
|
||||
* default value: false
|
||||
*/
|
||||
static public inline var FORCE = "force";
|
||||
|
||||
static public inline var BINDABLE_FIELDS = ":bindableFields";
|
||||
|
||||
static var processed:Array<String> = [];
|
||||
|
||||
static var bindingSignalProvider:IBindingSignalProvider;
|
||||
|
||||
macro static public function buildIBindable():Array<Field> {
|
||||
var type = Context.getLocalType();
|
||||
var tName = type.toComplexType().toString();
|
||||
if (processed.indexOf(tName) > -1) {
|
||||
return null;
|
||||
}
|
||||
processed.push(tName);
|
||||
|
||||
var classType = type.getClass();
|
||||
|
||||
Context.onMacroContextReused(function () {
|
||||
processed = [];
|
||||
return true;
|
||||
});
|
||||
|
||||
if (bindingSignalProvider == null) {
|
||||
bindingSignalProvider = new bindx.macro.BindSignalProvider();
|
||||
}
|
||||
|
||||
var fields = Context.getBuildFields();
|
||||
|
||||
var meta = classType.bindableMeta();
|
||||
if (meta != null) injectBindableMeta(fields, meta);
|
||||
|
||||
if (classType.isInterface) {
|
||||
var a = [];
|
||||
for (f in fields) {
|
||||
for (m in f.meta) if (m.name == MetaUtils.BINDABLE_META) {
|
||||
a.push(macro $v { f.name } );
|
||||
if (m.params.length > 0)
|
||||
Context.warning('Interface doesn\'t support @:bindable meta params', m.pos);
|
||||
}
|
||||
}
|
||||
var classMeta = classType.meta;
|
||||
if (classMeta.has(BINDABLE_FIELDS))
|
||||
classMeta.remove(BINDABLE_FIELDS);
|
||||
classMeta.add(BINDABLE_FIELDS, a, classType.pos);
|
||||
return fields;
|
||||
}
|
||||
|
||||
var interfaceFields = getBindableFieldsFromInterfaces(classType);
|
||||
|
||||
var res = [];
|
||||
for (f in fields)
|
||||
if (f.hasBindableMeta()) {
|
||||
if (!isFieldBindable(f, fields)) Context.error('can\'t bind field \'${f.name}\'', f.pos);
|
||||
|
||||
bindField(f, fields, res);
|
||||
} else {
|
||||
if (interfaceFields.exists(f.name))
|
||||
Context.fatalError('Interface "${typeName(interfaceFields.get(f.name))}" expects @:bindable metadata', f.pos);
|
||||
res.push(f);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
inline static function typeName(t: { module:String, name:String } ) {
|
||||
return t.module + (t.module.length > 0 ? "." + t.name : t.name);
|
||||
}
|
||||
|
||||
static function getBindableFieldsFromInterfaces(classType:ClassType):Map<String, ClassType> {
|
||||
var interfaceFields = new Map();
|
||||
|
||||
function iter(t:ClassType) {
|
||||
var meta = t.meta.get();
|
||||
for (m in meta) if (m.name == BINDABLE_FIELDS) {
|
||||
for (a in m.params) {
|
||||
var value = switch a.expr { case EConst(CString(s)): s; case _: null; };
|
||||
interfaceFields.set(value, t);
|
||||
}
|
||||
break;
|
||||
}
|
||||
for (it in t.interfaces) iter(it.t.get());
|
||||
}
|
||||
|
||||
for (i in classType.interfaces) iter(i.t.get());
|
||||
return interfaceFields;
|
||||
}
|
||||
|
||||
static function bindField(field:Field, fields:Array<Field>, res:Array<Field>):Void {
|
||||
var meta = field.bindableMeta();
|
||||
bindingSignalProvider.getFieldDispatcher(field, res);
|
||||
|
||||
var forceParam = meta.findParam(FORCE);
|
||||
var inlineSetter = meta.findParam(INLINE_SETTER);
|
||||
if (forceParam.isNotNullAndTrue()) {
|
||||
if (inlineSetter != null)
|
||||
Warn.w('\'$INLINE_SETTER\' ignored. \'$FORCE\' mode', inlineSetter.pos, WarnPriority.INFO);
|
||||
res.push(field);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (field.kind) {
|
||||
case FVar(type, expr):
|
||||
var fieldName = field.name;
|
||||
var setterName = 'set_$fieldName';
|
||||
field.kind = FProp("default", "set", type, expr);
|
||||
res.push(field);
|
||||
var setter = macro function foo(value:$type) {
|
||||
var $OLD_VALUE = this.$fieldName;
|
||||
if ($i{OLD_VALUE} == value) return $i{OLD_VALUE};
|
||||
this.$fieldName = value;
|
||||
${bindingSignalProvider.getFieldChangedExpr(field, macro $i{OLD_VALUE}, macro $i{"value"})}
|
||||
return value;
|
||||
};
|
||||
var setterAccess = [APrivate];
|
||||
if (inlineSetter.isNotNullAndTrue()) setterAccess.push(AInline);
|
||||
res.push({
|
||||
name: setterName,
|
||||
kind: FFun(switch (setter.expr) { case EFunction (_, func): func; case _: throw false; }),
|
||||
pos: field.pos,
|
||||
access: setterAccess
|
||||
});
|
||||
|
||||
case FProp(get, set, type, expr):
|
||||
if (inlineSetter != null)
|
||||
Warn.w('$INLINE_SETTER ignored. Setter already exist', inlineSetter.pos, WarnPriority.INFO);
|
||||
var fieldName = field.name;
|
||||
var setter = fields.find(function (it) return it.name == 'set_$fieldName');
|
||||
if (setter != null) {
|
||||
switch (setter.kind) {
|
||||
case FFun(func):
|
||||
patchField = field;
|
||||
func.expr = macro {
|
||||
var $OLD_VALUE = this.$fieldName;
|
||||
if ($i{OLD_VALUE} == $i{func.args[0].name}) return $i{OLD_VALUE};
|
||||
$e{patchSetter(func.expr)};
|
||||
};
|
||||
patchField = null;
|
||||
case _:
|
||||
}
|
||||
}
|
||||
res.push(field);
|
||||
|
||||
case FFun(f):
|
||||
if (inlineSetter != null)
|
||||
Warn.w('methods doesn\'t support \'$INLINE_SETTER\'', inlineSetter.pos, WarnPriority.INFO);
|
||||
res.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
static var patchField:Field;
|
||||
|
||||
static function patchSetter(expr:Expr):Expr {
|
||||
return switch (expr.expr) {
|
||||
case EReturn(res):
|
||||
var fieldName = patchField.name;
|
||||
|
||||
macro {
|
||||
var $NEW_VALUE = ${res.map(patchSetter)};
|
||||
${bindingSignalProvider.getFieldChangedExpr(patchField, macro $i{OLD_VALUE}, macro $i{NEW_VALUE})};
|
||||
return $i{NEW_VALUE};
|
||||
}
|
||||
|
||||
case _: expr.map(patchSetter);
|
||||
}
|
||||
}
|
||||
|
||||
static inline function injectBindableMeta(fields:Array<Field>, meta:MetadataEntry):Void {
|
||||
for (f in fields) {
|
||||
if (f.hasBindableMeta()) continue;
|
||||
if (f.access.exists(function (it) return it.equals(APrivate))) continue;
|
||||
|
||||
var forceParam = meta.findParam(FORCE);
|
||||
if (isFieldBindable(f, fields, forceParam.isNotNullAndTrue()))
|
||||
switch (f.kind) {
|
||||
case FFun(_):
|
||||
case _: f.meta.push({name:MetaUtils.BINDABLE_META, pos:f.pos, params:meta.params});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function isFieldBindable(field:Field, fields:Array<Field>, force = false):Bool {
|
||||
if (field.name == "new") return false;
|
||||
|
||||
for (a in field.access)
|
||||
if (a.equals(AMacro) || a.equals(ADynamic) || a.equals(AStatic))
|
||||
return false;
|
||||
|
||||
var fn = field.name;
|
||||
if (fn.startsWith("set_") || fn.startsWith("get_")) {
|
||||
var propName = fn.substr(4);
|
||||
for (f in fields) if (f.name == propName) {
|
||||
switch (f.kind) {
|
||||
case FProp(get, set, _, _) if (fn == get || fn == set):
|
||||
return false;
|
||||
case _:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!force) {
|
||||
var meta = field.bindableMeta();
|
||||
var forceParam = meta != null ? meta.findParam(FORCE) : null;
|
||||
force = forceParam.isNotNullAndTrue();
|
||||
}
|
||||
|
||||
if (force) return switch (field.kind) {
|
||||
case FProp("never", _, _, _): false;
|
||||
case _: true;
|
||||
}
|
||||
|
||||
return switch (field.kind) {
|
||||
case FProp("never", _, _, _) | FProp(_, "never", _, _) | FProp(_, "dynamic", _, _) | FProp(_, "null", _, _): false;
|
||||
case _: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user