Bridging the gap between Jurassic and the DLR, Part Two
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.
