refactoring
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
-main Tests
|
||||
-cp src
|
||||
-cp test
|
||||
-D dump=pretty
|
||||
-neko bin/test.n
|
||||
-lib buddy
|
||||
+7
-108
@@ -1,129 +1,28 @@
|
||||
package bindx;
|
||||
|
||||
#if macro
|
||||
import bindx.GenericError;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Context;
|
||||
import bindx.macro.BindMacros;
|
||||
|
||||
using haxe.macro.Tools;
|
||||
using bindx.MetaUtils;
|
||||
using Lambda;
|
||||
#end
|
||||
|
||||
|
||||
@:access(bindx.BindMacros)
|
||||
@:access(bindx.macro.BindMacros)
|
||||
class Bind {
|
||||
|
||||
@:noUsing macro static public function bind(field:Expr, listener:Expr):Expr {
|
||||
return internalBind(field, listener, true);
|
||||
return BindMacros.internalBind(field, listener, true);
|
||||
}
|
||||
|
||||
@:noUsing macro static public function bindTo(field:Expr, target:Expr):Expr {
|
||||
return internalBindTo(field, target);
|
||||
return BindMacros.internalBindTo(field, target);
|
||||
}
|
||||
|
||||
@:noUsing macro static public function unbind(field:Expr, ?listener:Expr):Expr {
|
||||
return internalBind(field, listener, false);
|
||||
return BindMacros.internalBind(field, listener, false);
|
||||
}
|
||||
|
||||
@:noUsing macro static public function notify(field:Expr, ?oldValue:Expr, ?newValue:Expr):Expr {
|
||||
return internalNotify(field, oldValue, newValue);
|
||||
return BindMacros.internalNotify(field, oldValue, newValue);
|
||||
}
|
||||
|
||||
@:noUsing macro static public function unbindAll(object:ExprOf<IBindable>):Expr {
|
||||
return internalUnbindAll(object);
|
||||
return BindMacros.internalUnbindAll(object);
|
||||
}
|
||||
|
||||
#if macro
|
||||
|
||||
static inline function internalBind(field:Expr, listener:Expr, doBind:Bool):Expr {
|
||||
var fieldData = warnCheckField(field);
|
||||
return if (doBind) BindMacros.bindingSignalProvider.getClassFieldBindExpr(fieldData.e, fieldData.field, listener);
|
||||
else BindMacros.bindingSignalProvider.getClassFieldUnbindExpr(fieldData.e, fieldData.field, listener);
|
||||
}
|
||||
|
||||
static inline function internalBindTo(field:Expr, target:Expr):Expr {
|
||||
var fieldData = warnCheckField(field);
|
||||
return BindMacros.bindingSignalProvider.getClassFieldBindToExpr(fieldData.e, fieldData.field, target);
|
||||
}
|
||||
|
||||
static inline function internalNotify(field:Expr, ?oldValue:Expr, ?newValue:Expr):Expr {
|
||||
var fieldData = warnCheckField(field);
|
||||
return BindMacros.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 BindMacros.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;
|
||||
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
|
||||
}
|
||||
+6
-342
@@ -1,365 +1,29 @@
|
||||
package bindx;
|
||||
|
||||
#if macro
|
||||
|
||||
import bindx.GenericError;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.MacroStringTools;
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Printer;
|
||||
import bindx.macro.BindExtMacros;
|
||||
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
using haxe.macro.Tools;
|
||||
|
||||
typedef FieldExpr = {
|
||||
var field:ClassField;
|
||||
var bindable:Bool;
|
||||
var e:Expr;
|
||||
@:optional var params:Array<Expr>;
|
||||
}
|
||||
|
||||
typedef Chain = {
|
||||
var init:Array<Expr>;
|
||||
var bind:Array<Expr>;
|
||||
var unbind:Array<Expr>;
|
||||
var expr:Expr;
|
||||
}
|
||||
#end
|
||||
|
||||
@:access(bindx.BindMacros)
|
||||
@:access(bindx.Bind)
|
||||
@:access(bindx.macro.BindxExtMacro)
|
||||
class BindExt {
|
||||
|
||||
@:noUsing macro static public function expr<T>(expr:ExprOf<T>, listener:ExprOf<Null<T>->Null<T>->Void>):ExprOf<Void->Void> {
|
||||
return internalBindExpr(expr, listener);
|
||||
return BindxExtMacro.internalBindExpr(expr, listener);
|
||||
}
|
||||
|
||||
@:noUsing macro static public function exprTo<T>(expr:ExprOf<T>, target:ExprOf<T>):ExprOf<Void->Void> {
|
||||
var type = Context.typeof(expr).toComplexType();
|
||||
return internalBindExpr(expr, macro function (_, to:Null<$type>) $target = to);
|
||||
return BindxExtMacro.internalBindExpr(expr, macro function (_, to:Null<$type>) $target = to);
|
||||
}
|
||||
|
||||
@:noUsing macro static public function chain<T>(expr:ExprOf<T>, listener:Expr):ExprOf<Void->Void> {
|
||||
return internalBindChain(expr, listener);
|
||||
return BindxExtMacro.internalBindChain(expr, listener);
|
||||
}
|
||||
|
||||
@:noUsing macro static public function chainTo<T>(expr:ExprOf<T>, target:ExprOf<T>):ExprOf<Void->Void> {
|
||||
var type = Context.typeof(expr).toComplexType();
|
||||
return internalBindChain(expr, macro function (_, to:Null<$type>) $target = to);
|
||||
return BindxExtMacro.internalBindChain(expr, macro function (_, to:Null<$type>) $target = to);
|
||||
}
|
||||
|
||||
#if macro
|
||||
|
||||
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 = Bind.checkField(expr);
|
||||
var firstParams;
|
||||
if (first.field == null) {
|
||||
switch (expr.expr) {
|
||||
case ECall(e, params):
|
||||
first = Bind.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 = Bind.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 = Bind.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 = BindMacros.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)
|
||||
$ { BindMacros.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)
|
||||
${BindMacros.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(BindMacros.bindingSignalProvider.getClassFieldBindExpr(macro $i{zeroName}, zeroListener.f.field, zeroListener.l ));
|
||||
res.unbind.push(BindMacros.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
|
||||
}
|
||||
@@ -1,172 +1,5 @@
|
||||
package bindx;
|
||||
|
||||
#if macro
|
||||
|
||||
import bindx.BindSignal.BindSignalProvider;
|
||||
import bindx.BindSignal.Signal;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Context;
|
||||
|
||||
using bindx.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();
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
|
||||
class MethodSignal extends Signal<Void -> Void> {
|
||||
|
||||
public function dispatch():Void {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package bindx;
|
||||
|
||||
@:autoBuild(bindx.BindMacros.buildIBindable())
|
||||
@:autoBuild(bindx.macro.BindableMacros.buildIBindable())
|
||||
interface IBindable {
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,17 @@
|
||||
package bindx;
|
||||
package bindx.macro;
|
||||
|
||||
import bindx.GenericError;
|
||||
import bindx.macro.GenericError;
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.TypeTools;
|
||||
|
||||
using haxe.macro.Tools;
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
using bindx.MetaUtils;
|
||||
using haxe.macro.Tools;
|
||||
using bindx.macro.MetaUtils;
|
||||
|
||||
class BindableMacros {
|
||||
|
||||
class BindMacros {
|
||||
#if macro
|
||||
static inline var OLD_VALUE = "__oldValue__";
|
||||
static inline var NEW_VALUE = "__newValue__";
|
||||
|
||||
@@ -48,7 +46,7 @@ class BindMacros {
|
||||
});
|
||||
|
||||
if (bindingSignalProvider == null) {
|
||||
bindingSignalProvider = new bindx.BindSignal.BindSignalProvider();
|
||||
bindingSignalProvider = new bindx.macro.BindSignalProvider();
|
||||
}
|
||||
|
||||
var fields = Context.getBuildFields();
|
||||
@@ -240,5 +238,4 @@ class BindMacros {
|
||||
case _: true;
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
package bindx;
|
||||
|
||||
#if macro
|
||||
package bindx.macro;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr.Position;
|
||||
@@ -50,5 +48,4 @@ class GenericError {
|
||||
public function contextFatal():Void {
|
||||
Context.fatalError(message, pos);
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
package bindx;
|
||||
|
||||
#if macro
|
||||
package bindx.macro;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
@@ -15,5 +13,4 @@ interface IBindingSignalProvider {
|
||||
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;
|
||||
}
|
||||
#end
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package bindx;
|
||||
package bindx.macro;
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
@@ -79,6 +78,4 @@ class ExprMetaUtils {
|
||||
|
||||
static public inline function isNullOrTrue(expr:Expr):Bool
|
||||
return expr == null || isTrue(expr);
|
||||
}
|
||||
|
||||
#end
|
||||
}
|
||||
Reference in New Issue
Block a user