package bindx; #if macro import bindx.Error; import haxe.macro.Expr; import haxe.macro.Type; import haxe.macro.Context; using haxe.macro.Tools; using bindx.MetaUtils; using Lambda; #end @:access(bindx.BindMacros) class Bind { @:noUsing macro static public function bind(field:Expr, listener:Expr):Expr { return internalBind(field, listener, true); } @:noUsing macro static public function bindTo(field:Expr, target:Expr):Expr { return internalBindTo(field, target); } @:noUsing macro static public function unbind(field:Expr, ?listener:Expr):Expr { return internalBind(field, listener, false); } @:noUsing macro static public function notify(field:Expr, ?oldValue:Expr, ?newValue:Expr):Expr { return internalNotify(field, oldValue, newValue); } @:noUsing macro static public function unbindAll(object:ExprOf):Expr { return internalUnbindAll(object); } #if macro public static function internalBind(field:Expr, listener:Expr, doBind:Bool):Expr { var fieldData = checkField(field); return if (fieldData != null) { if (doBind) BindMacros.bindingSignalProvider.getClassFieldBindExpr(fieldData.e, fieldData.field, listener); else BindMacros.bindingSignalProvider.getClassFieldUnbindExpr(fieldData.e, fieldData.field, listener); } else macro {}; } public static function internalBindTo(field:Expr, target:Expr):Expr { var fieldData = checkField(field); return BindMacros.bindingSignalProvider.getClassFieldBindToExpr(fieldData.e, fieldData.field, target); } public static function internalNotify(field:Expr, ?oldValue:Expr, ?newValue:Expr):Expr { var fieldData = checkField(field); return BindMacros.bindingSignalProvider.getClassFieldChangedExpr(fieldData.e, fieldData.field, oldValue, newValue); } public static function internalUnbindAll(object:ExprOf):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); } public static function checkField(field:Expr):{e:Expr, field:ClassField} { return try { tryCheckField(field); } catch (e:bindx.Error) { e.contextError(); null; }; } public static function tryCheckField(field:Expr):{e:Expr, field:ClassField} { switch (field.expr) { case EField(e, field): var classType = Context.typeof(e).follow().getClass(); if (classType == null) { throw new FatalError('Type \'${e.toString()}\' is unknown', e.pos); return null; } if (!isBindable(classType)) { throw new Error('\'${e.toString()}\' must be bindx.IBindable', e.pos); return null; } var field:ClassField = classType.findField(field, null); if (field == null) { throw new FatalError('\'${e.toString()}.${field.name}\' expected', field.pos); return null; } if (!field.hasBindableMeta()) { throw new Error('\'${e.toString()}.${field.name}\' is not bindable', field.pos); return null; } return {e:e, field:field}; case EConst(CIdent(_)): throw new Error('can\'t bind \'${field.toString()}\'. Please use \'this.${field.toString()}\'', field.pos); case _: throw new Error('can\'t bind field \'${field.toString()}\'', field.pos); } return null; } public static function isBindable(classType:ClassType):Bool { if (classType.module == "bindx.IBindable" && classType.name == "IBindable") return true; var t = classType; while (t != null) { for (it in t.interfaces) if (isBindable(it.t.get())) return true; t = t.superClass != null ? t.superClass.t.get() : null; } return false; } #end }