Pārlūkot izejas kodu

Adding Symbol object

Sebastien Ros 8 gadi atpakaļ
vecāks
revīzija
bd0d1d742f

+ 12 - 2
Jint/Engine.cs

@@ -1,6 +1,8 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Esprima;
+using Esprima.Ast;
 using Jint.Native;
 using Jint.Native.Argument;
 using Jint.Native.Array;
@@ -15,8 +17,7 @@ using Jint.Native.Number;
 using Jint.Native.Object;
 using Jint.Native.RegExp;
 using Jint.Native.String;
-using Esprima;
-using Esprima.Ast;
+using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.CallStack;
 using Jint.Runtime.Debugger;
@@ -79,6 +80,7 @@ namespace Jint
             Object = ObjectConstructor.CreateObjectConstructor(this);
             Function = FunctionConstructor.CreateFunctionConstructor(this);
 
+            Symbol = SymbolConstructor.CreateSymbolConstructor(this);
             Array = ArrayConstructor.CreateArrayConstructor(this);
             String = StringConstructor.CreateStringConstructor(this);
             RegExp = RegExpConstructor.CreateRegExpConstructor(this);
@@ -96,6 +98,8 @@ namespace Jint
             TypeError = ErrorConstructor.CreateErrorConstructor(this, "TypeError");
             UriError = ErrorConstructor.CreateErrorConstructor(this, "URIError");
 
+            GlobalSymbolRegistry = new GlobalSymbolRegistry();
+
             // Because the properties might need some of the built-in object
             // their configuration is delayed to a later step
 
@@ -104,6 +108,9 @@ namespace Jint
             Object.Configure();
             Object.PrototypeObject.Configure();
 
+            Symbol.Configure();
+            Symbol.PrototypeObject.Configure();
+
             Function.Configure();
             Function.PrototypeObject.Configure();
 
@@ -177,6 +184,7 @@ namespace Jint
         public DateConstructor Date { get; private set; }
         public MathInstance Math { get; private set; }
         public JsonInstance Json { get; private set; }
+        public SymbolConstructor Symbol { get; private set; }
         public EvalFunctionInstance Eval { get; private set; }
 
         public ErrorConstructor Error { get; private set; }
@@ -189,6 +197,8 @@ namespace Jint
 
         public ExecutionContext ExecutionContext { get { return _executionContexts.Peek(); } }
 
+        public GlobalSymbolRegistry GlobalSymbolRegistry { get; }
+
         internal Options Options { get; private set; }
 
         #region Debugger

+ 3 - 2
Jint/Native/Global/GlobalObject.cs

@@ -30,6 +30,7 @@ namespace Jint.Native.Global
             // Global object properties
             FastAddProperty("Object", Engine.Object, true, false, true);
             FastAddProperty("Function", Engine.Function, true, false, true);
+            FastAddProperty("Symbol", Engine.Symbol, true, false, true);
             FastAddProperty("Array", Engine.Array, true, false, true);
             FastAddProperty("String", Engine.String, true, false, true);
             FastAddProperty("RegExp", Engine.RegExp, true, false, true);
@@ -449,7 +450,7 @@ namespace Jint.Native.Global
                     }
                     else if (v <= 0xD7FF)
                     {
-                        // xxxxyyyy yyzzzzzz -> 1110xxxx; 10yyyyyy; 10zzzzzz	
+                        // xxxxyyyy yyzzzzzz -> 1110xxxx; 10yyyyyy; 10zzzzzz
                         octets = new[]
                         {
                             (byte)(0xE0 | (v >> 12)),
@@ -582,7 +583,7 @@ namespace Jint.Native.Global
 
                             B = Convert.ToByte(uriString[k + 1].ToString() + uriString[k + 2], 16);
 
-                            // B & 11000000 != 10000000 
+                            // B & 11000000 != 10000000
                             if ((B & 0xC0) != 0x80)
                             {
                                 throw new JavaScriptException(Engine.UriError);

+ 92 - 6
Jint/Native/JsValue.cs

@@ -3,7 +3,6 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics.Contracts;
 using System.Dynamic;
-using System.Reflection;
 using System.Threading;
 using Jint.Native.Array;
 using Jint.Native.Boolean;
@@ -56,6 +55,14 @@ namespace Jint.Native
             _object = value;
         }
 
+        public JsValue(Completion value)
+        {
+            _double = double.NaN;
+            _type = Types.Completion;
+
+            _object = value;
+        }
+
         private JsValue(Types type)
         {
             _double = double.NaN;
@@ -63,11 +70,11 @@ namespace Jint.Native
             _type = type;
         }
 
-        private readonly double _double;
+        protected double _double;
 
-        private readonly object _object;
+        protected object _object;
 
-        private readonly Types _type;
+        protected Types _type;
 
         [Pure]
         public bool IsPrimitive()
@@ -129,6 +136,18 @@ namespace Jint.Native
             return _type == Types.Null;
         }
 
+        [Pure]
+        public bool IsCompletion()
+        {
+            return _type == Types.Completion;
+        }
+
+        [Pure]
+        public bool IsSymbol()
+        {
+            return _type == Types.Symbol;
+        }
+
         [Pure]
         public ObjectInstance AsObject()
         {
@@ -140,6 +159,17 @@ namespace Jint.Native
             return _object as ObjectInstance;
         }
 
+        [Pure]
+        public TInstance AsInstance<TInstance>() where TInstance : class
+        {
+            if (_type != Types.Object)
+            {
+                throw new ArgumentException("The value is not an object");
+            }
+
+            return _object as TInstance;
+        }
+
         [Pure]
         public ArrayInstance AsArray()
         {
@@ -173,6 +203,17 @@ namespace Jint.Native
             return _object as RegExpInstance;
         }
 
+        [Pure]
+        public Completion AsCompletion()
+        {
+            if (_type != Types.Completion)
+            {
+                throw new ArgumentException("The value is not a completion record");
+            }
+
+            return (Completion)_object;
+        }
+
         [Pure]
         public T TryCast<T>(Action<JsValue> fail = null) where T : class
         {
@@ -228,7 +269,23 @@ namespace Jint.Native
                 throw new ArgumentException("The value is not defined");
             }
 
-            return _object as string;
+            return (string)_object;
+        }
+
+        [Pure]
+        public string AsSymbol()
+        {
+            if (_type != Types.Symbol)
+            {
+                throw new ArgumentException("The value is not a symbol");
+            }
+
+            if (_object == null)
+            {
+                throw new ArgumentException("The value is not defined");
+            }
+
+            return (string)_object;
         }
 
         [Pure]
@@ -465,7 +522,7 @@ namespace Jint.Native
                             var numberInstance = _object as NumberInstance;
                             if (numberInstance != null)
                             {
-                                return numberInstance.PrimitiveValue.AsNumber();
+                                return numberInstance.NumberData.AsNumber();
                             }
 
                             break;
@@ -535,6 +592,24 @@ namespace Jint.Native
             return callable.Call(thisObj, arguments);
         }
 
+        public static bool ReturnOnAbruptCompletion(ref JsValue argument)
+        {
+            if (argument.IsCompletion())
+            {
+                return false;
+            }
+
+            var completion = argument.AsCompletion();
+            if (completion.IsAbrupt())
+            {
+                return true;
+            }
+
+            argument = completion.Value;
+
+            return false;
+        }
+
         public override string ToString()
         {
             switch (Type)
@@ -660,4 +735,15 @@ namespace Jint.Native
             }
         }
     }
+
+    /// <summary>
+    /// The _object value of a <see cref="JsSymbol"/> is the [[Description]] internal slot.
+    /// </summary>
+    public class JsSymbol : JsValue
+    {
+        public JsSymbol(string description) : base(description)
+        {
+            _type = Types.Symbol;
+        }
+    }
 }

+ 1 - 1
Jint/Native/Number/NumberConstructor.cs

@@ -64,7 +64,7 @@ namespace Jint.Native.Number
         {
             var instance = new NumberInstance(Engine);
             instance.Prototype = PrototypeObject;
-            instance.PrimitiveValue = value;
+            instance.NumberData = value;
             instance.Extensible = true;
 
             return instance;

+ 2 - 2
Jint/Native/Number/NumberInstance.cs

@@ -28,10 +28,10 @@ namespace Jint.Native.Number
 
         JsValue IPrimitiveInstance.PrimitiveValue
         {
-            get { return PrimitiveValue; }
+            get { return NumberData; }
         }
 
-        public JsValue PrimitiveValue { get; set; }
+        public JsValue NumberData { get; set; }
 
         public static bool IsNegativeZero(double x)
         {

+ 2 - 2
Jint/Native/Number/NumberPrototype.cs

@@ -21,7 +21,7 @@ namespace Jint.Native.Number
         {
             var obj = new NumberPrototype(engine);
             obj.Prototype = engine.Object.PrototypeObject;
-            obj.PrimitiveValue = 0;
+            obj.NumberData = 0;
             obj.Extensible = true;
 
             obj.FastAddProperty("constructor", numberConstructor, true, false, true);
@@ -84,7 +84,7 @@ namespace Jint.Native.Number
                 throw new JavaScriptException(Engine.TypeError);
             }
 
-            return number.PrimitiveValue;
+            return number.NumberData;
         }
 
         private const double Ten21 = 1e21;

+ 37 - 0
Jint/Native/Object/ObjectInstance.cs

@@ -2,11 +2,14 @@
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using System;
+using System.Collections.Specialized;
 
 namespace Jint.Native.Object
 {
     public class ObjectInstance
     {
+        private Dictionary<string, PropertyDescriptor> _intrinsicProperties;
+
         public ObjectInstance(Engine engine)
         {
             Engine = engine;
@@ -17,6 +20,40 @@ namespace Jint.Native.Object
 
         protected IDictionary<string, PropertyDescriptor> Properties { get; private set; }
 
+        protected bool TryGetIntrinsicValue(JsSymbol symbol, out JsValue value)
+        {
+            PropertyDescriptor descriptor;
+
+            if (_intrinsicProperties != null && _intrinsicProperties.TryGetValue(symbol.AsSymbol(), out descriptor))
+            {
+                value = descriptor.Value;
+                return true;
+            }
+
+            if (Prototype == null)
+            {
+                value = JsValue.Undefined;
+                return false;
+            }
+
+            return Prototype.TryGetIntrinsicValue(symbol, out value);
+        }
+
+        public void SetIntrinsicValue(string name, JsValue value, bool writable, bool enumerable, bool configurable)
+        {
+            SetOwnProperty(name, new PropertyDescriptor(value, writable, enumerable, configurable));
+        }
+
+        protected void SetIntrinsicValue(JsSymbol symbol, JsValue value, bool writable, bool enumerable, bool configurable)
+        {
+            if (_intrinsicProperties == null)
+            {
+                _intrinsicProperties = new Dictionary<string, PropertyDescriptor>();
+            }
+
+            _intrinsicProperties[symbol.AsSymbol()] = new PropertyDescriptor(value, writable, enumerable, configurable);
+        }
+
         /// <summary>
         /// The prototype of this object.
         /// </summary>

+ 19 - 0
Jint/Native/Symbol/GlobalSymbolRegistry.cs

@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+
+namespace Jint.Native.Symbol
+{
+    public class GlobalSymbolRegistry : Dictionary<string, JsSymbol>
+    {
+        public static JsSymbol HasInstance { get; } = new JsSymbol("Symbol.hasInstance");
+        public static JsSymbol IsConcatSpreadable { get; } = new JsSymbol("Symbol.isConcatSpreadable");
+        public static JsSymbol Iterator { get; } = new JsSymbol("Symbol.iterator");
+        public static JsSymbol Match { get; } = new JsSymbol("Symbol.match");
+        public static JsSymbol Replace { get; } = new JsSymbol("Symbol.replace");
+        public static JsSymbol Search { get; } = new JsSymbol("Symbol.search");
+        public static JsSymbol Species { get; } = new JsSymbol("Symbol.species");
+        public static JsSymbol Split { get; } = new JsSymbol("Symbol.split");
+        public static JsSymbol ToPrimitive { get; } = new JsSymbol("Symbol.toPrimitive");
+        public static JsSymbol ToStringTag { get; } = new JsSymbol("Symbol.toStringTag");
+        public static JsSymbol Unscopables { get; } = new JsSymbol("Symbol.unscopables");
+    }
+}

+ 128 - 0
Jint/Native/Symbol/SymbolConstructor.cs

@@ -0,0 +1,128 @@
+using System;
+using Jint.Native.Function;
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Interop;
+
+namespace Jint.Native.Symbol
+{
+    /// <summary>
+    /// 19.4
+    /// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-symbol-objects
+    /// </summary>
+    public sealed class SymbolConstructor : FunctionInstance, IConstructor
+    {
+        public SymbolConstructor(Engine engine)
+            : base(engine, null, null, false)
+        {
+        }
+
+        public static SymbolConstructor CreateSymbolConstructor(Engine engine)
+        {
+            var obj = new SymbolConstructor(engine);
+            obj.Extensible = true;
+
+            // The value of the [[Prototype]] internal property of the Symbol constructor is the Function prototype object
+            obj.Prototype = engine.Function.PrototypeObject;
+            obj.PrototypeObject = SymbolPrototype.CreatePrototypeObject(engine, obj);
+
+            obj.FastAddProperty("length", 0, false, false, false);
+
+            // The initial value of String.prototype is the String prototype object
+            obj.FastAddProperty("prototype", obj.PrototypeObject, false, false, false);
+
+
+            return obj;
+        }
+
+        public void Configure()
+        {
+            FastAddProperty("for", new ClrFunctionInstance(Engine, For, 1), true, false, true);
+            FastAddProperty("keyFor", new ClrFunctionInstance(Engine, KeyFor, 1), true, false, true);
+
+            FastAddProperty("hasInstance", GlobalSymbolRegistry.HasInstance, false, false, false);
+            FastAddProperty("isConcatSpreadable", GlobalSymbolRegistry.IsConcatSpreadable, false, false, false);
+            FastAddProperty("iterator", GlobalSymbolRegistry.Iterator, false, false, false);
+            FastAddProperty("match", GlobalSymbolRegistry.Match, false, false, false);
+            FastAddProperty("replace", GlobalSymbolRegistry.Replace, false, false, false);
+            FastAddProperty("search", GlobalSymbolRegistry.Search, false, false, false);
+            FastAddProperty("species", GlobalSymbolRegistry.Species, false, false, false);
+            FastAddProperty("split", GlobalSymbolRegistry.Split, false, false, false);
+            FastAddProperty("toPrimitive", GlobalSymbolRegistry.ToPrimitive, false, false, false);
+            FastAddProperty("toStringTag", GlobalSymbolRegistry.ToStringTag, false, false, false);
+            FastAddProperty("unscopables", GlobalSymbolRegistry.Unscopables, false, false, false);
+
+        }
+
+        /// <summary>
+        /// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-symbol-description
+        /// </summary>
+        public override JsValue Call(JsValue thisObject, JsValue[] arguments)
+        {
+            var description = arguments.At(0);
+            var descString = description == Undefined.Instance
+                ? Undefined.Instance
+                : TypeConverter.ToString(description);
+
+            var value = new JsSymbol(description.AsString());
+
+            if (JsValue.ReturnOnAbruptCompletion(ref descString))
+            {
+                return descString;
+            }
+
+            return value;
+        }
+
+        public JsValue For(JsValue thisObj, JsValue[] arguments)
+        {
+            var stringKey = TypeConverter.ToString(arguments.At(0));
+
+            // 2. ReturnIfAbrupt(stringKey).
+
+            JsSymbol symbol;
+            if (!Engine.GlobalSymbolRegistry.TryGetValue(stringKey, out symbol))
+            {
+                symbol = new JsSymbol(stringKey);
+                Engine.GlobalSymbolRegistry.Add(stringKey, symbol);
+            }
+
+            return symbol;
+        }
+
+        public JsValue KeyFor(JsValue thisObj, JsValue[] arguments)
+        {
+            var sym = arguments.At(0);
+
+            if (!sym.IsSymbol())
+            {
+                throw new JavaScriptException(Engine.TypeError);
+            }
+
+            JsSymbol symbol;
+            if (!Engine.GlobalSymbolRegistry.TryGetValue(sym.AsSymbol(), out symbol))
+            {
+                return Undefined.Instance;
+            }
+
+            return sym.AsSymbol();
+        }
+
+        public ObjectInstance Construct(JsValue[] arguments)
+        {
+            throw new JavaScriptException(Engine.TypeError);
+        }
+
+        public SymbolInstance Construct(string description)
+        {
+            var instance = new SymbolInstance(Engine);
+            instance.Prototype = PrototypeObject;
+            instance.SymbolData = new JsSymbol(description);
+            instance.Extensible = true;
+
+            return instance;
+        }
+
+        public SymbolPrototype PrototypeObject { get; private set; }
+    }
+}

+ 33 - 0
Jint/Native/Symbol/SymbolInstance.cs

@@ -0,0 +1,33 @@
+using Jint.Native.Object;
+using Jint.Runtime;
+
+namespace Jint.Native.Symbol
+{
+    public class SymbolInstance : ObjectInstance, IPrimitiveInstance
+    {
+        public SymbolInstance(Engine engine)
+            : base(engine)
+        {
+        }
+
+        public override string Class
+        {
+            get
+            {
+                return "Symbol";
+            }
+        }
+
+        Types IPrimitiveInstance.Type
+        {
+            get { return Types.Symbol; }
+        }
+
+        JsValue IPrimitiveInstance.PrimitiveValue
+        {
+            get { return SymbolData; }
+        }
+
+        public JsSymbol SymbolData { get; set; }
+    }
+}

+ 92 - 0
Jint/Native/Symbol/SymbolPrototype.cs

@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Jint.Native.Array;
+using Jint.Native.Function;
+using Jint.Native.Object;
+using Jint.Native.RegExp;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Interop;
+
+namespace Jint.Native.Symbol
+{
+
+
+    /// <summary>
+    /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.5.4
+    /// </summary>
+    public sealed class SymbolPrototype : ObjectInstance
+    {
+        private SymbolPrototype(Engine engine)
+            : base(engine)
+        {
+        }
+
+        public static SymbolPrototype CreatePrototypeObject(Engine engine, SymbolConstructor symbolConstructor)
+        {
+            var obj = new SymbolPrototype(engine);
+            obj.Prototype = engine.Object.PrototypeObject;
+            obj.Extensible = true;
+            obj.FastAddProperty("length", 0, false, false, false);
+            obj.FastAddProperty("constructor", symbolConstructor, true, false, true);
+
+            return obj;
+        }
+
+        public void Configure()
+        {
+            FastAddProperty("toString", new ClrFunctionInstance(Engine, ToSymbolString), true, false, true);
+            FastAddProperty("valueOf", new ClrFunctionInstance(Engine, ValueOf), true, false, true);
+            FastAddProperty("toStringTag", new JsValue("Symbol"), false, false, true);
+
+            SetIntrinsicValue(GlobalSymbolRegistry.ToPrimitive, new ClrFunctionInstance(Engine, ToPrimitive), false, false, true);
+            SetIntrinsicValue(GlobalSymbolRegistry.ToStringTag, new JsValue("Symbol"), false, false, true);
+        }
+
+        public string SymbolDescriptiveString(JsSymbol sym)
+        {
+            return $"Symbol({sym.AsSymbol()})";
+        }
+
+        private JsValue ToSymbolString(JsValue thisObject, JsValue[] arguments)
+        {
+            if (!thisObject.IsSymbol())
+            {
+                throw new JavaScriptException(Engine.TypeError);
+            }
+
+            return SymbolDescriptiveString((JsSymbol)thisObject);
+        }
+
+        private JsValue ValueOf(JsValue thisObject, JsValue[] arguments)
+        {
+            var sym = thisObject.TryCast<SymbolInstance>();
+            if (sym == null)
+            {
+                throw new JavaScriptException(Engine.TypeError);
+            }
+
+            return sym.SymbolData;
+        }
+
+        private JsValue ToPrimitive(JsValue thisObject, JsValue[] arguments)
+        {
+            if (thisObject.IsSymbol())
+            {
+                return thisObject;
+            }
+
+            // Steps 3. and 4.
+            var o = thisObject.AsInstance<SymbolInstance>();
+            if (o == null)
+            {
+                throw new JavaScriptException(Engine.TypeError);
+            }
+
+            return o.SymbolData;
+        }
+
+    }
+}

+ 5 - 0
Jint/Runtime/Completion.cs

@@ -30,6 +30,11 @@ namespace Jint.Runtime
             return Value != null ? Value : Undefined.Instance;
         }
 
+        public bool IsAbrupt()
+        {
+            return Type != Normal;
+        }
+
         public Location Location { get; set; }
     }
 }

+ 32 - 14
Jint/Runtime/TypeConverter.cs

@@ -9,6 +9,7 @@ using Jint.Native.Number;
 using Jint.Native.Object;
 using Jint.Native.String;
 using Jint.Runtime.References;
+using Jint.Native.Symbol;
 
 namespace Jint.Runtime
 {
@@ -20,7 +21,9 @@ namespace Jint.Runtime
         Boolean,
         String,
         Number,
-        Object
+        Object,
+        Completion,
+        Symbol
     }
 
     public class TypeConverter
@@ -46,7 +49,7 @@ namespace Jint.Runtime
             return input.AsObject().DefaultValue(preferredType);
         }
 
-    
+
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.2
         /// </summary>
@@ -63,7 +66,7 @@ namespace Jint.Runtime
             {
                 return false;
             }
-            
+
             if (o.IsBoolean())
             {
                 return o.AsBoolean();
@@ -109,8 +112,8 @@ namespace Jint.Runtime
             if (o.IsNumber())
             {
                 return o.AsNumber();
-            } 
-            
+            }
+
             if (o.IsObject())
             {
                 var p = o.AsObject() as IPrimitiveInstance;
@@ -178,7 +181,7 @@ namespace Jint.Runtime
                     }
 
                     int i = int.Parse(s.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
-                 
+
                     return i;
                 }
                 catch (OverflowException)
@@ -207,7 +210,7 @@ namespace Jint.Runtime
             {
                 return 0;
             }
-            
+
             if (number.Equals(0) || double.IsInfinity(number))
             {
                 return number;
@@ -253,6 +256,11 @@ namespace Jint.Runtime
         /// <returns></returns>
         public static string ToString(JsValue o)
         {
+            if (o.IsString())
+            {
+                return o.AsString();
+            }
+
             if (o.IsObject())
             {
                 var p = o.AsObject() as IPrimitiveInstance;
@@ -260,11 +268,16 @@ namespace Jint.Runtime
                 {
                     o = p.PrimitiveValue;
                 }
-            }
-
-            if (o.IsString())
-            {
-                return o.AsString();
+                else
+                {
+                    var s = o.AsInstance<SymbolInstance>();
+                    if (s != null)
+                    {
+                        // TODO: throw a TypeError
+                        // NB: But it requires an Engine reference
+                        throw new JavaScriptException(new JsValue("TypeError"));
+                    }
+                }
             }
 
             if (o == Undefined.Instance)
@@ -276,7 +289,7 @@ namespace Jint.Runtime
             {
                 return Null.Text;
             }
-            
+
             if (o.IsBoolean())
             {
                 return o.AsBoolean() ? "true" : "false";
@@ -322,6 +335,11 @@ namespace Jint.Runtime
                 return engine.String.Construct(value.AsString());
             }
 
+            if (value.IsSymbol())
+            {
+                return engine.Symbol.Construct(value.AsSymbol());
+            }
+
             throw new JavaScriptException(engine.TypeError);
         }
 
@@ -389,7 +407,7 @@ namespace Jint.Runtime
                 {
                     var arg = objectArguments[i];
                     var paramType = parameters[i].ParameterType;
-                    
+
                     if (arg == null)
                     {
                         if (!TypeIsNullable(paramType))