view jseval.py @ 27:7f12db104246

Added evaluate, new_array, and new_object functions to JsSandbox class.
author Atul Varma <varmaa@toolness.com>
date Tue, 08 Sep 2009 09:38:46 -0700
parents 1102944b174d
children
line wrap: on
line source

# jseval.py
# By Atul Varma <varmaa@toolness.com>
#
# This is a simple example of embedding the SpiderMonkey engine into
# Python using ctypes.

import sys
import ctypes

# Sample Python function to inject into JavaScript space.
def displayMessage(msg):
    print msg

class JSEngine( object ):
    def __init__( self, dllpath ):
        self._dllpath = dllpath

    class JSClass( ctypes.Structure ):
        _fields_ = [("name", ctypes.c_char_p),
                    ("flags", ctypes.c_uint32),
                    # Mandatory non-null members
                    ("addProperty", ctypes.c_void_p),
                    ("delProperty", ctypes.c_void_p),
                    ("getProperty", ctypes.c_void_p),
                    ("setProperty", ctypes.c_void_p),
                    ("enumerate", ctypes.c_void_p),
                    ("resolve", ctypes.c_void_p),
                    ("convert", ctypes.c_void_p),
                    ("finalize", ctypes.c_void_p),
                    # Optionally non-null members
                    ("getObjectOps", ctypes.c_void_p),
                    ("checkAccess", ctypes.c_void_p),
                    ("call", ctypes.c_void_p),
                    ("construct", ctypes.c_void_p),
                    ("xdrObject", ctypes.c_void_p),
                    ("hasInstance", ctypes.c_void_p),
                    ("mark", ctypes.c_void_p),
                    ("reserveSlots", ctypes.c_void_p)
                    ]

    JSNativeFunction = ctypes.CFUNCTYPE(
        ctypes.c_int, # Return value
        ctypes.c_void_p, # Context
        ctypes.c_void_p, # Object
        ctypes.c_int, # argc
        ctypes.POINTER( ctypes.c_int ), # argv
        ctypes.POINTER( ctypes.c_int ), # retval
        )

    def _initRuntime( self ):
        self.jsdll = ctypes.cdll.LoadLibrary(self._dllpath)
        self.runtime = self.jsdll.JS_Init( 32768 )

        if self.runtime == 0:
            raise JSError( "JS_Init() failed." )

    def _shutdownRuntime( self ):
        self.jsdll.JS_Finish( self.runtime )
        del self.runtime
        del self.jsdll
        
    def _getNewContext( self ):
        context = self.jsdll.JS_NewContext(self.runtime, 8192)
        if context == 0:
            raise JSError( "JS_NewContext() failed." )
        return context

    def _createGlobalClass( self, name ):
        globalClass = self.JSClass(
            name, 0,
            ctypes.cast( self.jsdll.JS_PropertyStub, ctypes.c_void_p ),
            ctypes.cast( self.jsdll.JS_PropertyStub, ctypes.c_void_p ),
            ctypes.cast( self.jsdll.JS_PropertyStub, ctypes.c_void_p ),
            ctypes.cast( self.jsdll.JS_PropertyStub, ctypes.c_void_p ),
            ctypes.cast( self.jsdll.JS_EnumerateStub, ctypes.c_void_p ),
            ctypes.cast( self.jsdll.JS_ResolveStub, ctypes.c_void_p ),
            ctypes.cast( self.jsdll.JS_ConvertStub, ctypes.c_void_p ),
            ctypes.cast( self.jsdll.JS_FinalizeStub, ctypes.c_void_p ),
            0, 0, 0, 0, 0, 0, 0, 0
            )
        return globalClass

    def _createGlobalObject( self, context, globalClass ):
        globalObj = self.jsdll.JS_NewObject(
            context,
            ctypes.byref( globalClass ),
            0,
            0
            )

        if not globalObj:
            raise JSError( "JS_NewObject() failed." )

        return globalObj

    def _installDisplayMessageFunction( self, context, globalObj ):
        def jsDisplayMessage( context, object, argc, argv, retval ):
            jsString = self.jsdll.JS_ValueToString( context, argv[0] )
            cString = ctypes.c_char_p( self.jsdll.JS_GetStringBytes( jsString ) )
            displayMessage( cString.value )
            retval[0] = 0
            return 1

        retval = self.jsdll.JS_DefineFunction(
            context,
            globalObj,
            "displayMessage",
            self.JSNativeFunction( jsDisplayMessage ),
            1,
            0
            )

        if retval == 0:
            raise JSError( "JS_DefineFunction() failed." )
        
    def eval( self, scriptString ):
        """
        Evaluates the given string of JavaScript code and returns the
        result as a string.
        
        The string must be ascii-encodable, as this function does not
        currently use JavaScript's unicode API.
        """

        # For more information on what's going on here, see:
        #
        # http://developer.mozilla.org  [continued on next line]
        #            /en/docs/JavaScript_C_Engine_Embedder%27s_Guide
        # http://www.mozilla.org/js/spidermonkey/tutorial.html

        script = scriptString.encode( "ascii" )

        # We carefully set up and shutdown everything on each pass, to
        # make the world a happier place.
        self._initRuntime()
        context = self._getNewContext()
        name = ctypes.c_char_p( "enso" )
        globalClass = self._createGlobalClass( name )
        globalObj = self._createGlobalObject( context, globalClass )
        self._installDisplayMessageFunction( context, globalObj )
        builtins = self.jsdll.JS_InitStandardClasses( context, globalObj )

        try:
            rval = ctypes.c_int()

            ok = self.jsdll.JS_EvaluateScript(
                context,
                globalObj,
                script,
                len(script),
                "<string>",
                0,
                ctypes.byref( rval )
                )

            if not ok:
                raise JSEvalError( "JS_EvaluateScript() failed." )

            jsString = self.jsdll.JS_ValueToString( context, rval )
            cString = ctypes.c_char_p(
                self.jsdll.JS_GetStringBytes( jsString )
                )
            retval = cString.value
        finally:
            self.jsdll.JS_DestroyContext( context )
            self._shutdownRuntime()

        return retval

class JSError( Exception ):
    pass

class JSEvalError( JSError ):
    pass

if __name__ == "__main__":
    args = sys.argv[1:]
    if len(args) < 1:
        print "usage: %s <path-to-spidermonkey-dll>" % sys.argv[0]
        sys.exit(1)

    e = JSEngine(sys.argv[1])

    e.eval("displayMessage('hi from spidermonkey! ' + Math.abs(-5));")