refactoring

This commit is contained in:
Dima Granetchi
2015-01-08 15:47:14 +02:00
parent ca9b5b4ff9
commit 24f2247b74
13 changed files with 648 additions and 644 deletions
+344
View File
@@ -0,0 +1,344 @@
package bindx.macro;
#if macro
import bindx.macro.GenericError;
import haxe.macro.Expr;
import haxe.macro.MacroStringTools;
import haxe.macro.Type;
import haxe.macro.Context;
import bindx.macro.BindMacros;
using Lambda;
using StringTools;
using haxe.macro.Tools;
private typedef FieldExpr = {
var field:ClassField;
var bindable:Bool;
var e:Expr;
@:optional var params:Array<Expr>;
}
private typedef Chain = {
var init:Array<Expr>;
var bind:Array<Expr>;
var unbind:Array<Expr>;
var expr:Expr;
}
@:access(bindx.macro.BindableMacros)
@:access(bindx.macro.BindMacros)
class BindxExtMacro {
static inline function internalBindChain(expr:Expr, listener:Expr):Expr {
var zeroListener = listenerName(0, "");
var chain = null;
try { chain = warnPrepareChain(expr); } catch (e:GenericError) e.contextError();
var res = macro (function ($zeroListener):Void->Void
$b { chain.init.concat(chain.bind).concat([(macro var res = function ():Void $b { chain.unbind }), macro return res]) }
)($listener);
return res;
}
static inline function unwrapFormatedString(expr:Expr):Expr {
return if (MacroStringTools.isFormatExpr(expr)) {
var f = switch (expr.expr) {
case EConst(CString(s)): s;
case _: null;
}
if (f != null) MacroStringTools.formatString(f, expr.pos) else expr;
} else expr;
}
static function internalBindExpr(expr:Expr, listener:Expr):Expr {
var type = Context.typeof(expr).toComplexType();
var listenerNameExpr = macro listener;
var fieldListenerName = "fieldListener";
var fieldListenerNameExpr = macro $i{fieldListenerName};
var methodListenerName = "methodListener";
var methodListenerNameExpr = macro $i{methodListenerName};
var chain:Chain = { init:[], bind:[], unbind:[], expr:expr };
var binded:Map<String, {prebind:Expr, c:Chain}> = new Map();
var prefix = 0;
function findChain(expr:Expr) {
var isChain;
expr = unwrapFormatedString(expr);
var e = expr;
do switch (e.expr) {
case EField(le, _) | ECall(le, _):
isChain = true;
e = le;
case _:
isChain = false;
} while (isChain);
var doBind = e != expr;
if (doBind) {
var key = expr.toString();
for (k in binded.keys()) if (k.startsWith(key)) {
doBind = false;
break;
}
}
if (doBind) {
var pre = '_${prefix++}';
var zeroListener = listenerName(0, pre);
var c = null;
try {
c = warnPrepareChain(expr, pre, true);
} catch (e:GenericError) {
Warn.w('${expr.toString()} is not bindable.', e.pos, WarnPriority.ALL);
}
if (c != null) {
var key = c.expr.toString();
if (!binded.exists(key)) {
var ecall = switch (c.expr.expr) {
case EField(e, field):
var type = Context.typeof(e);
var classRef = type.getClass();
var field = classRef.findField(field);
field.kind.match(FMethod(_));
case _: false;
}
var prebind = macro var $zeroListener = ${ecall ? methodListenerNameExpr : fieldListenerNameExpr};
binded.set(key, {prebind:prebind, c:c});
}
}
}
expr.iter(findChain);
}
findChain(expr);
var keys = [for (k in binded.keys()) k];
var i = 0;
while (i < keys.length) {
var k = keys[i];
var remove = false;
var j = i;
while (!remove && ++j < keys.length) remove = keys[j].startsWith(k);
if (remove) keys = keys.splice(i, 1); else i++;
}
var msg = [];
for (k in keys) {
var data = binded.get(k);
msg.push(data.c.expr.toString());
chain.bind.unshift(data.prebind);
var c = data.c;
chain.init = chain.init.concat(c.init);
chain.bind = chain.bind.concat(c.bind);
chain.unbind = chain.unbind.concat(c.unbind);
}
Warn.w('Bind \'${msg.join("', '")}\'', expr.pos, WarnPriority.INFO);
var zeroListener = listenerName(0, "");
var zeroValue = 'value0';
chain.unbind.unshift(macro $i { zeroValue } = null);
var callListener = switch (type) {
case macro : Void: macro if (!init) $i{zeroListener}();
case _:
macro if (!init) {
var v:Null < $type > = null;
try { v = $expr; } catch (e:Dynamic) { };
$i { zeroListener } ($i { zeroValue }, v);
$i { zeroValue } = v;
};
}
var preInit = [
(macro var init:Bool = true),
macro var $zeroValue:Null<$type> = null
];
var postInit = [
macro function $fieldListenerName(?from:Dynamic, ?to:Dynamic) $callListener,
macro function $methodListenerName() $callListener
];
var result = [
macro init = false,
macro $i { methodListenerName } (),
(macro var res = function ():Void $b { chain.unbind } ),
macro return res
];
var res = macro (function ($zeroListener):Void->Void
$b { preInit.concat(chain.init).concat(postInit).concat(chain.bind).concat(result) }
)($listener);
return res;
}
static function checkFields(expr:Expr):Array<FieldExpr> {
var first = BindMacros.checkField(expr);
var firstParams;
if (first.field == null) {
switch (expr.expr) {
case ECall(e, params):
first = BindMacros.checkField(e);
firstParams = params;
case _:
}
}
if (first.field == null) {
if (first.error != null) throw first.error;
else throw new FatalError('${expr.toString()} is not bindable.', expr.pos);
}
var prevField = {e:first.e, field:first.field, error:null};
var fields:Array<FieldExpr> = [ { field:first.field, bindable:first.error == null, e:first.e, params:firstParams } ];
var end;
do {
end = true;
var field = BindMacros.checkField(prevField.e);
if (field.field != null) {
fields.push( { field:field.field, bindable:field.error == null, e:field.e } );
end = false;
} else if (field.error != null) switch (prevField.e.expr) {
case ECall(e, params):
field = BindMacros.checkField(e);
if (field.field == null) throw new FatalError('${e.toString()} is not bindable.', expr.pos);
else fields.push( { e:field.e, field:field.field, params:params, bindable:field.error == null } );
end = false;
case _:
}
else if (field.e == null) {
throw new FatalError('${prevField.e.toString()} is not bindable.', prevField.e.pos);
}
prevField = field;
} while (!end);
return fields;
}
static function warnPrepareChain(expr:Expr, prefix = "", skipUnbindable = false):Chain {
var fields = checkFields(expr);
if (fields.length == 0)
throw new FatalError('Can\'t bind empty expression: ${expr.toString()}', expr.pos);
var i = fields.length;
var first = null;
while (i-- > 0) {
var f = fields[i];
if (first != null) f.bindable = false;
else if (!f.bindable && first == null) {
first = f;
if (skipUnbindable) {
fields = fields.splice(i+1, fields.length - i);
break;
}
}
}
var bindableNum = fields.fold(function (it, n) return n += it.bindable ? 1 : 0, 0);
if (bindableNum == 0) {
throw new GenericError('${expr.toString()} is not bindable.', expr.pos);
}
if (first != null) {
Warn.w('${expr.toString()} is not full bindable. Can bind only "${first.e.toString()}".', expr.pos, WarnPriority.INFO);
}
return prepareChain(fields, expr, prefix);
}
inline static function listenerName(idx:Int, prefix) return '${prefix}listener$idx';
static function prepareChain(fields:Array<FieldExpr>, expr:Expr, prefix = ""):Chain {
var res:Chain = { init:[], bind:[], unbind:[], expr:null };
var prevListenerName = listenerName(0, prefix);
var prevListenerNameExpr = macro $i { prevListenerName };
var zeroListener = fields[0].bindable ? { f:fields[0], l:prevListenerNameExpr } : null;
if (zeroListener != null) {
var fn = zeroListener.f.field.name;
res.expr = macro @:pos(zeroListener.f.e.pos) ${zeroListener.f.e}.$fn;
}
var i = -1;
while (++i < fields.length - 1) {
var field = fields[i + 1];
var prev = fields[i];
var type = Context.typeof(field.e).toComplexType();
var listenerName = listenerName(i+1, prefix);
var listenerNameExpr = macro $i { listenerName };
var value = '${prefix}value${i+1}';
var valueExpr = macro $i { value };
var oldValue = '${prefix}oldValue${i+1}';
var oldValueExpr = macro $i { oldValue };
var fieldName = prev.field.name;
var e = prev.e;
var fieldListenerBody = [];
var fieldListener;
if (field.bindable) zeroListener = { f:field, l:listenerNameExpr };
if (prev.bindable && res.expr == null) {
var fn = prev.field.name;
res.expr = macro @:pos(prev.e.pos) ${prev.e}.$fn;
}
if (prev.bindable) {
var unbind = BindableMacros.bindingSignalProvider.getClassFieldUnbindExpr(valueExpr, prev.field, prevListenerNameExpr );
res.bind.push(macro var $value:Null<$type> = null );
res.unbind.push(macro if ($valueExpr != null) { $unbind; $valueExpr = null; } );
fieldListenerBody.push(macro if ($valueExpr != null) $unbind );
fieldListenerBody.push(macro $valueExpr = n );
fieldListenerBody.push(macro if (n != null)
$ { BindableMacros.bindingSignalProvider.getClassFieldBindExpr(macro n, prev.field, prevListenerNameExpr ) });
}
var callPrevArgs = prev.params != null ? [] : [macro o != null ? o.$fieldName : null, macro n != null ? n.$fieldName : null];
var callPrev = macro $prevListenerNameExpr($a { callPrevArgs } );
fieldListenerBody.push(callPrev);
if (field.params != null) {
fieldListenerBody.unshift(macro $i { oldValue } = n);
fieldListenerBody.unshift(macro try { n = $e; } catch (e:Dynamic) { });
fieldListenerBody.unshift(macro var n:Null < $type > = null);
fieldListenerBody.unshift(macro var o:Null<$type> = $i{oldValue} );
res.init.push(macro var $oldValue:Null<$type> = null);
res.unbind.push(macro $oldValueExpr = null);
fieldListener = macro function $listenerName ():Void $b { fieldListenerBody };
} else {
if (prev.bindable) {
fieldListenerBody.unshift(macro if (o != null)
${BindableMacros.bindingSignalProvider.getClassFieldUnbindExpr(macro o, prev.field, prevListenerNameExpr )}
);
}
fieldListener = macro function $listenerName (o:Null<$type>, n:Null<$type>):Void $b { fieldListenerBody };
}
res.bind.push(fieldListener);
prevListenerName = listenerName;
prevListenerNameExpr = listenerNameExpr;
}
if (zeroListener == null || zeroListener.f.bindable == false)
throw new GenericError('${expr.toString()} is not bindable.', expr.pos);
var zeroName = zeroListener.f.e.toString();
if (zeroName != "this")
res.init.unshift(macro var $zeroName = $i{zeroName});
res.bind.push(BindableMacros.bindingSignalProvider.getClassFieldBindExpr(macro $i{zeroName}, zeroListener.f.field, zeroListener.l ));
res.unbind.push(BindableMacros.bindingSignalProvider.getClassFieldUnbindExpr(macro $i{zeroName}, zeroListener.f.field, zeroListener.l ));
if (zeroListener.f.params != null) {
res.bind.push(macro ${zeroListener.l}());
} else {
var fieldName = zeroListener.f.field.name;
res.bind.push(macro $ { zeroListener.l } (null, $ { zeroListener.f.e } .$fieldName ));
}
return res;
}
}
#end
+106
View File
@@ -0,0 +1,106 @@
package bindx.macro;
#if macro
import bindx.macro.GenericError;
import haxe.macro.Expr;
import haxe.macro.Type;
import haxe.macro.Context;
using haxe.macro.Tools;
using bindx.macro.MetaUtils;
using Lambda;
@:access(bindx.macro.BindableMacros)
class BindMacros {
static inline function internalBind(field:Expr, listener:Expr, doBind:Bool):Expr {
var fieldData = warnCheckField(field);
return if (doBind) BindableMacros.bindingSignalProvider.getClassFieldBindExpr(fieldData.e, fieldData.field, listener);
else BindableMacros.bindingSignalProvider.getClassFieldUnbindExpr(fieldData.e, fieldData.field, listener);
}
static inline function internalBindTo(field:Expr, target:Expr):Expr {
var fieldData = warnCheckField(field);
return BindableMacros.bindingSignalProvider.getClassFieldBindToExpr(fieldData.e, fieldData.field, target);
}
static inline function internalNotify(field:Expr, ?oldValue:Expr, ?newValue:Expr):Expr {
var fieldData = warnCheckField(field);
return BindableMacros.bindingSignalProvider.getClassFieldChangedExpr(fieldData.e, fieldData.field, oldValue, newValue);
}
static inline function internalUnbindAll(object:ExprOf<IBindable>):Expr {
var type = Context.typeof(object).follow();
if (!isBindable(type.getClass())) {
Context.error('\'${object.toString()}\' must be bindx.IBindable', object.pos);
}
return BindableMacros.bindingSignalProvider.getUnbindAllExpr(object, type);
}
static inline function warnCheckField(field:Expr):{e:Expr, field:ClassField} {
var res = null;
try {
res = checkField(field);
if (res.error != null) res.error.contextError();
} catch (e:FatalError) {
e.contextFatal();
} catch (e:GenericError) {
e.contextError();
};
return res;
}
static function checkField(f:Expr):{e:Expr, field:ClassField, error:GenericError} {
var error:GenericError = null;
switch (f.expr) {
case EField(e, field):
var type = Context.typeof(e);
var classType = switch (type) { case TInst(c, _): c.get(); case _: null; };
if (classType == null) {
error = new FatalError('Type \'${e.toString()}\' is unknown', e.pos);
return {e:f, field:null, error:error};
}
if (!isBindable(classType)) {
error = new GenericError('\'${e.toString()}\' must be bindx.IBindable', e.pos);
}
var field = classType.findField(field, null);
if (field == null) {
throw new FatalError('\'${e.toString()}.${field.name}\' expected', field.pos);
return null;
}
if (!field.hasBindableMeta()) {
error = new GenericError('\'${e.toString()}.${field.name}\' is not bindable', field.pos);
}
return {e:e, field:field, error:error};
case EConst(CIdent(_)):
return {e:f, field:null, error:new GenericError('Can\'t bind \'${f.toString()}\'. Please use \'this.${f.toString()}\'', f.pos)};
case _:
}
return {e:f, field:null, error:new GenericError('\'${f.toString()}\' is not bindable', f.pos)};
}
static inline function isBindable(classType:ClassType):Bool {
var check = [classType];
var res = false;
while (check.length > 0 && !res) {
var t = check.shift();
while (t != null) {
if (t.module == "bindx.IBindable" && t.name == "IBindable") {
res = true;
break;
}
for (it in t.interfaces)
check.push(it.t.get());
t = t.superClass != null ? t.superClass.t.get() : null;
}
}
return res;
}
}
#end
+164
View File
@@ -0,0 +1,164 @@
package bindx.macro;
import bindx.BindSignal.Signal;
import haxe.macro.Expr;
import haxe.macro.Type;
import haxe.macro.Context;
import bindx.BindSignal;
using bindx.macro.MetaUtils;
using haxe.macro.Tools;
using Lambda;
class BindSignalProvider implements IBindingSignalProvider {
static inline var SIGNAL_POSTFIX = "Changed";
/**
* default value: true
*/
static inline var LAZY_SIGNAL = "lazySignal";
/**
* default value: false
*/
static inline var INLINE_SIGNAL_GETTER = "inlineSignalGetter";
public function new() {}
@:extern static inline function signalName(fieldName:String):String return fieldName + SIGNAL_POSTFIX;
@:extern static inline function signalGetterName(fieldName:String):String return "get_" + signalName(fieldName);
@:extern static inline function signalPrivateName(fieldName:String):String return "_" + signalName(fieldName);
public function getFieldDispatcher(field:Field, res:Array<Field>):Void {
switch (field.kind) {
case FFun(_):
generateSignal(field, macro : bindx.BindSignal.MethodSignal, macro new bindx.BindSignal.MethodSignal(), res);
case FProp(_, _, type, _) | FVar(type, _):
generateSignal(field, macro : bindx.BindSignal.FieldSignal<$type>, macro new bindx.BindSignal.FieldSignal<$type>(), res);
}
}
public function getFieldChangedExpr(field:Field, oldValue:Expr, newValue:Expr):Expr {
var args = switch (field.kind) { case FFun(_): []; case _: [oldValue, newValue]; }
return dispatchSignal(macro this, field.name, args, hasLazy(field.bindableMeta()));
}
public function getClassFieldBindExpr(expr:Expr, field:ClassField, listener:Expr):Expr {
var signalName = signalName(field.name);
return macro @:privateAccess $expr.$signalName.add($listener);
}
public function getClassFieldBindToExpr(expr:Expr, field:ClassField, target:Expr):Expr {
var signalName = signalName(field.name);
return switch (field.kind) {
case FMethod(_):
var fieldName = field.name;
macro @:privateAccess {
var listener = function () $target = $expr.$fieldName();
$expr.$signalName.add(listener);
function __unbind__() $expr.$signalName.remove(listener);
}
case FVar(_, _):
var type = field.type.follow().toComplexType();
macro @:privateAccess {
var listener = function (from:$type, to:$type) $target = to;
$expr.$signalName.add(listener);
function __unbind__() $expr.$signalName.remove(listener);
}
}
}
public function getClassFieldUnbindExpr(expr:Expr, field:ClassField, listener:Expr):Expr {
var signalName = signalName(field.name);
return if (!listener.isNullOrEmpty())
macro @:privateAccess $expr.$signalName.remove($listener);
else
macro @:privateAccess $expr.$signalName.removeAll();
}
public function getClassFieldChangedExpr(expr:Expr, field:ClassField, oldValue:Expr, newValue:Expr):Expr {
var args = switch (field.kind) {
case FMethod(_):
if (!oldValue.isNullOrEmpty())
Context.error("method notify doesn't require oldValue", oldValue.pos);
if (!newValue.isNullOrEmpty())
Context.error("method notify doesn't require newValue", newValue.pos);
[];
case FVar(_, _):
[oldValue, newValue];
}
return dispatchSignal(expr, field.name, args, hasLazy(field.bindableMeta()));
}
public function getUnbindAllExpr(expr:ExprOf<IBindable>, type:Type):Expr {
return macro bindx.BindSignal.SignalTools.unbindAll($expr);
}
function generateSignal(field:Field, type:ComplexType, builder:Expr, res:Array<Field>):Void {
var signalName = signalName(field.name);
var meta = field.bindableMeta();
var inlineSignalGetter = meta.findParam(INLINE_SIGNAL_GETTER);
if (hasLazy(meta)) {
var signalPrivateName = signalPrivateName(field.name);
res.push({
name: signalPrivateName,
kind: FVar(type, null),
pos: field.pos,
meta: [ { name:SignalTools.BIND_SIGNAL_META, pos:field.pos, params: [macro true] } ],
access: [APrivate]
});
res.push({
name: signalName,
kind: FProp("get", "never", type, null),
pos: field.pos,
access: [APrivate],
});
var getter = macro function foo() {
if (this.$signalPrivateName == null)
this.$signalPrivateName = ${builder};
return $i{signalPrivateName};
};
var getterAccess = [APrivate];
if (inlineSignalGetter.isNotNullAndTrue()) getterAccess.push(AInline);
res.push({
name: signalGetterName(field.name),
kind: FFun(switch (getter.expr) { case EFunction (_, func): func; case _: throw false; }),
pos: field.pos,
access: getterAccess
});
} else {
if (inlineSignalGetter != null)
Context.warning('$INLINE_SIGNAL_GETTER works only with lazy signals', inlineSignalGetter.pos);
res.push({
name: signalName,
kind: FProp("default", "null", type, builder),
pos: field.pos,
access: [APrivate],
meta: [ { name:SignalTools.BIND_SIGNAL_META, pos:field.pos, params: [macro false] } ]
});
}
}
inline function dispatchSignal(expr:Expr, fieldName:String, args:Array<Expr>, lazy:Bool):Expr {
return
if (lazy) {
var signalPrivateName = signalPrivateName(fieldName);
macro @:privateAccess {
if ($expr.$signalPrivateName != null)
$expr.$signalPrivateName.dispatch($a { args } );
}
} else {
var signalName = signalName(fieldName);
macro @:privateAccess $expr.$signalName.dispatch($a { args } );
}
}
@:extern inline function hasLazy(meta:MetadataEntry):Bool {
return meta.findParam(LAZY_SIGNAL).isNullOrTrue();
}
}
+241
View File
@@ -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;
}
}
}
+51
View File
@@ -0,0 +1,51 @@
package bindx.macro;
import haxe.macro.Context;
import haxe.macro.Expr.Position;
@:enum abstract WarnPriority(Int) to Int from Int {
var ALL = 2;
var INFO = 1;
var LOW = 0;
}
class Warn {
@:isVar static var level(get, null):WarnPriority = null;
static function get_level():WarnPriority {
if (Warn.level == null) {
Warn.level = Context.defined("bindx_log") ? Std.parseInt(Context.definedValue("bindx_log")) : LOW;
}
return Warn.level;
}
public static function w(msg:String, pos:Position, level:WarnPriority) {
if ((Warn.level : Int) >= (level : Int))
Context.warning(msg, pos);
}
}
class FatalError extends GenericError {}
class GenericError {
public var pos(default, null):Position;
public var message(default, null):String;
public function new(message:String, pos:Position) {
this.message = message;
this.pos = pos;
}
public function contextError():Void {
Context.error(message, pos);
}
public function contextWarning():Void {
Context.warning(message, pos);
}
public function contextFatal():Void {
Context.fatalError(message, pos);
}
}
+16
View File
@@ -0,0 +1,16 @@
package bindx.macro;
import haxe.macro.Expr;
import haxe.macro.Type;
interface IBindingSignalProvider {
function getFieldDispatcher(field:Field, result:Array<Field>):Void;
function getFieldChangedExpr(field:Field, oldValue:Expr, newValue:Expr):Expr;
function getClassFieldBindExpr(expr:Expr, field:ClassField, listener:Expr):Expr;
function getClassFieldBindToExpr(expr:Expr, field:ClassField, target:Expr):Expr;
function getClassFieldUnbindExpr(expr:Expr, field:ClassField, listener:Expr):Expr;
function getClassFieldChangedExpr(expr:Expr, field:ClassField, oldValue:Expr, newValue:Expr):Expr;
function getUnbindAllExpr(expr:ExprOf<IBindable>, type:Type):Expr;
}
+81
View File
@@ -0,0 +1,81 @@
package bindx.macro;
import haxe.macro.Type;
import haxe.macro.Expr;
import haxe.macro.Context;
using haxe.macro.Tools;
using Lambda;
class MetaUtils {
static public inline var BINDABLE_META = ":bindable";
static public inline function findParam(meta:MetadataEntry, name:String):Expr {
var res = null;
if (meta.params != null) for (p in meta.params) {
switch (p) {
case macro $e1 = $e2:
if (e1.toString() == name) res = { expr:e2.expr, pos:p.pos };
case {expr:EConst(CIdent(s))}:
if (s == name) res = { expr:(macro true).expr , pos: p.pos };
case _:
Context.warning('Bindable arguments syntax error. Supported syntax: (flag1=true, flag2=false, flag3)', p.pos);
}
if (res != null) break;
}
return res;
}
static public inline function bindableMeta(meta:Metadata):MetadataEntry {
var res = null;
for (m in meta) if (m.name == BINDABLE_META) {
res = m;
break;
}
return res;
}
}
class FieldMetaUtils {
static public inline function bindableMeta(field:Field):MetadataEntry
return MetaUtils.bindableMeta(field.meta);
static public inline function hasBindableMeta(field:Field):Bool
return bindableMeta(field) != null;
}
class ClassFieldMetaUtils {
static public inline function bindableMeta(field:ClassField):MetadataEntry
return MetaUtils.bindableMeta(field.meta.get());
static public inline function hasBindableMeta(field:ClassField):Bool
return bindableMeta(field) != null;
}
class ClassTypeMetaUtils {
static public inline function bindableMeta(classType:ClassType):MetadataEntry
return MetaUtils.bindableMeta(classType.meta.get());
static public inline function hasBindableMeta(classType:ClassType):Bool
return bindableMeta(classType) != null;
}
class ExprMetaUtils {
static public inline function isTrue(expr:Expr):Bool
return expr.expr.match(EConst(CIdent("true")));
static public inline function isFalse(expr:Expr):Bool
return expr.expr.match(EConst(CIdent("false")));
static public inline function isNullOrEmpty(expr:Expr):Bool {
return expr == null || expr.expr.match(EConst(CIdent("null")));
}
static public inline function isNotNullAndTrue(expr:Expr):Bool
return expr != null && isTrue(expr);
static public inline function isNullOrTrue(expr:Expr):Bool
return expr == null || isTrue(expr);
}