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
+6
View File
@@ -0,0 +1,6 @@
-main Tests
-cp src
-cp test
-D dump=pretty
-neko bin/test.n
-lib buddy
+7 -108
View File
@@ -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);
}
#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 BindMacros.internalUnbindAll(object);
}
}
return res;
}
#end
}
+6 -342
View File
@@ -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);
}
#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;
return BindxExtMacro.internalBindChain(expr, macro function (_, to:Null<$type>) $target = to);
}
}
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
}
-167
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
package bindx;
@:autoBuild(bindx.BindMacros.buildIBindable())
@:autoBuild(bindx.macro.BindableMacros.buildIBindable())
interface IBindable {
}
+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();
}
}
@@ -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;
@@ -51,4 +49,3 @@ class GenericError {
Context.fatalError(message, pos);
}
}
#end
@@ -1,6 +1,4 @@
package bindx;
#if macro
package bindx.macro;
import haxe.macro.Expr;
import haxe.macro.Type;
@@ -16,4 +14,3 @@ interface IBindingSignalProvider {
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;
@@ -80,5 +79,3 @@ class ExprMetaUtils {
static public inline function isNullOrTrue(expr:Expr):Bool
return expr == null || isTrue(expr);
}
#end
+1 -1
View File
@@ -2,5 +2,5 @@
-cp src
-cp test
-D dump=pretty
-neko bin/test.n
--interp
-lib buddy