ExecutionVisitor CallFunction

Feb 12, 2010 at 8:33 PM

I'm trying to add some custom functionality to the SVN build of Jint. However, the flow of some methods in the ExecutionVisitor is very strange. Right now I'm most stumped on CallFunction(JsFunction function, JsDictionaryObject that, JsInstance[] parameters). Is there a reason why you are passing 'that' and 'parameters'? The only thing the method does is call 'Accept' on the Statement stored in 'function'. Am I missing something? Shouldn't you be passing 'that' and 'parameters' to Accept? Otherwise how are statements not created by the parser expected to retrieve the parameter information?

Oversaucy

Coordinator
Feb 13, 2010 at 12:39 PM

Hi,

The ExecutionVisitor, as the name says is a Visitor. A statement can be visited by the visitor. I would suggest you to get information about the visitor pattern. Mainly, the aim of a visitor is to have the logic of the execution outside the class itself.

Feb 13, 2010 at 5:10 PM

Here's the problem I'm having. Suppose I create a new statement type, StatementX. If I create a JsFunction and manually create the BlockedStatement :: ExpressionStatement :: MemberStatement :: StatementX structure for the JsFunction's statement the StatementX will never receive the arguments passed to the MemberStatement. MethodCall is passed the argument values when it is actually created by the E3Parser. However, the arguments are also passed around for a short type, as well as the calling object, via JsDictionaryObject that and JsInstance[] parameters. These get lost when calling BlockStatement.Statements[x].Accept(this). The only way for StatementX to ever know what values were passed is to change the definition of all Accept( type ) in the statement/expression classes to be Accept ( IJintVisitor, JsDictionaryObject, JsInstance[]) and all Visit methods in IJintVisitor to Visit ( type, JsDictionaryObject, JsInstance[] ). When you do this each statement or expression is passed its arguments by the execution rather than when the statement is generated by the parser.

Also, the ExecutionVisitor isn't really a visitor, it handles all the primary execution logic of the code it's handling. I still don't see why you aren't passing the arguments at execution instead of assigning them when lex/parsing the code text. The way you do it now the only way to get args from a script is to create a new construct within the parser. You can't create pseudo elements that aren't intended to be based on the script file.

Oversaucy

Coordinator
Feb 15, 2010 at 7:24 AM

Hi,

Can you tell us more about what you would like to do ? It would be easier for us to help you if we know what you want to do.

Feb 15, 2010 at 4:00 PM
Edited Feb 15, 2010 at 4:04 PM

I'm trying to register native CLR objects within the Jint scope. This is trivial, I have a working base that does it completely automagically. However there is one major gotcha. Here's my long winded around about way of describing the problem and what I did to solve it. 

Since javascript objects are simply functions with attributes I simply took a JsFunction object and registered functions in its Scope. For example.

Jint.Delegates.Func HelloWorld = new Jint.Delegates.Func<JsInstance[], JsInstance>
(
     delegate(JsInstance[] args)
     {
          Console.WriteLine(args[0].Value.ToString());
          return new JsNumber(0);
     }
);

JsFunction someObject = new JsFunction(); 
someObject.Scope["HelloWorld"] = new JsFunctionWrapper(HelloWorld);

JintEngine myEngine = new JintEngine(Options.Ecmascript3);
myEngine.SetFunction("SomeObject", someObject);

This works. The script would look something like:

var myObject = new SomeObject();
myObject.HelloWorld("My Message");

However, an important aspect of objects is their constructor. Since the JsInstance itself stores its execution code in a Statement object we can't simply push a JsFunction/JsFunctionWrapper the same way. Consider:

var O = new SomeObject();
var myFunction = function ( message )
{
     O.HelloWorld(message);
}   

The JsObject representing myFunction doesn't store the sequence of code "O.HelloWorld(message)" within its scope. Rather it stores it as a Statement. In this case it would look something like:

JsObject
     Statement (BlockStatement)
          ExpressionStatement
               MemberExpression
                    MethodCall
                        JsInstance[] Arguments

The JsInstance Arguments is assigned directly from the E3Parser. This means that there is no way for someone to create a new Statement type that receives this argument data since it is only received directly from the parser. Now consider that SomeObject in my original example has only a default constructor that apparently does nothing. What if you wanted the constructor to do something, or receive arguments? Because it is a Statement and not a JsInstance you cannot simply wrap a Jint.Delegates.Func<TResult,T> with a JsFunctionWrapper. You have to either rewrite a portion of the E3Parser and create your own Statement type, manually unwrap a function by creating your own BlockStatement, which is a horrendous thought to begin with, have the constructor/function code simply be a JsInstance within the scope using the same name as the function register, or to pass the JsDictionaryObject and JsInstance[] around via IJintVisitor.Visit and Statement.Accept. This way you can simply create your own statement type and you received the arguments whenever something calls the Accept method or in the Visit code you will have to add to the ExecutionVisitor. 

 

Hopefully this clarifies what I'm trying to do, what I did to solve my problem, and perhaps insight into how I'm 'doing it wrong'. I had a much more elegant post written up but codeplex gave me an awesome error when I tried to fix the generic list in the first example and I lost the entire thing D:

 

Oversaucy

 

 

 

Coordinator
Feb 15, 2010 at 7:52 PM

Hi,

If you want to put some logic in the constructor, why don't you make a JsFunctionWrapper (which can receive JsInstance[]). When you make new SomeObject(), the behavior (like in any other javascript interpreter), is to create an empty object and apply the function on this newly created object. You can also make your custom class inheriting from JsFunction. 

Nicolas Penin

Feb 16, 2010 at 5:34 AM

Apparently I had some magical code that made my above examples work, because I can't get them, verbatim, to work anymore. Perhaps you can further assist me with examples as to how to do what I'm trying to do. What I was expecting would work, which did somehow for a period of time, would be to declare a Jint.Native.JsFunction and simply add other Jint.Native.JsFunctions to its Scope property via someJsFunction.Scope["MyFunction"] = new JsFunctionWrapper( ... ); However, this simply creates a JsFunction no properties defined. I know at some point I was able to create an object references via the `new` keyword and call function properties created from C#. 

Now, however all I get are exceptions that read 

 

Object expected: Test
Line: 2 Char: 0
No source code available.

Object expected: MyFunction

Line: 2 Char: 0

No source code available.

On top of registering function and value properties of a function object I'd like to be able to define the constructor function. The problem I was having above, which you didn't really answer how to do it, is that the JsFunction's actual functional block is a Statement which isn't really ideal to use in creating a method block. It's really designed for building up the parsed representation of the script file. Would be splendid if you had any insight as to what I'm trying to do. Something to note, the use for this code requires that the executing script cannot have any access to CLR types and therefore requires visible objects to be explicitly registered through the JintEngine.

 

Oversaucy

 

Coordinator
Feb 16, 2010 at 10:01 AM

Hi,

There was a bug I was working on. Now it should be corrected. I think what you want to do is similar to a JsConstructor. Have you tried having a look at JsArrayConstructor or any other constructor ?

What I would suggest you to inherit from JsConstructor and override the Execute method (this is code will be executed when you call a new on your function. If "that" is null, this is because it was not used with a new. If you override the InitPrototype method, you should be able to add everything you need to the prototype (like in the JsArrayConstructor).

Hope this helps.

Nicolas Penin

Feb 16, 2010 at 11:07 PM

Thanky thanky, got it working again. Everything looks good now :D