Prechádzať zdrojové kódy

Handling types conversions in interop scenarios

Sebastien Ros 11 rokov pred
rodič
commit
830ecbbf9b

+ 116 - 0
Jint.Tests/Runtime/InteropTests.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Runtime.Remoting.Messaging;
 using Jint.Native;
 using Jint.Native.Object;
 using Xunit;
@@ -241,6 +242,115 @@ namespace Jint.Tests.Runtime
             ");
         }
 
+        [Fact]
+        public void ShouldCallInstanceMethodWithString()
+        {
+            var p = new Person();
+            _engine.SetValue("a", new A());
+            _engine.SetValue("p", p);
+
+            RunTest(@"
+                p.Name = a.Call2('foo');
+                assert(p.Name === 'foo');
+            ");
+
+            Assert.Equal("foo", p.Name);
+        }
+
+        [Fact]
+        public void CanUseTrim()
+        {
+            var p = new Person { Name = "Mickey Mouse "};
+            _engine.SetValue("p", p);
+
+            RunTest(@"
+                assert(p.Name === 'Mickey Mouse ');
+                p.Name = p.Name.trim();
+                assert(p.Name === 'Mickey Mouse');
+            ");
+
+            Assert.Equal("Mickey Mouse", p.Name);
+        }
+
+        [Fact]
+        public void CanUseMathFloor()
+        {
+            var p = new Person();
+            _engine.SetValue("p", p);
+
+            RunTest(@"
+                p.Age = Math.floor(1.6);p
+                assert(p.Age === 1);
+            ");
+
+            Assert.Equal(1, p.Age);
+        }
+
+        [Fact]
+        public void CanUseDelegateAsFunction()
+        {
+            var even = new Func<int, bool>(x => x % 2 == 0);
+            _engine.SetValue("even", even);
+
+            RunTest(@"
+                assert(even(2) === true);
+            ");
+        }
+
+
+        [Fact]
+        public void ShouldConvertArrayInstanceToArray()
+        {
+            var result = _engine.Execute("'[email protected]'.split('@');");
+            var parts = result.ToObject();
+            
+            Assert.True(parts.GetType().IsArray);
+            Assert.Equal(2, ((object[])parts).Length);
+            Assert.Equal("foo", ((object[])parts)[0]);
+            Assert.Equal("bar.com", ((object[])parts)[1]);
+        }
+
+        [Fact]
+        public void ShouldConvertBooleanInstanceToBool()
+        {
+            var result = _engine.Execute("new Boolean(true)");
+            var value = result.ToObject();
+
+            Assert.Equal(typeof(bool), value.GetType());
+            Assert.Equal(true, value);
+        }
+
+        [Fact]
+        public void ShouldConvertDateInstanceToDateTime()
+        {
+            var result = _engine.Execute("new Date(0)");
+            var value = result.ToObject();
+
+            Assert.Equal(typeof(DateTime), value.GetType());
+            Assert.Equal(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), value);
+        }
+
+        [Fact]
+        public void ShouldConvertNumberInstanceToDouble()
+        {
+            var result = _engine.Execute("new Number(10)");
+            var value = result.ToObject();
+
+            Assert.Equal(typeof(double), value.GetType());
+            Assert.Equal(10d, value);
+        }
+
+        [Fact]
+        public void ShouldConvertStringInstanceToString()
+        {
+            var result = _engine.Execute("new String('foo')");
+            var value = result.ToObject();
+
+            Assert.Equal(typeof(string), value.GetType());
+            Assert.Equal("foo", value);
+        }
+
+
         public class Person
         {
             public string Name { get; set; }
@@ -263,6 +373,12 @@ namespace Jint.Tests.Runtime
             {
                 return x;
             }
+
+            public string Call2(string x)
+            {
+                return x;
+            }
+
         }
     }
 }

+ 2 - 0
Jint/Jint.csproj

@@ -158,6 +158,8 @@
     <Compile Include="Runtime\Environments\LexicalEnvironment.cs" />
     <Compile Include="Runtime\Environments\ObjectEnvironmentRecord.cs" />
     <Compile Include="Runtime\ExpressionIntepreter.cs" />
+    <Compile Include="Runtime\Interop\DefaultTypeConverter.cs" />
+    <Compile Include="Runtime\Interop\ITypeConverter.cs" />
     <Compile Include="Runtime\Interop\MethodInfoFunctionInstance.cs" />
     <Compile Include="Runtime\Interop\ClrFunctionInstance.cs" />
     <Compile Include="Runtime\Interop\ObjectWrapper .cs" />

+ 118 - 0
Jint/Native/JsValue.cs

@@ -1,8 +1,13 @@
 using System;
 using System.Diagnostics;
 using System.Diagnostics.Contracts;
+using Jint.Native.Array;
+using Jint.Native.Boolean;
+using Jint.Native.Date;
+using Jint.Native.Number;
 using Jint.Native.Object;
 using Jint.Native.RegExp;
+using Jint.Native.String;
 using Jint.Runtime;
 using Jint.Runtime.Interop;
 
@@ -312,17 +317,45 @@ namespace Jint.Native
                     throw new ArgumentOutOfRangeException();
             }
 
+            // if an ObjectInstance is passed directly, use it as is
             var instance = value as ObjectInstance;
             if (instance != null)
             {
                 return new JsValue(instance);
             }
 
+            // if a JsValue is passed directly, use it as is
             if (value is JsValue)
             {
                 return (JsValue) value;
             }
 
+            var array = value as System.Array;
+            if (array != null)
+            {
+                var jsArray = new ArrayInstance(engine);
+                foreach (var item in array)
+                {
+                    var jsItem = FromObject(engine, item);
+                    engine.Array.PrototypeObject.Push(jsArray, Arguments.From(jsItem));
+                }
+
+                return jsArray;
+            }
+
+            var regex = value as System.Text.RegularExpressions.Regex;
+            if (regex != null)
+            {
+                var jsRegex = engine.RegExp.Construct(regex.ToString().Trim('/'));
+                return jsRegex;
+            }
+
+            var d = value as Delegate;
+            if (d != null)
+            {
+                return new DelegateWrapper(engine, d);
+            }
+
             // if no known type could be guessed, wrap it as an ObjectInstance
             return new ObjectWrapper(engine, value);
         }
@@ -346,6 +379,89 @@ namespace Jint.Native
                 case Types.Number:
                     return _double;
                 case Types.Object:
+                    var wrapper = _object as ObjectWrapper;
+                    if (wrapper != null)
+                    {
+                        return wrapper.Target;
+                    }
+
+                    switch (_object.Class)
+                    {
+                        case "Array":
+                            var arrayInstance = _object as ArrayInstance;
+                            if (arrayInstance != null)
+                            {
+                                var len = TypeConverter.ToInt32(arrayInstance.Get("length"));
+                                var result = new object[len];
+                                for (var k = 0; k < len; k++)
+                                {
+                                    var pk = k.ToString();
+                                    var kpresent = arrayInstance.HasProperty(pk);
+                                    if (kpresent)
+                                    {
+                                        var kvalue = arrayInstance.Get(pk);
+                                        result[k] = kvalue.ToObject();
+                                    }
+                                    else
+                                    {
+                                        result[k] = null;
+                                    }
+                                }
+                                return result;
+                            }
+                            break;
+                        
+                        case "String":
+                            var stringInstance = _object as StringInstance;
+                            if (stringInstance != null)
+                            {
+                                return stringInstance.PrimitiveValue.AsString();
+                            }
+
+                            break;
+
+                        case "Date":
+                            var dateInstance = _object as DateInstance;
+                            if (dateInstance != null)
+                            {
+                                return dateInstance.ToDateTime();
+                            }
+
+                            break;
+
+                        case "Boolean":
+                            var booleanInstance = _object as BooleanInstance;
+                            if (booleanInstance != null)
+                            {
+                                return booleanInstance.PrimitiveValue.AsBoolean();
+                            }
+
+                            break;
+
+                        case "Function":
+                            // todo
+                            throw new NotSupportedException("Function objects can't be converted yet");
+
+                        case "Number":
+                            var numberInstance = _object as NumberInstance;
+                            if (numberInstance != null)
+                            {
+                                return numberInstance.PrimitiveValue.AsNumber();
+                            }
+
+                            break;
+
+                        case "RegExp":
+                            var regeExpInstance = _object as RegExpInstance;
+                            if (regeExpInstance != null)
+                            {
+                                return regeExpInstance.Value;
+                            }
+
+                            break;
+                    }
+
+
                     return _object;
                 default:
                     throw new ArgumentOutOfRangeException();
@@ -459,5 +575,7 @@ namespace Jint.Native
             }
         }
 
+
+        public static object function { get; set; }
     }
 }

+ 18 - 1
Jint/Options.cs

@@ -1,10 +1,13 @@
-namespace Jint
+using Jint.Runtime.Interop;
+
+namespace Jint
 {
     public class Options
     {
         private bool _discardGlobal;
         private bool _strict;
         private bool _allowDebuggerStatement;
+        private ITypeConverter _typeConverter = new DefaultTypeConverter();
 
         /// <summary>
         /// When called, doesn't initialize the global scope.
@@ -38,6 +41,15 @@
             return this;
         }
 
+        /// <summary>
+        /// Sets a <see cref="ITypeConverter"/> instance to use when converting CLR types
+        /// </summary>
+        public Options SetTypeConverter(ITypeConverter typeConverter)
+        {
+            _typeConverter = typeConverter;
+            return this;
+        }
+
         internal bool GetDiscardGlobal()
         {
             return _discardGlobal;
@@ -52,5 +64,10 @@
         {
             return _allowDebuggerStatement;
         }
+
+        internal ITypeConverter GetTypeConverter()
+        {
+            return _typeConverter;
+        }
     }
 }

+ 1 - 2
Jint/Runtime/Descriptors/Specialized/ClrDataDescriptor.cs

@@ -30,11 +30,10 @@ namespace Jint.Runtime.Descriptors.Specialized
             set
             {
                 // attempt to convert the JsValue to the target type
-                // todo: this could be made extensible to support custom type conversion
                 var obj = value.GetValueOrDefault().ToObject();
                 if (obj.GetType() != _propertyInfo.PropertyType)
                 {
-                    obj = Convert.ChangeType(obj, _propertyInfo.PropertyType, CultureInfo.InvariantCulture);
+                    obj = _engine.Options.GetTypeConverter().Convert(obj, _propertyInfo.PropertyType, CultureInfo.InvariantCulture);
                 }
                 
                 _propertyInfo.SetValue(_item, obj, null);

+ 12 - 0
Jint/Runtime/Interop/DefaultTypeConverter.cs

@@ -0,0 +1,12 @@
+using System;
+
+namespace Jint.Runtime.Interop
+{
+    public class DefaultTypeConverter : ITypeConverter
+    {
+        public object Convert(object value, Type type, IFormatProvider formatProvider)
+        {
+            return System.Convert.ChangeType(value, type, formatProvider);
+        }
+    }
+}

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

@@ -1,4 +1,5 @@
 using System;
+using System.Globalization;
 using System.Linq;
 using Jint.Native;
 using Jint.Native.Function;
@@ -20,7 +21,18 @@ namespace Jint.Runtime.Interop
 
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)
         {
-            return JsValue.FromObject(Engine, _d.DynamicInvoke(arguments.Select(x => x.ToObject()).ToArray()));
+            // convert parameter to expected types
+            var parameters = new object[arguments.Length];
+            for (var i = 0; i < arguments.Length; i++)
+            {
+                parameters[i] = Engine.Options.GetTypeConverter().Convert(
+                    arguments[i].ToObject(),
+                    _d.Method.GetParameters()[i].ParameterType,
+                    CultureInfo.InvariantCulture);
+            }
+
+
+            return JsValue.FromObject(Engine, _d.DynamicInvoke(parameters));
         }
     }
 }

+ 9 - 0
Jint/Runtime/Interop/ITypeConverter.cs

@@ -0,0 +1,9 @@
+using System;
+
+namespace Jint.Runtime.Interop
+{
+    public interface ITypeConverter
+    {
+        object Convert(object value, Type type, IFormatProvider formatProvider);
+    }
+}

+ 3 - 11
Jint/Runtime/Interop/MethodInfoFunctionInstance.cs

@@ -1,5 +1,4 @@
-using System;
-using System.Globalization;
+using System.Globalization;
 using System.Linq;
 using System.Reflection;
 using Jint.Native;
@@ -36,20 +35,13 @@ namespace Jint.Runtime.Interop
             var parameters = new object[arguments.Length];
             for (var i = 0; i < arguments.Length; i++)
             {
-                parameters[i] = Convert.ChangeType(
+                parameters[i] = Engine.Options.GetTypeConverter().Convert(
                     arguments[i].ToObject(),
                     method.GetParameters()[i].ParameterType,
                     CultureInfo.InvariantCulture);
             }
 
-            var obj = thisObject.ToObject() as ObjectWrapper;
-
-            if (obj == null)
-            {
-                throw new JavaScriptException(Engine.TypeError, "Can't call a CLR method on a non CLR instance");
-            }
-
-            return JsValue.FromObject(Engine, method.Invoke(obj.Target, parameters.ToArray()));
+            return JsValue.FromObject(Engine, method.Invoke(thisObject.ToObject(), parameters.ToArray()));
         }
     }
 }