#31 new
qvx 3000

add support for scopes

Reported by qvx 3000 | November 9th, 2009 @ 09:19 AM

I need to use "scope chain" while evaluating scripts. I have to be able to make a "scope chain" and use it as it is explained in ECMAScript 10.1.4. I see that there are some functions to do that: JS_SetParent and JS_GetParent and even JS_NewObject which can do this in one step. Later, you use JS_ExecuteScript and give it the scope object created with those functions.

I'm writing a VoiceXML interpreter and it relies heavily on declaring variables and executing scripts in different scopes (application, document, form, form-item ...). Scopes form the chain and the leaf/local scopes are frequently created and destroyed in the process of interpreting VoiceXML document. It is important to hide the variables from enclosing scopes when I'm declaring variable in local scope, and also to be able to get to those masked/hidden variables when the local scope is destroyed. To be able to do this I have to use built in javascript scope support. If I understand correctly, this is done with manipulating "parent" of objects (JS_SetParent / JS_NewObject) and executing scripts with explicitly stating the scope object (JS_ExecuteScript). Also, see JS_GetScopeChain.

Comments and changes to this ticket

  • qvx 3000

    qvx 3000 November 9th, 2009 @ 10:00 AM

    I see that you are using Context->root as this scope object. How about adding enter_scope/leave_scope which manipulate the root scope object.

    Enter would create new object, set root as its parent and then replace the root with itself (all in one step):

      Context_enter_scope(){

    self->root = JS_NewObject(self->cx, &js_global_class, NULL, self->root);
    
    
    
    
    }
    Leave would do the opposite:
      Context_leave_scope(){

    self->root = JS_GetParent(self->cx, self->root);
    
    
    
    
    }
    I don't know if anything has to be destroyed/unallocated here.
  • Paul J. Davis

    Paul J. Davis November 9th, 2009 @ 01:21 PM

    I tried this at one point but I can't find the code for it. The bottom line though is that even though it looks like this should work, it's not quite 100% correct. There are weird interactions with how variable declarations work and which object they get attached to.

    You might look into the V8 bindings. The V8 documentation I read seemed to target this specific use case quite pointedly. I haven't the slightest if that functionality is exposed in the bindings or not though.

  • qvx 3000

    qvx 3000 November 9th, 2009 @ 04:26 PM

    I'v done as I proposed above and the simple tests are working correctly. What did you have in mind when you said that it is not 100% percent correct?

    tests:

    @t.cx()
    def test_scope(cx):
        t.eq(cx.execute("var g=1; var x = 4; x * x;"), 16)
        t.eq(cx.execute("g"), 1)
        t.eq(cx.execute("x"), 4)
        t.eq(cx.enter_scope(), None)
        t.eq(cx.execute("var x = 5; x * x;"), 25)
        t.eq(cx.execute("g"), 1)
        t.eq(cx.execute("x"), 5)
        t.eq(cx.leave_scope(), None)
        t.eq(cx.execute("g"), 1)
    t.eq(cx.execute("x"), 4)
    t.raises(Exception, cx.leave_scope)
    

    P.S. comment preview would be a nice feature to have.

  • qvx 3000

    qvx 3000 November 9th, 2009 @ 04:33 PM

    And this is the implementation:

    PyObject*
    Context_enter_scope(Context* self, PyObject* args, PyObject* kwargs)
    {
        PyObject* ret = NULL;
        JSObject* root = NULL;
    
        JS_BeginRequest(self->cx);
    
        root = JS_NewObject(self->cx, &js_global_class, NULL, self->root);
        if(root == NULL)
        {
            PyErr_SetString(PyExc_RuntimeError, "Error creating root object while entering scope.");
            goto error;
        }
        self->root = root;
    
        JS_EndRequest(self->cx);
        ret = Py_None;
        Py_INCREF(ret);
        goto success;
    
    error:
        JS_EndRequest(self->cx);
    success:
        return ret;
    }
    
    PyObject*
    Context_leave_scope(Context* self, PyObject* args, PyObject* kwargs)
    {
        PyObject* ret = NULL;
        JSObject* root = NULL;
    
        JS_BeginRequest(self->cx);
    
        root = JS_GetParent(self->cx, self->root);
        if(root == NULL)
        {
            PyErr_SetString(PyExc_RuntimeError, "Cannot leave scope if it was not entered.");
            goto error;
        }
        self->root = root;
    
        JS_EndRequest(self->cx);
        ret = Py_None;
        Py_INCREF(ret);
        goto success;
    
    error:
        JS_EndRequest(self->cx);
    success:
        return ret;
    }
    
  • Paul J. Davis

    Paul J. Davis November 9th, 2009 @ 04:55 PM

    Oh, I think the reason is that you're creating a blank scope where as I was doing a leaky thing.

    What happens if you do something like:

    @t.cx()
    def test_scope(cx):
        t.eq(cx.execute("var x = 4; x;"), 4)
        t.eq(cx.enter_scope(), None)
        t.eq(cx.execute("x;"), 4)
    

    I swear I had a ticket around here that had some code that another guy wrote to walk the parent chain to get the different bits.

    The 100% correct thing is that with the other patch that I had working, variable assignments were leaking outside of the nested scopes. It was finicky on how you used var instantiation and which global got written to etc etc. I ended up dropping it because of that weirdness.

  • Paul J. Davis

    Paul J. Davis November 9th, 2009 @ 04:58 PM

    Oh found it, it's an email thread I had with some guy. If you email me at paul.joseph.davis@gmail.com I'll forward you the conversation and what my thinking was that time so you can comment.

  • qvx 3000

    qvx 3000 November 9th, 2009 @ 05:17 PM

    I see! You are using custom "global class" implementation to track global objects. It is an error to create another global when entering a new scope. To fix this I have to say:

    root = JS_NewObject(self->cx, NULL, NULL, self->root);
    

    There are other changes, like introducing root0 to Context struct to keep track of original root (the one that is used for global object tracking). More details in scope.patch file.

    New test:

    @t.rt()
    def test_scope(rt):
        glbl = {}
        cx = rt.new_context(glbl=glbl)
        t.eq(cx.execute("var g=1; var x = 4; x * x;"), 16)
        t.eq(cx.add_global('a', 1), None)
        t.eq(cx.execute("g"), 1)
        t.eq(cx.execute("x"), 4)
        t.eq(glbl, {'g':1, 'a':1, 'x':4})
        t.eq(cx.enter_scope(), None)
        t.eq(cx.execute("var dummy=0; var x = 5; x * x;"), 25)
        t.eq(cx.execute("g"), 1)
        t.eq(cx.execute("x"), 5)
        t.eq(cx.add_global('A', 1), None)
        t.eq(glbl, {'g':1, 'a':1, 'A':1, 'x':4})
        t.eq(cx.leave_scope(), None)
        t.eq(cx.execute("a"), 1)
        t.eq(cx.execute("A"), 1)
        t.eq(cx.execute("g"), 1)
        t.eq(cx.execute("x"), 4)
        t.eq(glbl, {'g':1, 'a':1, 'A':1, 'x':4})
        t.raises(Exception, cx.leave_scope)
    
  • qvx 3000

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile ยป

Python/JavaScript bridge module, making use of Mozilla's spidermonkey JavaScript implementation. Allows implementation of JavaScript classes, objects and functions in Python, and evaluation and calling of JavaScript scripts and functions respectively. Borrows heavily from Claes Jacobssen's Javascript Perl module, in turn based on Mozilla's 'PerlConnect' Perl binding.

People watching this ticket

Attachments

Pages