Browse Source

Add protected function instance constructor (#759)

Michael Kriese 5 years ago
parent
commit
5d1114d48a

+ 25 - 0
Jint.Tests/Runtime/Domain/JsUuid.cs

@@ -0,0 +1,25 @@
+using System;
+using Jint.Native;
+
+namespace Jint.Tests.Runtime.Domain
+{
+    public sealed class JsUuid : JsValue, IEquatable<JsUuid>
+    {
+        internal readonly Guid _value;
+        public static readonly JsUuid Empty = new JsUuid(Guid.Empty);
+
+        public JsUuid(Guid value) : base(Jint.Runtime.Types.String) => _value = value;
+
+        public static implicit operator JsUuid(Guid g) => new JsUuid(g);
+
+        public override bool Equals(JsValue other) => Equals(other as JsUuid);
+
+        public bool Equals(JsUuid other) => other?._value == _value;
+
+        public override int GetHashCode() => _value.GetHashCode();
+
+        public override object ToObject() => _value;
+
+        public override string ToString() => _value.ToString();
+    }
+}

+ 70 - 0
Jint.Tests/Runtime/Domain/UuidConstructor.cs

@@ -0,0 +1,70 @@
+using System;
+using Jint.Native;
+using Jint.Native.Function;
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Interop;
+
+namespace Jint.Tests.Runtime.Domain
+{
+    internal sealed class UuidConstructor : FunctionInstance, IConstructor
+    {
+        private static readonly JsString _functionName = new JsString("Uuid");
+
+        private UuidConstructor(Engine engine) : base(engine, _functionName)
+        {
+        }
+
+        private JsValue Parse(JsValue @this, JsValue[] arguments)
+        {
+            switch (arguments.At(0))
+            {
+                case JsUuid uid:
+                    return Construct(uid);
+
+                case JsValue js when Guid.TryParse(js.AsString(), out var res):
+                    return Construct(res);
+            }
+
+            return Undefined;
+        }
+
+        protected override ObjectInstance GetPrototypeOf() => _prototype;
+
+        internal ObjectInstance _prototype;
+
+        public UuidPrototype PrototypeObject { get; private set; }
+
+        public static UuidConstructor CreateUuidConstructor(Engine engine)
+        {
+            var obj = new UuidConstructor(engine)
+            {
+                // The value of the [[Prototype]] internal property of the Uuid constructor is the Function prototype object
+                _prototype = engine.Function.PrototypeObject
+            };
+            obj.PrototypeObject = UuidPrototype.CreatePrototypeObject(engine, obj);
+
+            // The initial value of Uuid.prototype is the Date prototype object
+            obj.SetOwnProperty("prototype", new PropertyDescriptor(obj.PrototypeObject, false, false, false));
+
+            engine.SetValue("Uuid", obj);
+            obj.Configure();
+            obj.PrototypeObject.Configure();
+
+            return obj;
+        }
+
+        public override JsValue Call(JsValue thisObject, JsValue[] arguments) => Construct(arguments, null);
+
+        public void Configure()
+        {
+            FastAddProperty("parse", new ClrFunctionInstance(Engine, "parse", Parse), true, false, true);
+            FastAddProperty("Empty", JsUuid.Empty, true, false, true);
+        }
+
+        public UuidInstance Construct(JsUuid uuid) => new UuidInstance(Engine) { PrimitiveValue = uuid, _prototype = PrototypeObject };
+
+        public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) => Construct(Guid.NewGuid());
+    }
+}

+ 26 - 0
Jint.Tests/Runtime/Domain/UuidConverter.cs

@@ -0,0 +1,26 @@
+using Jint.Native;
+using Jint.Runtime.Interop;
+using System;
+
+namespace Jint.Tests.Runtime.Domain
+{
+    public class UuidConverter : IObjectConverter
+    {
+        internal UuidConverter()
+        {
+        }
+
+        public bool TryConvert(Engine engine, object value, out JsValue result)
+        {
+            switch (value)
+            {
+                case Guid g:
+                    result = new JsUuid(g);
+                    return true;
+            }
+
+            result = JsValue.Undefined;
+            return false;
+        }
+    }
+}

+ 20 - 0
Jint.Tests/Runtime/Domain/UuidInstance.cs

@@ -0,0 +1,20 @@
+using Jint.Native.Object;
+using Jint.Runtime.Interop;
+
+namespace Jint.Tests.Runtime.Domain
+{
+    internal class UuidInstance : ObjectInstance, IObjectWrapper
+    {
+        protected override ObjectInstance GetPrototypeOf() => _prototype;
+
+        internal ObjectInstance _prototype;
+
+        public JsUuid PrimitiveValue { get; set; }
+
+        public object Target => PrimitiveValue?._value;
+
+        public UuidInstance(Engine engine) : base(engine)
+        {
+        }
+    }
+}

+ 44 - 0
Jint.Tests/Runtime/Domain/UuidPrototype.cs

@@ -0,0 +1,44 @@
+using Jint.Native;
+using Jint.Runtime;
+using Jint.Runtime.Interop;
+
+namespace Jint.Tests.Runtime.Domain
+{
+    internal sealed class UuidPrototype : UuidInstance
+    {
+        private UuidPrototype(Engine engine) : base(engine)
+        {
+        }
+
+        private UuidInstance EnsureUuidInstance(JsValue thisObj)
+        {
+            return thisObj.TryCast<UuidInstance>(value =>
+            {
+                throw new JavaScriptException(Engine.TypeError, "Invalid Uuid");
+            });
+        }
+
+        private JsValue ToGuidString(JsValue thisObj, JsValue[] arguments) => EnsureUuidInstance(thisObj).PrimitiveValue.ToString();
+
+        private JsValue ValueOf(JsValue thisObj, JsValue[] arguments) => EnsureUuidInstance(thisObj).PrimitiveValue;
+
+        public static UuidPrototype CreatePrototypeObject(Engine engine, UuidConstructor ctor)
+        {
+            var obj = new UuidPrototype(engine)
+            {
+                PrimitiveValue = JsUuid.Empty,
+                _prototype = engine.Object.PrototypeObject,
+            };
+
+            obj.FastAddProperty("constructor", ctor, false, false, true);
+
+            return obj;
+        }
+
+        public void Configure()
+        {
+            FastAddProperty("toString", new ClrFunctionInstance(Engine, "toString", ToGuidString), true, false, true);
+            FastAddProperty("valueOf", new ClrFunctionInstance(Engine, "valueOf", ValueOf), true, false, true);
+        }
+    }
+}

+ 50 - 0
Jint.Tests/Runtime/UuidTests.cs

@@ -0,0 +1,50 @@
+using Jint.Tests.Runtime.Domain;
+using System;
+using Xunit;
+
+namespace Jint.Tests.Runtime
+{
+    public class UuidTests : IDisposable
+    {
+        private readonly Engine _engine;
+
+        public UuidTests()
+        {
+            _engine = new Engine(o => o.AddObjectConverter(new UuidConverter()))
+                .SetValue("copy", new Func<Guid, Guid>(v => new Guid(v.ToByteArray())))
+                ;
+            UuidConstructor.CreateUuidConstructor(_engine);
+        }
+
+        void IDisposable.Dispose()
+        {
+        }
+
+        private object RunTest(string source)
+        {
+            return _engine.Execute(source).GetCompletionValue().ToObject();
+        }
+
+        [Fact]
+        public void Empty()
+        {
+            Assert.Equal(Guid.Empty, RunTest($"Uuid.parse('{Guid.Empty}')"));
+            Assert.Equal(Guid.Empty, RunTest($"Uuid.Empty"));
+        }
+
+        [Fact]
+        public void Random()
+        {
+            var actual = RunTest($"new Uuid()");
+            Assert.NotEqual(Guid.Empty, actual);
+            Assert.IsType<Guid>(actual);
+        }
+
+        [Fact]
+        public void Copy()
+        {
+            var actual = (bool)RunTest($"const g = new Uuid(); copy(g).toString() === g.toString()");
+            Assert.True(actual);
+        }
+    }
+}

+ 12 - 5
Jint/Native/Function/FunctionInstance.cs

@@ -31,7 +31,7 @@ namespace Jint.Native.Function
             JintFunctionDefinition function,
             JintFunctionDefinition function,
             LexicalEnvironment scope,
             LexicalEnvironment scope,
             FunctionThisMode thisMode)
             FunctionThisMode thisMode)
-            : this(engine, !string.IsNullOrWhiteSpace(function.Name) ? new JsString(function.Name) : null,  thisMode)
+            : this(engine, !string.IsNullOrWhiteSpace(function.Name) ? new JsString(function.Name) : null, thisMode)
         {
         {
             _functionDefinition = function;
             _functionDefinition = function;
             _environment = scope;
             _environment = scope;
@@ -51,6 +51,13 @@ namespace Jint.Native.Function
             _thisMode = thisMode;
             _thisMode = thisMode;
         }
         }
 
 
+        protected FunctionInstance(
+            Engine engine,
+            JsString name)
+            : this(engine, name, FunctionThisMode.Global, ObjectClass.Function)
+        {
+        }
+
         /// <summary>
         /// <summary>
         /// Executed when a function object is used as a function
         /// Executed when a function object is used as a function
         /// </summary>
         /// </summary>
@@ -60,7 +67,7 @@ namespace Jint.Native.Function
         public abstract JsValue Call(JsValue thisObject, JsValue[] arguments);
         public abstract JsValue Call(JsValue thisObject, JsValue[] arguments);
 
 
         public bool Strict => _thisMode == FunctionThisMode.Strict;
         public bool Strict => _thisMode == FunctionThisMode.Strict;
-        
+
         public virtual bool HasInstance(JsValue v)
         public virtual bool HasInstance(JsValue v)
         {
         {
             if (!(v is ObjectInstance o))
             if (!(v is ObjectInstance o))
@@ -212,7 +219,7 @@ namespace Jint.Native.Function
             {
             {
                 return;
                 return;
             }
             }
-            
+
             if (name is JsSymbol symbol)
             if (name is JsSymbol symbol)
             {
             {
                 name = symbol._value.IsUndefined()
                 name = symbol._value.IsUndefined()
@@ -258,7 +265,7 @@ namespace Jint.Native.Function
             {
             {
                 name = TypeConverter.ToString(nameValue);
                 name = TypeConverter.ToString(nameValue);
             }
             }
-            return "function " + name  + "() {{[native code]}}";
+            return "function " + name + "() {{[native code]}}";
         }
         }
     }
     }
-}
+}