Bläddra i källkod

Implementing property descriptor as per the specification

Sebastien Ros 12 år sedan
förälder
incheckning
9aaef30b21

+ 2 - 3
Jint.Tests/Runtime/EngineTests.cs

@@ -239,9 +239,8 @@ namespace Jint.Tests.Runtime
         public void Scratch()
         {
             RunTest(@"
-                var o = [42, 'foo'];
-                var a = o.pop();
-                var b = o.length;
+                var a = 42;
+                a == 42;
             ");
         }
         

+ 9 - 3
Jint/Engine.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using Jint.Native;
 using Jint.Native.Array;
 using Jint.Native.Boolean;
 using Jint.Native.Errors;
@@ -67,7 +68,7 @@ namespace Jint
             {
                 foreach (var entry in Options.GetDelegates())
                 {
-                    Global.DefineOwnProperty(entry.Key, new DataDescriptor(new DelegateWrapper(this, entry.Value, RootFunction)), false);
+                    Global.DefineOwnProperty(entry.Key, new DataDescriptor(new DelegateWrapper(this, entry.Value)), false);
                 }
             }
 
@@ -244,6 +245,11 @@ namespace Jint
             }
         }
 
+        /// <summary>
+        /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.7.1
+        /// </summary>
+        /// <param name="value"></param>
+        /// <returns></returns>
         public object GetValue(object value)
         {
             var reference = value as Reference;
@@ -267,8 +273,8 @@ namespace Jint
                 return record.GetBindingValue(reference.GetReferencedName(), reference.IsStrict());
             }
 
-            /// todo: complete implementation http://www.ecma-international.org/ecma-262/5.1/#sec-8.7.1
-            return ((ObjectInstance) baseValue).Get(reference.GetReferencedName());
+            var o = TypeConverter.ToObject(this, baseValue);
+            return o.Get(reference.GetReferencedName());
         }
 
         public void SetValue(Reference reference, object value)

+ 3 - 1
Jint/Jint.csproj

@@ -123,7 +123,9 @@
     <Compile Include="Runtime\Environments\LexicalEnvironment.cs" />
     <Compile Include="Runtime\Environments\ObjectEnvironmentRecord.cs" />
     <Compile Include="Runtime\ExpressionIntepreter.cs" />
-    <Compile Include="Runtime\Interop\BuiltInPropertyWrapper.cs" />
+    <Compile Include="Runtime\Interop\ClrFunctionInstance.cs" />
+    <Compile Include="Runtime\Interop\SetterFunctionInstance.cs" />
+    <Compile Include="Runtime\Interop\GetterFunctionInstance.cs" />
     <Compile Include="Runtime\Interop\DelegateWrapper.cs" />
     <Compile Include="Runtime\References\Reference.cs" />
     <Compile Include="Runtime\StatementInterpreter.cs" />

+ 5 - 3
Jint/Native/Array/ArrayConstructor.cs

@@ -1,7 +1,9 @@
 using System;
+using Jint.Native.Errors;
 using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Runtime.Descriptors;
+using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.Array
@@ -19,8 +21,8 @@ namespace Jint.Native.Array
             this.Prototype.DefineOwnProperty("prototype", new DataDescriptor(this.Prototype) { Writable = true, Enumerable = false, Configurable = false }, false);
 
             // Array method
-            this.Prototype.DefineOwnProperty("push", new DataDescriptor(new BuiltInPropertyWrapper(engine, (Action<ArrayInstance, object>)Push, engine.RootFunction)), false);
-            this.Prototype.DefineOwnProperty("pop", new DataDescriptor(new BuiltInPropertyWrapper(engine, (Func<ArrayInstance, object>)Pop, engine.RootFunction)), false);
+            this.Prototype.DefineOwnProperty("push", new DataDescriptor(new ClrFunctionInstance(engine, (Action<ArrayInstance, object>)Push)), false);
+            this.Prototype.DefineOwnProperty("pop", new DataDescriptor(new ClrFunctionInstance(engine, (Func<ArrayInstance, object>)Pop)), false);
         }
 
         public override object Call(object thisObject, object[] arguments)
@@ -32,7 +34,7 @@ namespace Jint.Native.Array
         {
             var instance = new ArrayInstance(Prototype);
 
-            instance.DefineOwnProperty("length", new AccessorDescriptor(() => instance.Length, x => { }), false);
+            instance.DefineOwnProperty("length", new MethodProperty<ArrayInstance>(_engine, x => x.Length), false);
 
             foreach (var arg in arguments)
             {

+ 3 - 3
Jint/Native/Object/ObjectConstructor.cs

@@ -12,8 +12,8 @@ namespace Jint.Native.Object
         public ObjectConstructor(Engine engine) : base(engine, engine.RootFunction, null, null)
         {
             _engine = engine;
-            engine.RootFunction.DefineOwnProperty("hasOwnProperty", new DataDescriptor(new BuiltInPropertyWrapper(engine, (Func<ObjectInstance, string, bool>)HasOwnProperty, engine.RootFunction)), false);
-            engine.RootFunction.DefineOwnProperty("toString", new DataDescriptor(new BuiltInPropertyWrapper(engine, (Func<ObjectInstance, string>)ToString, engine.RootFunction)), false);
+            engine.RootFunction.DefineOwnProperty("hasOwnProperty", new DataDescriptor(new ClrFunctionInstance(engine, (Func<ObjectInstance, string, bool>)HasOwnProperty)), false);
+            engine.RootFunction.DefineOwnProperty("toString", new DataDescriptor(new ClrFunctionInstance(engine, (Func<ObjectInstance, string>)ToString)), false);
         }
 
         public override object Call(object thisObject, object[] arguments)
@@ -34,7 +34,7 @@ namespace Jint.Native.Object
         private static bool HasOwnProperty(ObjectInstance thisObject, string propertyName)
         {
             var desc = thisObject.GetOwnProperty(propertyName);
-            return desc != Undefined.Instance;
+            return desc != PropertyDescriptor.Undefined;
         }
 
         private static string ToString(ObjectInstance thisObject)

+ 129 - 46
Jint/Native/Object/ObjectInstance.cs

@@ -47,12 +47,19 @@ namespace Jint.Native.Object
         {
             var desc = GetProperty(propertyName);
 
-            if (desc == Undefined.Instance)
+            if (desc == PropertyDescriptor.Undefined)
             {
                 return Undefined.Instance;
             }
 
-            return ((PropertyDescriptor)desc).Get();
+            if (desc.IsDataDescriptor())
+            {
+                return desc.As<DataDescriptor>().Value;
+            }
+
+            var getter = desc.As<AccessorDescriptor>().Get;
+
+            return getter.Call(this, null);
         }
 
         public void Set(string name, object value)
@@ -75,15 +82,25 @@ namespace Jint.Native.Object
         /// </summary>
         /// <param name="propertyName"></param>
         /// <returns></returns>
-        public object GetOwnProperty(string propertyName)
+        public PropertyDescriptor GetOwnProperty(string propertyName)
         {
-            PropertyDescriptor value;
-            if (Properties.TryGetValue(propertyName, out value))
+            PropertyDescriptor x;
+            if (Properties.TryGetValue(propertyName, out x))
             {
-                return value;
+                PropertyDescriptor d;
+                if (x.IsDataDescriptor())
+                {
+                    d = new DataDescriptor(x.As<DataDescriptor>());
+                }
+                else
+                {
+                    d = new AccessorDescriptor(x.As<AccessorDescriptor>());
+                }
+
+                return d;
             }
             
-            return Undefined.Instance;
+            return PropertyDescriptor.Undefined;
         }
 
         /// <summary>
@@ -91,18 +108,18 @@ namespace Jint.Native.Object
         /// </summary>
         /// <param name="propertyName"></param>
         /// <returns></returns>
-        public object GetProperty(string propertyName)
+        public PropertyDescriptor GetProperty(string propertyName)
         {
             var prop = GetOwnProperty(propertyName);
 
-            if (prop != Undefined.Instance)
+            if (prop != PropertyDescriptor.Undefined)
             {
                 return prop;
             }
             
             if(Prototype == null)
             {
-                return Undefined.Instance;
+                return PropertyDescriptor.Undefined;
             }
 
             return Prototype.GetProperty(propertyName);
@@ -128,19 +145,21 @@ namespace Jint.Native.Object
                 return;
             }
 
-            var ownDesc = (PropertyDescriptor)GetOwnProperty(propertyName);
+            var ownDesc = GetOwnProperty(propertyName);
 
             if (ownDesc.IsDataDescriptor())
             {
-                ownDesc.Set(value);
+                var valueDesc = new DataDescriptor(value);
+                DefineOwnProperty(propertyName, valueDesc, throwOnError);
                 return;
             }
 
-            var desc = (PropertyDescriptor)GetProperty(propertyName);
+            var desc = GetProperty(propertyName);
 
             if (desc.IsAccessorDescriptor())
             {
-                desc.Set(value);
+                var setter = desc.As<AccessorDescriptor>().Set;
+                setter.Call(this, new [] {value});
             }
             else
             {
@@ -160,18 +179,20 @@ namespace Jint.Native.Object
         public bool CanPut(string propertyName)
         {
             var desc = GetOwnProperty(propertyName);
-            var pd = desc as PropertyDescriptor;
-            if (desc != Undefined.Instance)
-            {
 
-                if (pd.IsAccessorDescriptor())
+            if (desc != PropertyDescriptor.Undefined)
+            {
+                if (desc.IsAccessorDescriptor())
                 {
+                    if (desc.As<AccessorDescriptor>().Set == null)
+                    {
+                        return false;
+                    }
+
                     return true;
                 }
-                else
-                {
-                    return pd.Writable;
-                }
+                
+                return desc.As<DataDescriptor>().Writable;
             }
 
             if (Prototype == null)
@@ -179,32 +200,30 @@ namespace Jint.Native.Object
                 return Extensible;
             }
 
-            var inherited = (PropertyDescriptor)Prototype.GetProperty(propertyName);
+            var inherited = Prototype.GetProperty(propertyName);
 
-            if (inherited == Undefined.Instance)
+            if (inherited == PropertyDescriptor.Undefined)
             {
                 return Prototype.Extensible;
             }
 
             if (inherited.IsAccessorDescriptor())
             {
+                if (inherited.As<AccessorDescriptor>().Set == null)
+                {
+                    return false;
+                }
+
                 return true;
             }
 
-            if (pd.IsAccessorDescriptor())
+            if (!Extensible)
             {
-                return true;
+                return false;
             }
             else
             {
-                if (!Extensible)
-                {
-                    return false;
-                }
-                else
-                {
-                    return inherited.Writable;
-                }
+                return inherited.As<DataDescriptor>().Writable;
             }
         }
 
@@ -217,7 +236,7 @@ namespace Jint.Native.Object
         /// <returns></returns>
         public bool HasProperty(string propertyName)
         {
-            return GetProperty(propertyName) != Undefined.Instance;
+            return GetProperty(propertyName) != PropertyDescriptor.Undefined;
         }
 
         /// <summary>
@@ -231,14 +250,13 @@ namespace Jint.Native.Object
         public bool Delete(string propertyName, bool throwOnError)
         {
             var desc = GetOwnProperty(propertyName);
-            var pd = desc as PropertyDescriptor;
-
-            if (desc == Undefined.Instance)
+            
+            if (desc == PropertyDescriptor.Undefined)
             {
                 return true;
             }
 
-            if (pd.Configurable)
+            if (desc.Configurable)
             {
                 Properties.Remove(propertyName);
                 return true;
@@ -271,14 +289,14 @@ namespace Jint.Native.Object
         /// Descriptor. The flag controls failure handling.
         /// </summary>
         /// <param name="propertyName"></param>
-        /// <param name="property"></param>
+        /// <param name="desc"></param>
         /// <param name="throwOnError"></param>
         /// <returns></returns>
         public bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError)
         {
-            var property = GetOwnProperty(propertyName);
-
-            if (property == Undefined.Instance)
+            var current = GetOwnProperty(propertyName);
+            
+            if (current == PropertyDescriptor.Undefined)
             {
                 if (!Extensible)
                 {
@@ -291,13 +309,20 @@ namespace Jint.Native.Object
                 }
                 else
                 {
-                    Properties.Add(propertyName, desc);
+                    if (desc.IsGenericDescriptor() || desc.IsDataDescriptor())
+                    {
+                        Properties.Add(propertyName, new DataDescriptor(desc.As<DataDescriptor>()));
+                    }
+                    else
+                    {
+                        Properties.Add(propertyName, new AccessorDescriptor(desc.As<AccessorDescriptor>()));
+                    }
                 }
 
                 return true;
             }
 
-            var current = (PropertyDescriptor)property;
+            // todo: if desc and current are the same, return true
 
             if (!current.Configurable)
             {
@@ -322,7 +347,65 @@ namespace Jint.Native.Object
                 }
             }
 
-            /// todo: complete this implementation
+            if (desc.IsGenericDescriptor())
+            {
+                // ????
+            }
+
+            if (current.IsDataDescriptor() != desc.IsDataDescriptor())
+            {
+                if (!current.Configurable)
+                {
+                    if (throwOnError)
+                    {
+                        throw new TypeError();
+                    }
+
+                    return false;
+                }
+
+                if (current.IsDataDescriptor())
+                {
+                    // todo: convert to accessor
+                }
+            }
+            else if (current.IsDataDescriptor() && desc.IsDataDescriptor())
+            {
+                var cd = current.As<DataDescriptor>();
+                var dd = current.As<DataDescriptor>();
+
+                if (!current.Configurable)
+                {
+                    if (!cd.Writable && dd.Writable)
+                    {
+                        if (throwOnError)
+                        {
+                            throw new TypeError();
+                        }
+
+                        return false;
+                    }
+                }
+            }
+            else if (current.IsAccessorDescriptor() && desc.IsAccessorDescriptor())
+            {
+                var ca = current.As<AccessorDescriptor>();
+                var da = current.As<AccessorDescriptor>();
+
+                if (!current.Configurable)
+                {
+                    if ( (da.Set != null && da.Set != ca.Set)
+                        || (da.Get != null && da.Get != ca.Get))
+                    {
+                        if (throwOnError)
+                        {
+                            throw new TypeError();
+                        }
+
+                        return false;
+                    }
+                }
+            }
 
             Properties[propertyName] = desc;
 

+ 21 - 24
Jint/Runtime/Descriptors/AccessorDescriptor.cs

@@ -1,37 +1,34 @@
-using System;
-using Jint.Native;
+using Jint.Native.Function;
 
 namespace Jint.Runtime.Descriptors
 {
-    public sealed class AccessorDescriptor : PropertyDescriptor
+    public class AccessorDescriptor : PropertyDescriptor
     {
-        private readonly Func<object> _getter;
-        private readonly Action<object> _setter;
-
-        public AccessorDescriptor(Func<object> getter, Action<object> setter)
+        public AccessorDescriptor(FunctionInstance get, FunctionInstance set = null)
         {
-            _getter = getter;
-            _setter = setter;
+            Get = get;
+            Set = set;
         }
 
-        public override object Get()
+        public AccessorDescriptor(AccessorDescriptor a)
         {
-            if (_getter == null)
-            {
-                return Undefined.Instance;
-            }
-
-            return _getter();
-
+            Get = a.Get;
+            Set = a.Set;
+            Configurable = a.Configurable;
+            Enumerable = a.Enumerable;
         }
 
-        public override void Set(object value)
-        {
-            if (_setter != null)
-            {
-                _setter(value);
-            }
-        }
+        /// <summary>
+        /// The getter function
+        /// </summary>
+        /// <returns></returns>
+        public virtual FunctionInstance Get { get; set; }
+
+        /// <summary>
+        /// The setter function
+        /// </summary>
+        /// <returns></returns>
+        public virtual FunctionInstance Set { get; set; }
 
         public override bool IsAccessorDescriptor()
         {

+ 13 - 10
Jint/Runtime/Descriptors/DataDescriptor.cs

@@ -1,24 +1,27 @@
 namespace Jint.Runtime.Descriptors
 {
-    public sealed class DataDescriptor : PropertyDescriptor
+    public class DataDescriptor : PropertyDescriptor
     {
-        private object _value;
-
         public DataDescriptor(object value)
         {
-            _value = value;
+            Value = value;
             Writable = true;
         }
 
-        public override object Get()
+        public DataDescriptor(DataDescriptor d)
         {
-            return _value;
+            Value = d.Value;
+            Writable = d.Writable;
+            Configurable = d.Configurable;
+            Enumerable = d.Enumerable;
         }
 
-        public override void Set(object value)
-        {
-            _value = value;
-        }
+        public object Value { get; set; }
+        /// <summary>
+        /// If false, attempts by ECMAScript code to change the 
+        /// property‘s [[Value]] attribute using [[Put]] will not succeed.
+        /// </summary>
+        public bool Writable { get; set; }
 
         public override bool IsAccessorDescriptor()
         {

+ 27 - 9
Jint/Runtime/Descriptors/PropertyDescriptor.cs

@@ -5,15 +5,7 @@
     /// </summary>
     public abstract class PropertyDescriptor
     {
-        public abstract object Get();
-
-        public abstract void Set(object value);
-
-        /// <summary>
-        /// If false, attempts by ECMAScript code to change the 
-        /// property‘s [[Value]] attribute using [[Put]] will not succeed.
-        /// </summary>
-        public bool Writable { get; set; }
+        public static PropertyDescriptor Undefined = new UndefinedPropertyDescriptor();
 
         /// <summary>
         /// If true, the property will be enumerated by a for-in 
@@ -49,5 +41,31 @@
         {
             return !IsDataDescriptor() && !IsAccessorDescriptor();
         }
+
+        public T As<T>() where T : PropertyDescriptor
+        {
+            return (T)this;
+        }
+
+        /// <summary>
+        /// Local implementation used to create a singleton representing 
+        /// an undefined result of a PropertyDescriptor. This prevents the rest
+        /// of the code to return objects in order to be able to return
+        /// Undefined.Instance
+        /// </summary>
+        internal sealed class UndefinedPropertyDescriptor : PropertyDescriptor
+        {
+            public override bool IsAccessorDescriptor()
+            {
+                throw new System.NotImplementedException();
+            }
+
+            public override bool IsDataDescriptor()
+            {
+                throw new System.NotImplementedException();
+            }
+        }
+
+
     }
 }

+ 9 - 16
Jint/Runtime/Descriptors/Specialized/MethodProperty.cs

@@ -1,28 +1,21 @@
 using System;
+using Jint.Runtime.Interop;
 
 namespace Jint.Runtime.Descriptors.Specialized
 {
-    public sealed class MethodProperty : PropertyDescriptor
+    public sealed class MethodProperty<T> : AccessorDescriptor
     {
-        private readonly Engine _engine;
-        private readonly Action<object> _setter;
-        private readonly Func<object> _getter;
-
-        public MethodProperty(Engine engine, Func<object> getter, Action<object> setter)
-        {
-            _engine = engine;
-            _setter = setter;
-            _getter = getter;
-        }
-
-        public override object Get()
+        public MethodProperty(Engine engine, Func<T, object> get)
+            : this(engine, get, null)
         {
-            return _getter();
         }
 
-        public override void Set(object value)
+        public MethodProperty(Engine engine, Func<T, object> get, Action<T, object> set)
+            : base(
+                new GetterFunctionInstance<T>(engine, get),
+                set == null ? null : new SetterFunctionInstance<T>(engine, set)
+                )
         {
-            _setter(value);
         }
 
         public override bool IsAccessorDescriptor()

+ 7 - 2
Jint/Runtime/Environments/ObjectEnvironmentRecord.cs

@@ -26,13 +26,18 @@ namespace Jint.Runtime.Environments
             return _bindingObject.HasProperty(name);
         }
 
-        public override void CreateMutableBinding(string name, bool canBeDeleted = false)
+        /// <summary>
+        /// http://www.ecma-international.org/ecma-262/5.1/#sec-10.2.1.2.2
+        /// </summary>
+        /// <param name="name"></param>
+        /// <param name="configurable"></param>
+        public override void CreateMutableBinding(string name, bool configurable = true)
         {
             var property = new DataDescriptor(Undefined.Instance)
                 {
                     Writable = true,
                     Enumerable = true,
-                    Configurable = canBeDeleted
+                    Configurable = configurable
                 };
 
             _bindingObject.DefineOwnProperty(name, property, true);

+ 3 - 4
Jint/Runtime/Interop/BuiltInPropertyWrapper.cs → Jint/Runtime/Interop/ClrFunctionInstance.cs

@@ -1,20 +1,19 @@
 using System;
 using Jint.Native;
 using Jint.Native.Function;
-using Jint.Native.Object;
 
 namespace Jint.Runtime.Interop
 {
     /// <summary>
     /// Reprensents a Property wrapper for static methods representing built-in properties.
     /// </summary>
-    public sealed class BuiltInPropertyWrapper : FunctionInstance
+    public sealed class ClrFunctionInstance : FunctionInstance
     {
         private readonly Engine _engine;
         private readonly Delegate _d;
 
-        public BuiltInPropertyWrapper(Engine engine, Delegate d, ObjectInstance prototype)
-            : base(engine, prototype, null, null)
+        public ClrFunctionInstance(Engine engine, Delegate d)
+            : base(engine, null, null, null)
         {
             _engine = engine;
             _d = d;

+ 1 - 2
Jint/Runtime/Interop/DelegateWrapper.cs

@@ -1,7 +1,6 @@
 using System;
 using Jint.Native;
 using Jint.Native.Function;
-using Jint.Native.Object;
 
 namespace Jint.Runtime.Interop
 {
@@ -14,7 +13,7 @@ namespace Jint.Runtime.Interop
         private readonly Engine _engine;
         private readonly Delegate _d;
 
-        public DelegateWrapper(Engine engine, Delegate d, ObjectInstance prototype) : base(engine, prototype, null, null)
+        public DelegateWrapper(Engine engine, Delegate d) : base(engine, null, null, null)
         {
             _engine = engine;
             _d = d;

+ 24 - 0
Jint/Runtime/Interop/GetterFunctionInstance.cs

@@ -0,0 +1,24 @@
+using System;
+using Jint.Native.Function;
+
+namespace Jint.Runtime.Interop
+{
+    /// <summary>
+    /// Represents a FunctionInstance wrapping a Clr getter.
+    /// </summary>
+    public sealed class GetterFunctionInstance<T> : FunctionInstance
+    {
+        private readonly Func<T, object> _getter;
+
+        public GetterFunctionInstance(Engine engine, Func<T, object> getter)
+            : base(engine, null, null, null)
+        {
+            _getter = getter;
+        }
+
+        public override object Call(object thisObject, object[] arguments)
+        {
+            return _getter((T)thisObject);
+        }
+    }
+}

+ 27 - 0
Jint/Runtime/Interop/SetterFunctionInstance.cs

@@ -0,0 +1,27 @@
+using System;
+using Jint.Native;
+using Jint.Native.Function;
+
+namespace Jint.Runtime.Interop
+{
+    /// <summary>
+    /// Represents a FunctionInstance wrapping a Clr setter.
+    /// </summary>
+    public sealed class SetterFunctionInstance<T> : FunctionInstance
+    {
+        private readonly Action<T, object> _setter;
+
+        public SetterFunctionInstance(Engine engine, Action<T, object> setter)
+            : base(engine, null, null, null)
+        {
+            _setter = setter;
+        }
+
+        public override object Call(object thisObject, object[] arguments)
+        {
+            _setter((T)thisObject, arguments[0]);
+            
+            return Null.Instance;
+        }
+    }
+}

+ 2 - 2
Jint/Runtime/StatementInterpreter.cs

@@ -50,7 +50,7 @@ namespace Jint.Runtime
                 var varAlreadyDeclared = env.HasBinding(dn);
                 if (!varAlreadyDeclared)
                 {
-                    env.CreateMutableBinding(declaration.Id.Name, false);
+                    env.CreateMutableBinding(declaration.Id.Name, true);
                     env.SetMutableBinding(declaration.Id.Name, value, false);
                 }
             }
@@ -121,7 +121,7 @@ namespace Jint.Runtime
             object result = null;
 
             // create function objects
-            http://www.ecma-international.org/ecma-262/5.1/#sec-13.2
+            // http://www.ecma-international.org/ecma-262/5.1/#sec-13.2
 
             var identifier = functionDeclaration.Id.Name;