static Expression BindArrayAccess(Expression arr, Expression key, Expression ctx, AccessMask access, Expression rvalue) { Debug.Assert(key.Type == typeof(IntStringKey)); if (access.EnsureObject()) { // (arr ?? arr = []).EnsureItemObject(key) return(Expression.Call( EnsureNotNullPhpArray(arr), Cache.Operators.PhpArray_EnsureItemObject, key)); } else if (access.EnsureArray()) { // (arr ?? arr = []).EnsureItemArray(key) return(Expression.Call( EnsureNotNullPhpArray(arr), Cache.Operators.PhpArray_EnsureItemArray, key)); } else if (access.EnsureAlias()) { // (arr ?? arr = []).EnsureItemAlias(key) return(Expression.Call( EnsureNotNullPhpArray(arr), Cache.Operators.PhpArray_EnsureItemAlias, key)); } else if (access.WriteAlias()) { Debug.Assert(rvalue.Type == typeof(PhpAlias)); rvalue = ConvertExpression.Bind(rvalue, typeof(PhpAlias), ctx); // (arr ?? arr = []).SetItemAlias(key, value) return(Expression.Call( EnsureNotNullPhpArray(arr), Cache.Operators.PhpArray_SetItemAlias, key, rvalue)); } else if (access.Unset()) { Debug.Assert(rvalue == null); // remove key // arr.RemoveKey(name) // TODO: if (arr != null) return(Expression.Call(arr, Cache.Operators.PhpArray_RemoveKey, key)); } else if (access.Write()) { rvalue = ConvertExpression.Bind(rvalue, typeof(PhpValue), ctx); return(Expression.Call( EnsureNotNullPhpArray(arr), Cache.Operators.PhpArray_SetItemValue, key, rvalue)); } else { // read // TODO: (arr != null) ? arr[key] : (quiet ? void : ERROR) return(Expression.Call(arr, Cache.Operators.PhpArray_GetItemValue, key)); } }
/// <summary> /// Binds recursion check for property magic method. /// </summary> static Expression InvokeHandler(Expression ctx, Expression target, string field, Expression getter, AccessMask access, Expression @default = null, Type resultType = null) { // default resultType = resultType ?? Cache.Types.PhpValue[0]; @default = @default ?? Expression.Field(null, Cache.Properties.PhpValue_Null); // TODO: ERR field not found @default = ConvertExpression.Bind(@default, resultType, ctx); if (getter == null) { return(@default); } else { /* Template: * var token; * try { * return (token = new Context.RecursionCheckToken(_ctx, target, access))).IsInRecursion) * ? default * : getter; * } finally { * token.Dispose(); * } */ // recursion prevention key ~ do not invoke getter twice for the same field int subkey1 = access.Write() ? 1 : access.Unset() ? 2 : access.Isset() ? 3 : 4; int subkey = field.GetHashCode() ^ (1 << subkey1); // Template: RecursionCheckToken token; var tokenvar = Expression.Variable(typeof(Context.RecursionCheckToken), "token"); // Template: token = new RecursionCheckToken(_ctx, (object)target, (int)subkey)) var tokenassign = Expression.Assign(tokenvar, Expression.New(Cache.RecursionCheckToken.ctor_ctx_object_int, ctx, Expression.Convert(target, Cache.Types.Object[0]), Expression.Constant(subkey))); // bind getter access if (access.EnsureAlias() || access.EnsureArray() || access.EnsureObject()) { getter = BindAccess(getter, ctx, access, rvalue: null); } getter = ConvertExpression.Bind(getter, resultType, ctx); // return(Expression.Block(resultType, new[] { tokenvar }, Expression.TryFinally( Expression.Condition(Expression.Property(tokenassign, Cache.RecursionCheckToken.IsInRecursion), @default, getter), Expression.Call(tokenvar, Cache.RecursionCheckToken.Dispose) ))); } }
public static Expression BindField(PhpTypeInfo type, Type classCtx, Expression target, string field, Expression ctx, AccessMask access, Expression rvalue) { if (access.Write() != (rvalue != null)) { throw new ArgumentException(); } // lookup a declared field for (var t = type; t != null; t = t.BaseType) { foreach (var p in t.DeclaredFields.GetPhpProperties(field)) { if (p.IsStatic == (target == null) && p.IsVisible(classCtx)) { return(BindAccess(p.Bind(ctx, target), ctx, access, rvalue)); } } } // // runtime fields // if (type.RuntimeFieldsHolder != null) // we don't handle magic methods without the runtime fields { var runtimeflds = Expression.Field(target, type.RuntimeFieldsHolder); // Template: target->__runtime_fields var fieldkey = Expression.Constant(new IntStringKey(field)); // Template: IntStringKey(field) var resultvar = Expression.Variable(Cache.Types.PhpValue[0], "result"); // Template: PhpValue result; // Template: runtimeflds != null && runtimeflds.TryGetValue(field, out result) var trygetfield = Expression.AndAlso(Expression.ReferenceNotEqual(runtimeflds, Expression.Constant(null)), Expression.Call(runtimeflds, Cache.Operators.PhpArray_TryGetValue, fieldkey, resultvar)); var containsfield = Expression.AndAlso(Expression.ReferenceNotEqual(runtimeflds, Expression.Constant(null)), Expression.Call(runtimeflds, Cache.Operators.PhpArray_ContainsKey, fieldkey)); Expression result; // if (access.EnsureObject()) { // (object)target->field-> // Template: runtimeflds.EnsureObject(key) result = Expression.Call(EnsureNotNullPhpArray(runtimeflds), Cache.Operators.PhpArray_EnsureItemObject, fieldkey); var __get = BindMagicMethod(type, classCtx, target, ctx, TypeMethods.MagicMethods.__get, field, null); if (__get != null) { // Template: runtimeflds.Contains(key) ? runtimeflds.EnsureObject(key) : ( __get(key) ?? runtimeflds.EnsureObject(key)) return(Expression.Condition(containsfield, Expression.Call(runtimeflds, Cache.Operators.PhpArray_EnsureItemObject, fieldkey), InvokeHandler(ctx, target, field, __get, access, result, typeof(object)))); } else { return(result); } } else if (access.EnsureArray()) { // (IPhpArray)target->field[] = result = Expression.Call(EnsureNotNullPhpArray(runtimeflds), Cache.Operators.PhpArray_EnsureItemArray, fieldkey); var __get = BindMagicMethod(type, classCtx, target, ctx, TypeMethods.MagicMethods.__get, field, null); if (__get != null) { // Template: runtimeflds.Contains(key) ? runtimeflds.EnsureArray(key) : ( __get(key) ?? runtimeflds.EnsureArray(key)) return(Expression.Condition(containsfield, Expression.Call(runtimeflds, Cache.Operators.PhpArray_EnsureItemArray, fieldkey), InvokeHandler(ctx, target, field, __get, access, result, typeof(IPhpArray)))); } else { // runtimeflds.EnsureItemArray(key) return(result); } } else if (access.EnsureAlias()) { // (PhpAlias)&target->field result = Expression.Call(EnsureNotNullPhpArray(runtimeflds), Cache.Operators.PhpArray_EnsureItemAlias, fieldkey); var __get = BindMagicMethod(type, classCtx, target, ctx, TypeMethods.MagicMethods.__get, field, null); if (__get != null) { // Template: runtimeflds.Contains(key) ? runtimeflds.EnsureItemAlias(key) : ( __get(key) ?? runtimeflds.EnsureItemAlias(key)) return(Expression.Condition(containsfield, Expression.Call(runtimeflds, Cache.Operators.PhpArray_EnsureItemAlias, fieldkey), InvokeHandler(ctx, target, field, __get, access, result, typeof(PhpAlias)))); } else { // runtimeflds.EnsureItemAlias(key) return(result); } } else if (access.Unset()) { // unset(target->field) // Template: if (!runtimeflds.RemoveKey(key)) __unset(key) var removekey = Expression.Call(runtimeflds, Cache.Operators.PhpArray_RemoveKey, fieldkey); var __unset = BindMagicMethod(type, classCtx, target, ctx, TypeMethods.MagicMethods.__unset, field, null); if (__unset != null) { return(Expression.IfThen( Expression.OrElse(Expression.ReferenceEqual(runtimeflds, Expression.Constant(null)), Expression.IsFalse(removekey)), InvokeHandler(ctx, target, field, __unset, access, Expression.Block(), typeof(void)))); } else { // if (runtimeflds != null) runtimeflds.RemoveKey(key) return(Expression.IfThen( Expression.ReferenceNotEqual(runtimeflds, Expression.Constant(null)), removekey)); } } else if (access.Write()) { var __set = BindMagicMethod(type, classCtx, target, ctx, TypeMethods.MagicMethods.__set, field, rvalue); if (access.WriteAlias()) { // target->field = (PhpAlias)&rvalue Debug.Assert(rvalue.Type == typeof(PhpAlias)); rvalue = ConvertExpression.Bind(rvalue, typeof(PhpAlias), ctx); // EnsureNotNull(runtimeflds).SetItemAlias(key, rvalue) result = Expression.Call(EnsureNotNullPhpArray(runtimeflds), Cache.Operators.PhpArray_SetItemAlias, fieldkey, rvalue); if (__set != null) { // if (ContainsKey(key)) ? runtimeflds.SetItemAlias(rvalue) : (__set(key, rvalue) ?? runtimeflds.SetItemAlias(key, rvalue) return(Expression.Condition(containsfield, Expression.Call(runtimeflds, Cache.Operators.PhpArray_SetItemAlias, fieldkey, rvalue), InvokeHandler(ctx, target, field, __set, access, result, typeof(void)))); } else { return(result); } } else { // target->field = rvalue rvalue = ConvertExpression.Bind(rvalue, typeof(PhpValue), ctx); /* Template: * return runtimeflds != null && runtimeflds.ContainsKey(field) * ? runtimeflds.SetItemValue(key, rvalue) * : (__set(field, value) ?? runtimeflds.SetItemValue(key, value)) */ result = Expression.Call(EnsureNotNullPhpArray(runtimeflds), Cache.Operators.PhpArray_SetItemValue, fieldkey, rvalue); if (__set != null) { return(Expression.Condition(containsfield, Expression.Call(runtimeflds, Cache.Operators.PhpArray_SetItemValue, fieldkey, rvalue), InvokeHandler(ctx, target, field, __set, access, result, typeof(void)))); } else { return(result); } } } else if (access.Isset()) { // isset(target->field) var __isset = BindMagicMethod(type, classCtx, target, ctx, TypeMethods.MagicMethods.__isset, field, null); // Template: (TryGetField(result) || (bool)__isset(key)) ? true : void result = Expression.Condition(Expression.OrElse( trygetfield, ConvertExpression.BindToBool(InvokeHandler(ctx, target, field, __isset, access))), Expression.Property(null, Cache.Properties.PhpValue_True), Expression.Field(null, Cache.Properties.PhpValue_Void)); } else { // = target->field /* Template: * return runtimeflds.TryGetValue(field, out result) ? result : (__get(field) ?? ERR); */ var __get = BindMagicMethod(type, classCtx, target, ctx, TypeMethods.MagicMethods.__get, field, null); result = Expression.Condition(trygetfield, resultvar, InvokeHandler(ctx, target, field, __get, access)); // TODO: @default = { ThrowError; return null; } } // return(Expression.Block(result.Type, new[] { resultvar }, result)); } // TODO: IDynamicMetaObject // return(null); }
static Expression BindAccess(Expression expr, Expression ctx, AccessMask access, Expression rvalue) { if (access.EnsureObject()) { if (expr.Type == typeof(PhpAlias)) { // ((PhpAlias)fld).EnsureObject() expr = Expression.Call(expr, Cache.Operators.PhpAlias_EnsureObject); } else if (expr.Type == typeof(PhpValue)) { // ((PhpValue)fld).EnsureObject() expr = Expression.Call(expr, Cache.Operators.PhpValue_EnsureObject); } else { // getter // TODO: ensure it is not null Debug.Assert(!expr.Type.GetTypeInfo().IsValueType); } } else if (access.EnsureArray()) { if (expr.Type == typeof(PhpAlias)) { // ((PhpAlias)fld).EnsureArray() expr = Expression.Call(expr, Cache.Operators.PhpAlias_EnsureArray); } else if (expr.Type == typeof(PhpValue)) { // ((PhpValue)fld).EnsureArray() expr = Expression.Call(expr, Cache.Operators.PhpValue_EnsureArray); } else if (expr.Type == typeof(PhpArray)) { // (PhpArray)fld // TODO: ensure it is not null } else { // getter } } else if (access.EnsureAlias()) { if (expr.Type == typeof(PhpAlias)) { // (PhpAlias)getter } else if (expr.Type == typeof(PhpValue)) { // ((PhpValue)fld).EnsureAlias() expr = Expression.Call(expr, Cache.Operators.PhpValue_EnsureAlias); } else { // getter // cannot read as reference } } else if (access.WriteAlias()) { // write alias Debug.Assert(rvalue.Type == typeof(PhpAlias)); rvalue = ConvertExpression.Bind(rvalue, typeof(PhpAlias), ctx); if (expr.Type == typeof(PhpAlias)) { // ok } else if (expr.Type == typeof(PhpValue)) { // fld = PhpValue.Create(alias) rvalue = Expression.Call(typeof(PhpValue).GetMethod("Create", Cache.Types.PhpAlias), rvalue); } else { // fld is not aliasable Debug.Assert(false, "Cannot assign aliased value to field of type " + expr.Type.ToString()); rvalue = ConvertExpression.Bind(rvalue, expr.Type, ctx); } expr = Expression.Assign(expr, rvalue); } else if (access.Unset()) { Debug.Assert(rvalue == null); expr = Expression.Assign(expr, ConvertExpression.BindDefault(expr.Type)); } else if (access.Write()) { // write by value if (expr.Type == typeof(PhpAlias)) { // Template: fld.Value = (PhpValue)value expr = Expression.Assign(Expression.PropertyOrField(expr, "Value"), ConvertExpression.Bind(rvalue, typeof(PhpValue), ctx)); } else if (expr.Type == typeof(PhpValue)) { // Template: Operators.SetValue(ref fld, (PhpValue)value) expr = Expression.Call(Cache.Operators.SetValue_PhpValueRef_PhpValue, expr, ConvertExpression.Bind(rvalue, typeof(PhpValue), ctx)); } else { // Template: fld = value // default behaviour by value to value expr = Expression.Assign(expr, ConvertExpression.Bind(rvalue, expr.Type, ctx)); } } else if (access.ReadCopy()) { // dereference & copy if (expr.Type == typeof(PhpValue)) { // Template: value.GetValue().DeepCopy() expr = Expression.Call(Expression.Call(expr, Cache.Operators.PhpValue_GetValue), Cache.Operators.PhpValue_DeepCopy); } else if (expr.Type == typeof(PhpAlias)) { } } // return(expr); }
static Expression BindAccess(Expression expr, Expression ctx, AccessMask access, Expression rvalue) { if (access.EnsureObject()) { if (expr.Type == typeof(PhpAlias)) { // ((PhpAlias)fld).EnsureObject() expr = Expression.Call(expr, Cache.Operators.PhpAlias_EnsureObject); } else if (expr.Type == typeof(PhpValue)) { // ((PhpValue)fld).EnsureObject() expr = Expression.Call(expr, Cache.Operators.PhpValue_EnsureObject); } else { // getter // TODO: ensure it is not null Debug.Assert(!expr.Type.GetTypeInfo().IsValueType); } } else if (access.EnsureArray()) { if (expr.Type == typeof(PhpAlias)) { // ((PhpAlias)fld).EnsureArray() expr = Expression.Call(expr, Cache.Operators.PhpAlias_EnsureArray); } else if (expr.Type == typeof(PhpValue)) { // ((PhpValue)fld).EnsureArray() expr = Expression.Call(expr, Cache.Operators.PhpValue_EnsureArray); } else if (expr.Type == typeof(PhpArray)) { // (PhpArray)fld // TODO: ensure it is not null } else { // Operators.EnsureArray( fld ) // TODO: string expr = Expression.Call(Cache.Operators.Object_EnsureArray, expr); } } else if (access.EnsureAlias()) { if (expr.Type == typeof(PhpAlias)) { // (PhpAlias)getter } else if (expr.Type == typeof(PhpValue)) { // ((PhpValue)fld).EnsureAlias() expr = Expression.Call(expr, Cache.Operators.PhpValue_EnsureAlias); } else { // getter // cannot read as reference } } else if (access.WriteAlias()) { // write alias Debug.Assert(rvalue.Type == typeof(PhpAlias)); rvalue = ConvertExpression.Bind(rvalue, typeof(PhpAlias), ctx); if (expr.Type == typeof(PhpAlias)) { // ok } else if (expr.Type == typeof(PhpValue)) { // fld = PhpValue.Create(alias) rvalue = Expression.Call(typeof(PhpValue).GetMethod("Create", Cache.Types.PhpAlias), rvalue); } else { // fld is not aliasable Debug.Assert(false, "Cannot assign aliased value to field of type " + expr.Type.ToString()); rvalue = ConvertExpression.Bind(rvalue, expr.Type, ctx); } expr = Expression.Assign(expr, rvalue); } else if (access.Unset()) { Debug.Assert(rvalue == null); expr = Expression.Assign(expr, ConvertExpression.BindDefault(expr.Type)); } else if (access.Write()) { // write by value if (expr.Type == typeof(PhpAlias)) { // Template: fld.Value = (PhpValue)value expr = Expression.Assign(Expression.Field(expr, Cache.PhpAlias.Value), ConvertExpression.Bind(rvalue, typeof(PhpValue), ctx)); } else if (expr.Type == typeof(PhpValue)) { // Template: Operators.SetValue(ref fld, (PhpValue)value) expr = Expression.Call(Cache.Operators.SetValue_PhpValueRef_PhpValue, expr, ConvertExpression.Bind(rvalue, typeof(PhpValue), ctx)); } else { // Template: fld = value // default behaviour by value to value expr = Expression.Assign(expr, ConvertExpression.Bind(rvalue, expr.Type, ctx)); } } else if (access.ReadValue()) { // dereference if (expr.Type == typeof(PhpValue)) { // Template: value.GetValue().DeepCopy() expr = Expression.Call(expr, Cache.Operators.PhpValue_GetValue); } else if (expr.Type == typeof(PhpAlias)) { // Template: alias.Value // expecting alias cannot be null ref expr = Expression.Field(expr, Cache.PhpAlias.Value); } } else if (access.ReadValueCopy()) { // dereference & copy if (expr.Type == typeof(PhpValue)) { // Template: value.GetValue().DeepCopy() expr = Expression.Call(Expression.Call(expr, Cache.Operators.PhpValue_GetValue), Cache.Operators.PhpValue_DeepCopy); } else if (expr.Type == typeof(PhpAlias)) { // TODO: specify - ReadCopy | ReadValue | ReadValueCopy - currently not consistent } } else if (access.Isset()) { if (expr.Type == typeof(PhpAlias)) { expr = Expression.Field(expr, Cache.PhpAlias.Value); } // if (expr.Type == typeof(PhpValue)) { // Template: Operators.IsSet( value ) expr = Expression.Call(Cache.Operators.IsSet_PhpValue, expr); } else if (!expr.Type.GetTypeInfo().IsValueType) { // Template: value != null expr = Expression.ReferenceNotEqual(expr, Expression.Constant(null, typeof(object))); } else { // if there is bound typed symbol, it is always set: expr = Expression.Constant(true, typeof(bool)); } } // Read, IsSet return(expr); }
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args) { bool hasTargetInstance = (target.LimitType != typeof(TargetTypeParam)); var bound = new CallSiteContext(!hasTargetInstance) { ClassContext = _classContext, Name = _name } .ProcessArgs(target, args, hasTargetInstance); if (hasTargetInstance) { var isobject = bound.TargetType != null; if (isobject == false) { var defaultexpr = ConvertExpression.BindDefault(_returnType); if (!_access.Quiet()) { // PhpException.VariableMisusedAsObject(target, _access.ReadRef) var throwcall = BinderHelpers.VariableMisusedAsObject(target.Expression, _access.EnsureAlias()); defaultexpr = Expression.Block(throwcall, defaultexpr); } return(new DynamicMetaObject(defaultexpr, bound.Restrictions)); } // instance := (T)instance bound.TargetInstance = Expression.Convert(bound.TargetInstance, bound.TargetType.Type); } Debug.Assert(IsClassConst ? (bound.TargetInstance == null) : true); // var getter = IsClassConst ? BinderHelpers.BindClassConstant(bound.TargetType, bound.ClassContext, bound.Name, bound.Context) : BinderHelpers.BindField(bound.TargetType, bound.ClassContext, bound.TargetInstance, bound.Name, bound.Context, _access, null); if (getter != null) { // return(new DynamicMetaObject(ConvertExpression.Bind(getter, _returnType, bound.Context), bound.Restrictions)); } if (IsClassConst) { // error: constant not defined // ... } // unreachable: property not found throw new InvalidOperationException($"{bound.TargetType.Name}::{bound.Name} could not be resolved."); }
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args) { bool hasTargetInstance = (target.LimitType != typeof(TargetTypeParam)); var bound = new CallSiteContext() { ClassContext = _classContext, Name = _name } .ProcessArgs(target, args, hasTargetInstance); if (hasTargetInstance) { var isobject = bound.TargetType != null; if (isobject == false) { var defaultexpr = ConvertExpression.BindDefault(_returnType); if (!_access.Quiet()) { // PhpException.VariableMisusedAsObject(target, _access.ReadRef) var throwcall = Expression.Call(typeof(PhpException), "VariableMisusedAsObject", Array.Empty <Type>(), ConvertExpression.BindToValue(target.Expression), Expression.Constant(_access.EnsureAlias())); defaultexpr = Expression.Block(throwcall, defaultexpr); } return(new DynamicMetaObject(defaultexpr, bound.Restrictions)); } // instance := (T)instance bound.TargetInstance = Expression.Convert(bound.TargetInstance, bound.TargetType.Type.AsType()); } Debug.Assert(IsClassConst ? (bound.TargetInstance == null) : true); // var getter = IsClassConst ? BinderHelpers.BindClassConstant(bound.TargetType, bound.ClassContext, bound.Name, bound.Context) : BinderHelpers.BindField(bound.TargetType, bound.ClassContext, bound.TargetInstance, bound.Name, bound.Context, _access, null); if (getter != null) { // return(new DynamicMetaObject(ConvertExpression.Bind(getter, _returnType, bound.Context), bound.Restrictions)); } // field not found throw new NotImplementedException(); }
public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args) { var restrictions = BindingRestrictions.Empty; PhpTypeInfo phptype; Expression target_expr; // var ctx = args[0]; var fldName = ResolveName(args, ref restrictions); // if (target.LimitType == typeof(PhpTypeInfo)) // static field { target_expr = null; phptype = (PhpTypeInfo)target.Value; // restrictions = restrictions.Merge(BindingRestrictions.GetInstanceRestriction(target.Expression, phptype)); } else { // instance field object target_value; BinderHelpers.TargetAsObject(target, out target_expr, out target_value, ref restrictions); if (target_value == null) { var defaultexpr = ConvertExpression.BindDefault(_returnType); if (!_access.Quiet()) { // PhpException.VariableMisusedAsObject(target, _access.ReadRef) var throwcall = Expression.Call(typeof(PhpException), "VariableMisusedAsObject", Array.Empty <Type>(), ConvertExpression.BindToValue(target.Expression), Expression.Constant(_access.EnsureAlias())); defaultexpr = Expression.Block(throwcall, defaultexpr); } return(new DynamicMetaObject(defaultexpr, restrictions)); } var runtime_type = target_value.GetType(); // if (target_expr.Type != runtime_type) { restrictions = restrictions.Merge(BindingRestrictions.GetTypeRestriction(target_expr, runtime_type)); target_expr = Expression.Convert(target_expr, runtime_type); } phptype = runtime_type.GetPhpTypeInfo(); } Debug.Assert(IsClassConst == (target_expr == null)); // var getter = IsClassConst ? BinderHelpers.BindClassConstant(phptype, _classContext, fldName, ctx.Expression) : BinderHelpers.BindField(phptype, _classContext, target_expr, fldName, ctx.Expression, _access, null); if (getter != null) { // return(new DynamicMetaObject(ConvertExpression.Bind(getter, _returnType, ctx.Expression), restrictions)); } // field not found throw new NotImplementedException(); }