Browse Source

Assemblies and classes access

Sebastien Ros 11 years ago
parent
commit
75aae5ef1a

+ 7 - 4
Jint.Repl/Program.cs

@@ -2,6 +2,7 @@
 using System.Diagnostics;
 using System.IO;
 using System.Reflection;
+using Jint.Native;
 using Jint.Runtime;
 
 namespace Jint.Repl
@@ -10,7 +11,7 @@ namespace Jint.Repl
     {
         static void Main(string[] args)
         {
-            var engine = new Engine()
+            var engine = new Engine(cfg => cfg.AllowClr())
                 .SetValue("print", new Action<object>(Console.WriteLine))
             ;
 
@@ -49,9 +50,11 @@ namespace Jint.Repl
                 try
                 {
                     var result = engine.GetValue(engine.Execute(input).GetCompletionValue());
-                    //var str = TypeConverter.ToString(engine.Json.Stringify(engine.Json, Arguments.From(result, Undefined.Instance, "  ")));
-                    //Console.ForegroundColor = ConsoleColor.Magenta;
-                    //Console.WriteLine("=> {0}", str);
+                    if (result.Type != Types.None && result.Type != Types.Null && result.Type != Types.Undefined)
+                    {
+                        var str = TypeConverter.ToString(engine.Json.Stringify(engine.Json, Arguments.From(result, Undefined.Instance, "  ")));
+                        Console.WriteLine("=> {0}", str);
+                    }
                 }
                 catch (JavaScriptException je)
                 {

+ 5 - 0
Jint/Engine.cs

@@ -116,6 +116,11 @@ namespace Jint
 
             _statements = new StatementInterpreter(this);
             _expressions = new ExpressionInterpreter(this);
+
+            if (Options.IsClrAllowed())
+            {
+                Global.FastAddProperty("System", new NamespaceReference(this, "System"), false, false, false);
+            }
         }
 
         public LexicalEnvironment GlobalEnvironment;

+ 4 - 0
Jint/Jint.csproj

@@ -171,13 +171,17 @@
     <Compile Include="Runtime\Environments\ObjectEnvironmentRecord.cs" />
     <Compile Include="Runtime\ExpressionIntepreter.cs" />
     <Compile Include="Runtime\Interop\DefaultTypeConverter.cs" />
+    <Compile Include="Runtime\Interop\IObjectWrapper.cs" />
     <Compile Include="Runtime\Interop\ITypeConverter.cs" />
     <Compile Include="Runtime\Interop\MethodInfoFunctionInstance.cs" />
     <Compile Include="Runtime\Interop\ClrFunctionInstance.cs" />
+    <Compile Include="Runtime\Interop\NamespaceReference.cs" />
     <Compile Include="Runtime\Interop\ObjectWrapper .cs" />
     <Compile Include="Runtime\Interop\SetterFunctionInstance.cs" />
     <Compile Include="Runtime\Interop\GetterFunctionInstance.cs" />
     <Compile Include="Runtime\Interop\DelegateWrapper.cs" />
+    <Compile Include="Runtime\Interop\TypeReference.cs" />
+    <Compile Include="Runtime\Interop\TypeReferencePrototype.cs" />
     <Compile Include="Runtime\JavaScriptException.cs" />
     <Compile Include="Runtime\References\Reference.cs" />
     <Compile Include="Runtime\StatementInterpreter.cs" />

+ 1 - 1
Jint/Native/JsValue.cs

@@ -383,7 +383,7 @@ namespace Jint.Native
                 case Types.Number:
                     return _double;
                 case Types.Object:
-                    var wrapper = _object as ObjectWrapper;
+                    var wrapper = _object as IObjectWrapper;
                     if (wrapper != null)
                     {
                         return wrapper.Target;

+ 14 - 3
Jint/Options.cs

@@ -8,8 +8,9 @@ namespace Jint
         private bool _discardGlobal;
         private bool _strict;
         private bool _allowDebuggerStatement;
+        private bool _allowClr;
         private ITypeConverter _typeConverter = new DefaultTypeConverter();
-        private int _maxStatements = 0;
+        private int _maxStatements;
         private CultureInfo _culture = CultureInfo.CurrentCulture;
 
         /// <summary>
@@ -53,6 +54,12 @@ namespace Jint
             return this;
         }
 
+        public Options AllowClr(bool allowClr = true)
+        {
+            _allowClr = allowClr;
+            return this;
+        }
+
         public Options MaxStatements(int maxStatements = 0)
         {
             _maxStatements = maxStatements;
@@ -74,12 +81,17 @@ namespace Jint
         {
             return _strict;
         }
-        
+
         internal bool IsDebuggerStatementAllowed()
         {
             return _allowDebuggerStatement;
         }
 
+        internal bool IsClrAllowed()
+        {
+            return _allowClr;
+        }
+
         internal ITypeConverter GetTypeConverter()
         {
             return _typeConverter;
@@ -94,6 +106,5 @@ namespace Jint
         {
             return _culture;
         }
-
     }
 }

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

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Jint.Runtime.Interop
+{
+    public interface IObjectWrapper
+    {
+        object Target { get; }
+    }
+}

+ 29 - 18
Jint/Runtime/Interop/MethodInfoFunctionInstance.cs

@@ -19,29 +19,40 @@ namespace Jint.Runtime.Interop
 
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)
         {
-            // filter methods with the expected number of parameters
-            var methods = _methods
-                .Where(m => m.GetParameters().Count() == arguments.Length)
-                .ToArray()
-                ;
+            return Invoke(_methods, thisObject, arguments);
+        }
 
-            if (!methods.Any())
-            {
-                throw new JavaScriptException(Engine.TypeError, "Invalid number of arguments");
-            }
+        public JsValue Invoke(MethodInfo[] methodInfos, JsValue thisObject, JsValue[] arguments)
+        {
+            var methods = TypeConverter.FindBestMatch(Engine, methodInfos, arguments).ToList();
 
-            // todo: look for compatible types    
-            var method = methods.First();
-            var parameters = new object[arguments.Length];
-            for (var i = 0; i < arguments.Length; i++)
+            foreach (var method in methods)
             {
-                parameters[i] = Engine.Options.GetTypeConverter().Convert(
-                    arguments[i].ToObject(),
-                    method.GetParameters()[i].ParameterType,
-                    CultureInfo.InvariantCulture);
+                var parameters = new object[arguments.Length];
+                try
+                {
+                    for (var i = 0; i < arguments.Length; i++)
+                    {
+                        parameters[i] = Engine.Options.GetTypeConverter().Convert(
+                            arguments[i].ToObject(),
+                            method.GetParameters()[i].ParameterType,
+                            CultureInfo.InvariantCulture);
+                    }
+
+                    var result = JsValue.FromObject(Engine, method.Invoke(thisObject.ToObject(), parameters.ToArray()));
+
+                    // todo: cache method info
+
+                    return result;
+                }
+                catch
+                {
+                    // ignore method
+                }
             }
 
-            return JsValue.FromObject(Engine, method.Invoke(thisObject.ToObject(), parameters.ToArray()));
+            throw new JavaScriptException(Engine.TypeError, "No public methods with the specified arguments were found.");
         }
+
     }
 }

+ 70 - 0
Jint/Runtime/Interop/NamespaceReference.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Jint.Native;
+using Jint.Native.Object;
+using Jint.Runtime.Descriptors;
+
+namespace Jint.Runtime.Interop
+{
+    /// <summary>
+    /// Any instance on this class represents a reference to a CLR namespace.
+    /// Accessing its properties will look for a class of the full name, or instantiate
+    /// a new <see cref="NamespaceReference"/> as it assumes that the property is a deeper
+    /// level of the current namespace
+    /// </summary>
+    public class NamespaceReference : ObjectInstance
+    {
+        private readonly string _path;
+
+        public NamespaceReference(Engine engine, string path) : base(engine)
+        {
+            _path = path;
+        }
+
+        public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError)
+        {
+            if (throwOnError)
+            {
+                throw new JavaScriptException(Engine.TypeError, "Can't define a property of a NamespaceReference");
+            }
+
+            return false;
+        }
+
+        public override bool Delete(string propertyName, bool throwOnError)
+        {
+            if (throwOnError)
+            {
+                throw new JavaScriptException(Engine.TypeError, "Can't delete a property of a NamespaceReference");
+            }
+
+            return false;
+        }
+
+        public override JsValue Get(string propertyName)
+        {
+            var newPath = _path + "." + propertyName;
+            var type = Type.GetType(newPath);
+            if (type != null)
+            {
+                return TypeReference.CreateTypeReference(Engine, type);
+            }
+
+            // the new path doesn't represent a known class, thus return a new namespace instance
+
+            return new NamespaceReference(Engine, newPath);
+        }
+
+        public override PropertyDescriptor GetOwnProperty(string propertyName)
+        {
+            return PropertyDescriptor.Undefined;
+        }
+
+        public override string ToString()
+        {
+            return "[Namespace: " + _path + "]";
+        }
+    }
+}

+ 1 - 1
Jint/Runtime/Interop/ObjectWrapper .cs

@@ -10,7 +10,7 @@ namespace Jint.Runtime.Interop
     /// <summary>
     /// Wrapps a CLR instance
     /// </summary>
-    public sealed class ObjectWrapper : ObjectInstance
+    public sealed class ObjectWrapper : ObjectInstance, IObjectWrapper
     {
         public Object Target { get; set; }
 

+ 135 - 0
Jint/Runtime/Interop/TypeReference.cs

@@ -0,0 +1,135 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using Jint.Native;
+using Jint.Native.Function;
+using Jint.Native.Object;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Descriptors.Specialized;
+
+namespace Jint.Runtime.Interop
+{
+    public class TypeReference : FunctionInstance, IConstructor, IObjectWrapper
+    {
+        private TypeReference(Engine engine)
+            : base(engine, null, null, false)
+        {
+        }
+
+        public Type Type { get; set; }
+
+        public static TypeReference CreateTypeReference(Engine engine, Type type)
+        {
+            var obj = new TypeReference(engine);
+            obj.Extensible = false;
+            obj.Type = type;
+
+            // The value of the [[Prototype]] internal property of the TypeReference constructor is the Function prototype object 
+            obj.Prototype = engine.Function.PrototypeObject;
+
+            obj.FastAddProperty("length", 0, false, false, false);
+
+            // The initial value of Boolean.prototype is the Boolean prototype object
+            obj.FastAddProperty("prototype", engine.Object.PrototypeObject, false, false, false);
+
+            return obj;
+        }
+
+        public override JsValue Call(JsValue thisObject, JsValue[] arguments)
+        {
+            // direct calls on a TypeReference constructor object is equivalent to the new operator 
+            return Construct(arguments);
+        }
+
+        public ObjectInstance Construct(JsValue[] arguments)
+        {
+            var constructors = Type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
+            
+            var methods = TypeConverter.FindBestMatch(Engine, constructors, arguments).ToList();
+
+            foreach (var method in methods)
+            {
+                var parameters = new object[arguments.Length];
+                try
+                {
+                    for (var i = 0; i < arguments.Length; i++)
+                    {
+                        parameters[i] = Engine.Options.GetTypeConverter().Convert(
+                            arguments[i].ToObject(),
+                            method.GetParameters()[i].ParameterType,
+                            CultureInfo.InvariantCulture);
+                    }
+
+                    var result =  TypeConverter.ToObject(Engine, JsValue.FromObject(Engine, method.Invoke(Type, parameters.ToArray())));
+
+                    // todo: cache method info
+
+                    return result;
+                }
+                catch
+                {
+                    // ignore method
+                }
+            }
+
+            throw new JavaScriptException(Engine.TypeError, "No public methods with the specified arguments were found.");
+            
+        }
+
+        public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError)
+        {
+            if (throwOnError)
+            {
+                throw new JavaScriptException(Engine.TypeError, "Can't define a property of a TypeReference");
+            }
+
+            return false;
+        }
+
+        public override bool Delete(string propertyName, bool throwOnError)
+        {
+            if (throwOnError)
+            {
+                throw new JavaScriptException(Engine.TypeError, "Can't delete a property of a TypeReference");
+            }
+
+            return false;
+        }
+
+        public override PropertyDescriptor GetOwnProperty(string propertyName)
+        {
+            // todo: cache members locally
+            var propertyInfo = Type.GetProperty(propertyName);
+            if (propertyInfo != null)
+            {
+                return new ClrDataDescriptor(Engine, propertyInfo, Type);
+            }
+
+            var methodInfo = Type
+                .GetMethods(BindingFlags.Public | BindingFlags.Static)
+                .Where(mi => mi.Name == propertyName)
+                .ToArray();
+
+            if (methodInfo.Length == 0)
+            {
+                return PropertyDescriptor.Undefined;
+            }
+
+            return new PropertyDescriptor(new MethodInfoFunctionInstance(Engine, methodInfo), false, false, false);
+        }
+
+        public object Target
+        {
+            get
+            {
+                return Type;
+            } 
+        }
+
+        public override string Class
+        {
+            get { return Type.FullName; }
+        }
+    }
+}

+ 40 - 0
Jint/Runtime/Interop/TypeReferencePrototype.cs

@@ -0,0 +1,40 @@
+//using Jint.Native;
+//using Jint.Native.Object;
+
+//namespace Jint.Runtime.Interop
+//{
+//    public sealed class TypeReferencePrototype : ObjectInstance
+//    {
+//        private TypeReferencePrototype(Engine engine)
+//            : base(engine)
+//        {
+//        }
+
+//        public static TypeReferencePrototype CreatePrototypeObject(Engine engine, TypeReference typeReferenceConstructor)
+//        {
+//            var obj = new TypeReferencePrototype(engine);
+//            obj.Prototype = engine.Object.PrototypeObject;
+//            obj.Extensible = false;
+
+//            obj.FastAddProperty("constructor", typeReferenceConstructor, true, false, true);
+
+//            return obj;
+//        }
+
+//        public void Configure()
+//        {
+//            FastAddProperty("toString", new ClrFunctionInstance(Engine, ToTypeReferenceString), true, false, true);
+//        }
+
+//        private JsValue ToTypeReferenceString(JsValue thisObj, JsValue[] arguments)
+//        {
+//            var typeReference = thisObj.As<TypeReference>();
+//            if (typeReference == null)
+//            {
+//                throw new JavaScriptException(Engine.TypeError);
+//            }
+
+//            return typeReference.Type.FullName;
+//        }
+//    }
+//}

+ 63 - 0
Jint/Runtime/TypeConverter.cs

@@ -1,5 +1,8 @@
 using System;
+using System.Collections.Generic;
 using System.Globalization;
+using System.Linq;
+using System.Reflection;
 using Jint.Native;
 using Jint.Native.Number;
 using Jint.Native.Object;
@@ -343,5 +346,65 @@ namespace Jint.Runtime
             }
         }
 
+        public static IEnumerable<MethodBase> FindBestMatch(Engine engine, MethodBase[] methods, JsValue[] arguments)
+        {
+            methods = methods
+                .Where(m => m.GetParameters().Count() == arguments.Length)
+                .ToArray()
+                ;
+
+            if (methods.Length == 1 && !methods[0].GetParameters().Any())
+            {
+                yield return methods[0];
+                yield break;
+            }
+
+            var objectArguments = arguments.Select(x => x.ToObject()).ToArray();
+            foreach (var method in methods)
+            {
+                var perfectMatch = true;
+                var parameters = method.GetParameters();
+                for (var i = 0; i < arguments.Length; i++)
+                {
+                    if (objectArguments[i].GetType() != parameters[i].ParameterType)
+                    {
+                        perfectMatch = false;
+                        break;
+                    }
+                }
+
+                if (perfectMatch)
+                {
+                    yield return method;
+                    yield break;
+                }
+            }
+
+            var candidates = new List<MethodBase>();
+            foreach (var method in methods)
+            {
+                var parameters = new object[arguments.Length];
+                try
+                {
+                    for (var i = 0; i < arguments.Length; i++)
+                    {
+                        parameters[i] = engine.Options.GetTypeConverter().Convert(
+                            arguments[i].ToObject(),
+                            method.GetParameters()[i].ParameterType,
+                            CultureInfo.InvariantCulture);
+                    }
+                }
+                catch
+                {
+                    // ignore method
+                }
+
+                candidates.Add(method);
+            }
+
+            foreach (var candidate in candidates)
+                yield return candidate;
+        }
+
     }
 }

+ 23 - 8
README.md

@@ -58,10 +58,30 @@ If you need to pass a JavaScript callback to the CLR, then it will be converted
         new JsValue("foo") /* thisArg */, 
         new JsValue[] { 1, "bar" } /* arguments */,
         ); // "foo1bar"
-        
-# Roadmap
 
-## Features:
+## Accessing .NET assemblies and classes
+
+You can allow an engine to access any .NET class by configuring the engine instance like this:
+
+    var engine = new Engine(cfg => cfg.AllowClr())
+
+Then you have access to the `System` namespace as a global value. Here is how it's used in the context on the command line utility:
+
+    jint> var file = new System.IO.File('log.txt');
+    jint> file.WriteLine('Hello World !');
+    jint> file.Dispose();
+
+And even create shortcuts to commong .NET methods
+
+    jint> var log = System.Console.WriteLine;
+    jint> log('Hello World !');
+    => "Hello World !"
+
+Loading custom assemblies dynamically can be done by using standard reflection
+
+    var import = System.Reflection.Assembly.Load;
+    
+## Implemented features:
 
 - ECMAScript 5.1 test suite (http://test262.ecmascript.org/) 
 - Manipulate CLR objects from JavaScript, including:
@@ -81,8 +101,3 @@ If you need to pass a JavaScript callback to the CLR, then it will be converted
   - boolean -> bool
   - Regex -> RegExp
   - Function -> Delegate
-
-## Roadmap:
-
-- Instantiate CLR classes from Javascript
-- ECMAScript 6.0