A new finding about setting properties with compatible types

Jan 14, 2010 at 12:09 PM
Just got an ArgumentException "Cannot convert from Double to Single" when setting a CLR object property from script.

From the source it seems adding a Convert would make it work.

propertyInfo.SetValue(
Result.Value,
    Convert.ChangeType(
value.Value, propertyInfo.PropertyType),
    null);

So that it keeps the feeling of JavaScript. ;-)
Jan 14, 2010 at 12:25 PM

BTW, I'd hope you can add a new overload to Run():

object Run(string script, string scriptName)

Then on any error, the Exception message would be able to say "bla bla error in 'scriptName' line xx". It helps a lot when there're many script files loaded into the same script engine. I enjoyed that a lot with LuaInterface.

 

Jan 14, 2010 at 1:03 PM

Now I'm stuck with porting lua's dofile(). It simply runs the script from another script file. Similar to #include preprocessing but it can report correct line number in included files for any error.

So I tried to expose a host function to call JintEngine.Run(). But I got endless NoViableAltException. It seems JintEngine's Run() cannot be called recursively.

Then I tried a less ideal approach, by using eval(). I'd like to encapsulate those hacks into an include() function. However, eval() executes the script in include()'s context rather than Global context. I googled and found people use eval.call(window, script) for browsers because window is the global for browsers. But what is the global context for Jint?

Do you have any suggestion?

 

Coordinator
Jan 15, 2010 at 12:34 PM

Hi,

In Jint, there is no name for the global scope, but you can create one easily. for example, if at the beginning of your script, you have a line like this : window = this.

Then you always can access you global scope through the window variable (if you have not overriden this variable in the script you want to run of course)

Jan 15, 2010 at 1:28 PM

OK, I have refactored on how the scripts load. Now I simply load every script file one by one first.

Now, some new issue popped up. I got NoViableAltException again and again. I wondered why. Finally, I found it caused by some syntax error in the script file.

E.g. for one place I typed "for (int i = 0;...". It took me 5 minutes to realize I shouldn't use int in Javascript.

And because I'm porting from Lua, I left some if statements without brackets...

Jan 16, 2010 at 11:31 AM
Edited Jan 16, 2010 at 1:05 PM

I have now played around with Jint's source code and add some functionalities. Maybe you genius guys have already been doing something on it, I'd like to share my idea though. :)

Functionality 1) allow set to indexer of CLR object, e.g.

var dict = new System.Collections.Hashtable();
dict["key"] = value

Functionality 2) allow to access CLR object in similar way to accessing JavaScript object. e.g.

dict.key = value; // same as dict["key"] = value;
writeln(dict.key)

Functionality 3) allow set to CLR property with convertible types, e.g. assign double to a property of float type.

The patch code is in ExecutionVisitor.Assign():

            // Assignment to Clr property
            if (Result.Class == JsClr.TYPEOF)
            {
                var propertyInfo = propertyGetter.GetValue(Result.Value, propertyName);
                // Qrli
                if (propertyInfo == null)
                {
                    propertyInfo = propertyGetter.GetValue(Result.Value, "Item");
                    if (propertyInfo != null)
                    {
                        object valueObj = (value.Value is IConvertible) ?
                            Convert.ChangeType(value.Value, propertyInfo.PropertyType) : value.Value;
                        propertyInfo.SetValue(Result.Value, valueObj, new object[] { propertyName });
                    }
                }
                else
                {
                    object valueObj = (value.Value is IConvertible) ?
                        Convert.ChangeType(value.Value, propertyInfo.PropertyType) : value.Value;
                    propertyInfo.SetValue(Result.Value, valueObj, null);
                }

            }

And in ExecutionVisitor.Visit(PropertyExpression)

                var propertyInfo = propertyGetter.GetValue(CurrentScope.Value, propertyName);
                if (propertyInfo != null)
                {
                    Result = Global.WrapClr(propertyInfo.GetValue(CurrentScope.Value, null));
                    return;
                }
                // Qrli
                propertyInfo = propertyGetter.GetValue(CurrentScope.Value, "Item");
                if (propertyInfo != null)
                {
                    Result = Global.WrapClr(propertyInfo.GetValue(CurrentScope.Value, new object[] { propertyName }));
                    return;
                }

Jan 17, 2010 at 3:28 AM

Another little patch, in case the script function is not found:

        public object CallFunction(string name, params object[] args)
        {
            JsFunction func = visitor.CurrentScope[name] as JsFunction;
            if (func == null)
                throw new JintException(string.Format("function {0} not exist.", name));


            visitor.ExecuteFunction(func, null, JsClr.ConvertParametersBack(visitor, args));
            return JsClr.ConvertParameter(visitor.Returned);
        }

 

Jan 17, 2010 at 3:31 AM

One more case for NoViableAltException:

var obj = { x = 100, y = 100 };

It causes NoViableAltException rather than reports syntax error.

Jan 17, 2010 at 4:13 AM
Edited Jan 17, 2010 at 5:33 AM

There're some issues when mixing JS objects with CLR objects. E.g. add a JS object to a CLR Dictionary, then read it back, you'll get undefined. I debugged into Jint, and found it is assuming that properties got from CLR objects are always CLR objects, thus preventing JS objects being stored in CLR objects.

So more patch here:

in ExecutionVisitor.Visit(PropertyExpression)

                var propertyInfo = propertyGetter.GetValue(CurrentScope.Value, propertyName);
                if (propertyInfo != null)
                {
                    Result = Global.WrapClr(propertyInfo.GetValue(CurrentScope.Value, null));
                    return;
                }
                // Qrli
                propertyInfo = propertyGetter.GetValue(CurrentScope.Value, "Item");
                if (propertyInfo != null)
                {
                    object valueObj = propertyInfo.GetValue(CurrentScope.Value, new object[] { propertyName });
                    if (valueObj is JsInstance) // if the value is already a JS object then do not wrap it again.
                        Result = valueObj as JsInstance;
                    else
                        Result = Global.WrapClr(valueObj);
                    return;

                }

in ExecutionVisitor.Assign():

            // Assignment to Clr property
            if (Result.Class == JsClr.TYPEOF)
            {
                var propertyInfo = propertyGetter.GetValue(Result.Value, propertyName);
                // Qrli
                if (propertyInfo == null)
                {
                    propertyInfo = propertyGetter.GetValue(Result.Value, "Item");
                    if (propertyInfo != null)
                    {
                        object valueObj = value.Value;
                        if (value.Class == JsObject.TYPEOF) // not covering all cases! e.g. what about JsArray?
                            valueObj = value;
                        else if (value.Value is IConvertible)
                            valueObj = Convert.ChangeType(value.Value, propertyInfo.PropertyType);
                        propertyInfo.SetValue(Result.Value, valueObj, new object[] { propertyName });

                    }
                }
                else
                {
                    object valueObj = (value.Value is IConvertible) ?
                        Convert.ChangeType(value.Value, propertyInfo.PropertyType) : value.Value;
                    propertyInfo.SetValue(Result.Value, valueObj, null);
                }

            }

in ExecutionVisitor.Visit(Indexer)

                        PropertyInfo pi = temp.Value.GetType().GetProperty("Item");

                        if (pi == null)
                        {
                            Result = JsUndefined.Instance;
                        }
                        else
                        {
                            object[] parameters = JsClr.ConvertParameters(Result);
                            methodInvoker.GetAppropriateParameters(parameters, pi.GetGetMethod().GetParameters(), temp.Value);
                            // Qrli
                            object valueObj = pi.GetValue(temp.Value, parameters);
                            if (valueObj is JsInstance)
                                Result = valueObj as JsInstance;
                            else
                                Result = JsClr.ConvertParameterBack(this, valueObj);

                            //Result = Global.ObjectClass.New(valueObj);
                        }

I'm sure my patch has mistakes in it. I'm not very clear about the difference between wrapping CLR objects with JsClr and with JsObject. But it seems wrapping CLR objects with JsObject may make it harder to tell whether it is a wrapped CLR object or a JS object. And the difference is important when setting it to a CLR property, as it has to decide whether to unwrap it or not.

Jan 17, 2010 at 5:57 AM

Yet another finding:

var obj = new Object();

obj.toString() // [object objectname] expected but returned null.

The reason seems in JsObject:

        public override string ToString()
        {
            if (value == null)
            {
                return null;
            }

            return value.ToString();
        }

 

Jan 17, 2010 at 10:34 AM

Wow, a big surprise. I thought there's no support for subscribing CLR events yet, but you have done that!

Code like myClrButton.add_Click(myScriptHandlerFunc); works! It is a great feature to include in the document page.

BTW, at the same time finding the event subscribing works, I found num.toFixed() not work. The exception says Object of type 'Jint.Native.JsClr' cannot be converted to type 'Jint.Native.JsNumber'. I guess it is because num (of type double) is a parameter passed from the host, and it is wrapped as JsClr object.

Jan 17, 2010 at 11:46 AM

It seems JsGlobal.WrapClr() and JsGlobal.Wrap() confused me somewhat. For an example, parameters passed to script function are wrapped as JsClr. It kind of works but may work differently. If I pass 100, and it is wrapped as JsClr(100). When it is compared to Number(100), the result is false!

It seems for primitive types, use JsGlobal.Wrap() will do the right thing.

e.g. in JsClr.ConvertParameterBack(ExecutionVisitor visitor, object parameter), add the blue lines:

                if (parameter.GetType().IsArray)
                {
                    JsArray jsArray = visitor.Global.ArrayClass.New();
                    int index = -1;

                    foreach (object value in (System.Collections.IEnumerable)parameter)
                    {
                        jsArray[(index++).ToString()] = ConvertParameterBack(visitor, value);
                    }
                    return jsArray;
                }
                else if (parameter.GetType().IsPrimitive)
                    return visitor.Global.Wrap(parameter);
                else

                    return visitor.Global.WrapClr(parameter);

Then it will convert CLR primitives to JS native types, and then comparison works. However, num.toFixed() still not work. Maybe it is converted elsewhere.

Coordinator
Jan 17, 2010 at 11:47 AM

When do you sleep ? ;)

I'm currently trying to implement the stuff you discuss in this thread. I'll commit as things are available.

Sebastien

Coordinator
Jan 18, 2010 at 8:24 AM

I have commited the code for arrays and dictionaries indexers, and also indexed clr properties.

Jan 18, 2010 at 1:23 PM

Great. I have updated the code and did the merge. It works.

Maybe the next step is about storing JS object in CLR containers and getting them back? ;)