Running with Code Like with scissors, only more dangerous

16Feb/120

Bridging the gap between Jurassic and the DLR, Part Two

Posted by Rob Paveza

Part Two: A More Complete Object Model

Once I started implementing the rest of the object model, things started coming together very well.

Let's take a look at the rest of the ObjectInstance implementation:

        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            return TryCallMemberFunction(out result, binder.Name, args);
        }

        public override bool TryConvert(ConvertBinder binder, out object result)
        {
            if (binder.ReturnType == typeof(string))
            {
                result = this.ToString();
                return true;
            }
            else
            {
                try
                {
                    result = Jurassic.TypeConverter.ConvertTo(this.engine, this, binder.ReturnType);
                    return true;
                }
                catch
                {
                    result = null;
                    return false;
                }
            }
        }

        public override bool TryDeleteMember(DeleteMemberBinder binder)
        {
            return Delete(binder.Name, false);
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            this.FastSetProperty(binder.Name, value, PropertyAttributes.FullAccess, true);
            return true;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = GetNamedPropertyValue(binder.Name, this);
            if (object.ReferenceEquals(null, result))
                return false;

            return true;
        }

For FunctionInstance:

        public override bool TryInvoke(System.Dynamic.InvokeBinder binder, object[] args, out object result)
        {
            try
            {
                result = CallLateBound(this, args);
                return true;
            }
            catch
            {
                result = null;
                return false;
            }
        }

For ArrayInstance, things were a little more interesting. JavaScript doesn't support multidimensional arrays (as in C#, where you can access something via someArr[1,5]). However, it's important to consider the type. Fortunately, Jurassic provides that as well, fairly easily.

        public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
        {
            Debug.Assert(indexes != null && indexes.Length > 0);

            result = null;

            if (indexes.Length > 1)
                return false; // multi-dimensional arrays are not supported.

            if (object.ReferenceEquals(null, indexes[0]))
                return false;

            Type indexType = indexes[0].GetType();
            if (indexType.IsEnum)
            {
                indexType = Enum.GetUnderlyingType(indexType);
            }

            if (indexType == typeof(byte) || indexType == typeof(sbyte) || indexType == typeof(short) || indexType == typeof(ushort) || indexType == typeof(int) || indexType == typeof(uint))
            {
                uint index = unchecked((uint)indexes[0]);
                try
                {
                    result = this[index];
                    return true;
                }
                catch
                {
                    result = null;
                    return false;
                }
            }
            else
            {
                string index = indexes[0].ToString();
                try
                {
                    result = this[index];
                    return true;
                }
                catch
                {
                    result = null;
                    return false;
                }
            }
        }

        public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
        {
            Debug.Assert(indexes != null && indexes.Length > 0);

            if (indexes.Length > 1)
                return false; // multi-dimensional arrays are not supported.

            if (object.ReferenceEquals(null, indexes[0]))
                return false;

            Type indexType = indexes[0].GetType();
            if (indexType.IsEnum)
            {
                indexType = Enum.GetUnderlyingType(indexType);
            }

            if (indexType == typeof(byte) || indexType == typeof(sbyte) || indexType == typeof(short) || indexType == typeof(ushort) || indexType == typeof(int) || indexType == typeof(uint))
            {
                uint index = unchecked((uint)indexes[0]);
                try
                {
                    this[index] = value;
                    return true;
                }
                catch
                {
                    return false;
                }
            }
            else
            {
                string index = indexes[0].ToString();
                try
                {
                    this[index] = value;
                    return true;
                }
                catch
                {
                    return false;
                }
            }
        }

        public override bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes)
        {
            Debug.Assert(indexes != null && indexes.Length > 0);

            if (indexes.Length > 1)
                return false; // multi-dimensional arrays are not supported.

            if (object.ReferenceEquals(null, indexes[0]))
                return false;

            Type indexType = indexes[0].GetType();
            if (indexType.IsEnum)
            {
                indexType = Enum.GetUnderlyingType(indexType);
            }

            if (indexType == typeof(byte) || indexType == typeof(sbyte) || indexType == typeof(short) || indexType == typeof(ushort) || indexType == typeof(int) || indexType == typeof(uint))
            {
                uint index = unchecked((uint)indexes[0]);
                return Delete(index, false);
            }
            else
            {
                string index = indexes[0].ToString();
                return Delete(index, false);
            }
        }

I did add one set of precompilation directives so that I could modify ScriptEngine with one little item:

        public 
#if SUPPORT_DYNAMIC
            dynamic
#else
            object 
#endif
            GetGlobalValue(string variableName)
        {
            if (variableName == null)
                throw new ArgumentNullException("variableName");
            return TypeUtilities.NormalizeValue(this.Global.GetPropertyValue(variableName));
        }

With that gem, we're in good shape. I'll update the test program; let's take a good look:

    class Program
    {
        static void Main(string[] args)
        {
            ScriptEngine engine = new ScriptEngine();

            engine.SetGlobalFunction("write", (Action<string>) ((s) => { Console.WriteLine(s); }));
            
            engine.Execute(@"
var a = {
    A: 'A',
    B: 20,
    C: function() { return 'Hello'; }
};

function double(val)
{
    return val * 2;
}

var array = [1, 5, 9, 13, 21];
");
            dynamic obj = engine.Evaluate<ObjectInstance>("a");
            Console.WriteLine(obj.A);
            Console.WriteLine(obj.B);
            Console.WriteLine(obj.C());
            obj.D = "What's that?";

            Console.WriteLine("C#: " + obj.D);

            engine.Execute(@"
write('JavaScript: ' + a.D);
");

            dynamic dbl = engine.GetGlobalValue("double");
            Console.WriteLine(dbl(20));
            Console.WriteLine(dbl.call(null, 20));

            dynamic array = engine.GetGlobalValue("array");
            Console.WriteLine(array[2]);

            Console.ReadLine();
            
        }
    }

Output is happily correct:

A
20
Hello
C#: What's that?
JavaScript: What's that?
40
40
9

Particularly neat about this implementation is that the calls automatically recurse. Note that I use the intrinsic JavaScript call method on the Function instance (of dbl). This implementation covers a whole bunch of typical scenarios and use-cases, and I'm happy to see that it has worked out fairly well thus far.

One item I've found is that there's a TypeLoadException when targeting .NET 4. This has something to do with a new CAS policy in .NET 4. For now, applying this attribute to the test program as well as the library will resolve the issue, though I don't intend for it to be long-term:

[assembly: System.Security.SecurityRules(System.Security.SecurityRuleSet.Level1)]

Next time, we'll do some more fit and finish, with precompilation constants and a security review.

14Feb/120

Bridging the gap between Jurassic and the DLR, Part One

Posted by Rob Paveza

Part One: ObjectInstance derives from DynamicObject

A while back I posted that I was joining the Jurassic team; Jurassic is an open-source JavaScript engine for .NET. If you've ever gone through the long search for a JavaScript implementation on .NET (other than JScript.NET, of course), there are a bunch of incomplete implementations, and if you're lucky enough to find the blog about the Microsoft project of JScript running on the DLR (once called Managed JScript), you'll find that it was an implementation that was specifically designed to give design feedback on the DLR itself, and was not planned to be carried forward into production. Personally I think that's too bad, but I'm happy to see a couple of projects (notably, Jurassic and IronJS) that have stepped up to fill the gap.

I had considered implementing IDynamicMetaObjectProvider, but inheriting from DynamicObject seems to be a better design decision all-around. Since all of the other JavaScript objects inherit from ObjectInstance, it's a simple matter of overriding its virtual methods instead of creating a new implementation of DynamicMetaObject for each class in the hierarchy.

I've created a new library project within the solution as well as a simple testing project to advise on the API as well as to step into my DynamicObject overrides. Here are some simple components:

// These are new:
using System.Dynamic;
using System.Diagnostics;

// This is updated
    public class ObjectInstance
        : DynamicObject
#if !SILVERLIGHT
        , System.Runtime.Serialization.IDeserializationCallback
#endif
    {

// The class exists as normal
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = GetNamedPropertyValue(binder.Name, this);
            if (result != null)
                return true;

            return false;
        }

        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            try
            {
                result = CallMemberFunction(binder.Name, args);
                return true;
            }
            catch
            {
                result = null;
                return false;
            }
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            this.AddProperty(binder.Name, value, PropertyAttributes.FullAccess, true);
            return true;
        }

    }

This is the source of the test application. It's very straightforward:

        static void Main(string[] args)
        {
            ScriptEngine engine = new ScriptEngine();

            engine.SetGlobalFunction("write", (Action<string>) ((s) => { Console.WriteLine(s); }));
            
            engine.Execute(@"
var a = {
    A: 'A',
    B: 20,
    C: function() { return 'Hello'; }
};
");
            dynamic obj = engine.Evaluate<ObjectInstance>("a");
            Console.WriteLine(obj.A);
            Console.WriteLine(obj.B);
            Console.WriteLine(obj.C());
            obj.D = "What's that?";

            Console.WriteLine("C#: " + obj.D);

            engine.Execute(@"
write('JavaScript: ' + a.D);
");

            Console.ReadLine();
            
        }

We create a global function 'write' which writes a string to the console. Then we create a global object a with properties A, B, and C. We then use C# to retrieve the value of this object as a dynamic. This is what provides us access to the DynamicObject's overrides intrinsic within C#'s support of the DLR. We then access each property (which each return a dynamic) and, happily because of Jurassic's automatic conversion of primitives to their respective .NET types, when these values are returned as dynamic, they can be automatically converted to their appropriate types for their Console.WriteLine parameter. You can see that we invoke TryGetMember on A and B, TryInvokeMember on C, and TrySetMember and then TryGetMember on D.

OK, it's late, so I'm not going to stick this out anymore right now. I'm not even sure if the previous paragraph was particularly coherent. :-)

There's a lot to update: it doesn't support case-insensitive languages (like Visual Basic), it's not particularly good at error checking, and I haven't dealt with any other components yet. The good news is that it seems like we should be good to go for the rest of the components.

Next time, we'll look at other classes, like ArrayInstance, FunctionInstance, and more.