using System; using System.Collections.Generic; using System.Dynamic; using System.Linq.Expressions; using AspClassic.Scripting.Utils; namespace AspClassic.Scripting; public sealed class ScopeStorage : IDynamicMetaObjectProvider { private class Meta : DynamicMetaObject { public new ScopeStorage Value => (ScopeStorage)base.Value; public Meta(Expression parameter, ScopeStorage storage) : base(parameter, BindingRestrictions.Empty, storage) { } public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { return DynamicTryGetValue(binder.Name, binder.IgnoreCase, binder.FallbackGetMember(this).Expression, (Expression tmp) => tmp); } public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { return DynamicTryGetValue(binder.Name, binder.IgnoreCase, binder.FallbackInvokeMember(this, args).Expression, (Expression tmp) => binder.FallbackInvoke(new DynamicMetaObject(tmp, BindingRestrictions.Empty), args, null).Expression); } private DynamicMetaObject DynamicTryGetValue(string name, bool ignoreCase, Expression fallback, Func resultOp) { IScopeVariable variable = Value.GetVariable(name, ignoreCase); ParameterExpression parameterExpression = Expression.Parameter(typeof(object)); return new DynamicMetaObject(Expression.Block(new ParameterExpression[1] { parameterExpression }, Expression.Condition(Expression.Call(Variable(variable), variable.GetType().GetMethod("TryGetValue"), parameterExpression), ExpressionUtils.Convert(resultOp(parameterExpression), typeof(object)), ExpressionUtils.Convert(fallback, typeof(object)))), BindingRestrictions.GetInstanceRestriction(base.Expression, Value)); } private static Expression Variable(IScopeVariable variable) { return Expression.Convert(Expression.Property(Expression.Constant(((IWeakReferencable)variable).WeakReference), typeof(WeakReference).GetProperty("Target")), variable.GetType()); } public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { IScopeVariable variable = Value.GetVariable(binder.Name, binder.IgnoreCase); Expression expression = ExpressionUtils.Convert(value.Expression, typeof(object)); return new DynamicMetaObject(Expression.Block(Expression.Call(Variable(variable), variable.GetType().GetMethod("SetValue"), expression), expression), BindingRestrictions.GetInstanceRestriction(base.Expression, Value)); } public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) { IScopeVariable variable = Value.GetVariable(binder.Name, binder.IgnoreCase); return new DynamicMetaObject(Expression.Condition(Expression.Call(Variable(variable), variable.GetType().GetMethod("DeleteValue")), Expression.Default(binder.ReturnType), binder.FallbackDeleteMember(this).Expression), BindingRestrictions.GetInstanceRestriction(base.Expression, Value)); } public override IEnumerable GetDynamicMemberNames() { return Value.GetMemberNames(); } } private readonly Dictionary _storage = new Dictionary(StringComparer.OrdinalIgnoreCase); public dynamic this[string index] { get { return GetValue(index, ignoreCase: false); } set { SetValue(index, ignoreCase: false, (object)value); } } public dynamic GetValue(string name, bool ignoreCase) { if (GetVariable(name, ignoreCase).TryGetValue(out var value)) { return value; } throw new KeyNotFoundException("no value"); } public bool TryGetValue(string name, bool ignoreCase, out dynamic value) { if (HasVariable(name) && GetVariable(name, ignoreCase).TryGetValue(out var value2)) { value = value2; return true; } value = null; return false; } public void SetValue(string name, bool ignoreCase, object value) { GetVariable(name, ignoreCase).SetValue(value); } public bool DeleteValue(string name, bool ignoreCase) { if (!HasVariable(name)) { return false; } return GetVariable(name, ignoreCase).DeleteValue(); } public bool HasValue(string name, bool ignoreCase) { if (!HasVariable(name)) { return false; } return GetVariable(name, ignoreCase).HasValue; } public IScopeVariable GetVariable(string name, bool ignoreCase) { if (ignoreCase) { return GetVariableIgnoreCase(name); } return GetVariable(name); } public ScopeVariable GetVariable(string name) { return GetVariableIgnoreCase(name).GetCaseSensitiveStorage(name); } public ScopeVariableIgnoreCase GetVariableIgnoreCase(string name) { lock (_storage) { if (!_storage.TryGetValue(name, out var value)) { value = (_storage[name] = new ScopeVariableIgnoreCase(name)); } return value; } } public IList GetMemberNames() { List list = new List(); lock (_storage) { foreach (ScopeVariableIgnoreCase value in _storage.Values) { value.AddNames(list); } return list; } } public IList> GetItems() { List> list = new List>(); lock (_storage) { foreach (ScopeVariableIgnoreCase value in _storage.Values) { value.AddItems(list); } return list; } } private bool HasVariable(string name) { lock (_storage) { return _storage.ContainsKey(name); } } public DynamicMetaObject GetMetaObject(Expression parameter) { return new Meta(parameter, this); } }