Commits

Amaury Forgeot d'Arc committed 0a6b064

cpyext: implement Py_CompileString and PyImport_ExecCodeModule

Comments (0)

Files changed (6)

pypy/module/cpyext/eval.py

 from pypy.interpreter.error import OperationError
 from pypy.rpython.lltypesystem import rffi, lltype
 from pypy.module.cpyext.api import (
-    cpython_api, CANNOT_FAIL, CONST_STRING, FILEP, fread, feof, Py_ssize_tP)
+    cpython_api, CANNOT_FAIL, CONST_STRING, FILEP, fread, feof, Py_ssize_tP,
+    cpython_struct)
 from pypy.module.cpyext.pyobject import PyObject, borrow_from
 from pypy.module.cpyext.pyerrors import PyErr_SetFromErrno
 from pypy.module.__builtin__ import compiling
 
+PyCompilerFlags = cpython_struct(
+    "PyCompilerFlags", ())
+PyCompilerFlagsPtr = lltype.Ptr(PyCompilerFlags)
+
 @cpython_api([PyObject, PyObject, PyObject], PyObject)
 def PyEval_CallObjectWithKeywords(space, w_obj, w_arg, w_kwds):
     return space.call(w_obj, w_arg, w_kwds)
 Py_file_input = 257
 Py_eval_input = 258
 
-def run_string(space, source, filename, start, w_globals, w_locals):
+def compile_string(space, source, filename, start):
     w_source = space.wrap(source)
     start = rffi.cast(lltype.Signed, start)
     if start == Py_file_input:
         mode = 'single'
     else:
         raise OperationError(space.w_ValueError, space.wrap(
-            "invalid mode parameter for PyRun_String"))
-    w_code = compiling.compile(space, w_source, filename, mode)
+            "invalid mode parameter for compilation"))
+    return compiling.compile(space, w_source, filename, mode)
+
+def run_string(space, source, filename, start, w_globals, w_locals):
+    w_code = compile_string(space, source, filename, start)
     return compiling.eval(space, w_code, w_globals, w_locals)
 
 @cpython_api([CONST_STRING], rffi.INT_real, error=-1)
         pi[0] = space.getindex_w(w_obj, None)
     return 1
 
+@cpython_api([rffi.CCHARP, rffi.CCHARP, rffi.INT_real, PyCompilerFlagsPtr],
+             PyObject)
+def Py_CompileStringFlags(space, source, filename, start, flags):
+    """Parse and compile the Python source code in str, returning the
+    resulting code object.  The start token is given by start; this
+    can be used to constrain the code which can be compiled and should
+    be Py_eval_input, Py_file_input, or Py_single_input.  The filename
+    specified by filename is used to construct the code object and may
+    appear in tracebacks or SyntaxError exception messages.  This
+    returns NULL if the code cannot be parsed or compiled."""
+    source = rffi.charp2str(source)
+    filename = rffi.charp2str(filename)
+    if flags:
+        raise OperationError(space.w_NotImplementedError, space.wrap(
+                "cpyext Py_CompileStringFlags does not accept flags"))
+    return compile_string(space, source, filename, start)

pypy/module/cpyext/import_.py

 from pypy.module.cpyext.api import (
     generic_cpy_call, cpython_api, PyObject, CONST_STRING)
 from pypy.module.cpyext.pyobject import borrow_from
-from pypy.rpython.lltypesystem import rffi
+from pypy.rpython.lltypesystem import lltype, rffi
 from pypy.interpreter.error import OperationError
 from pypy.interpreter.module import Module
+from pypy.interpreter.pycode import PyCode
+from pypy.module.imp import importing
 
 @cpython_api([PyObject], PyObject)
 def PyImport_Import(space, w_name):
     w_modulesDict = space.sys.get('modules')
     return borrow_from(None, w_modulesDict)
 
+@cpython_api([rffi.CCHARP, PyObject], PyObject)
+def PyImport_ExecCodeModule(space, name, w_code):
+    """Given a module name (possibly of the form package.module) and a code
+    object read from a Python bytecode file or obtained from the built-in
+    function compile(), load the module.  Return a new reference to the module
+    object, or NULL with an exception set if an error occurred.  Before Python
+    2.4, the module could still be created in error cases.  Starting with Python
+    2.4, name is removed from sys.modules in error cases, and even if name was
+    already in sys.modules on entry to PyImport_ExecCodeModule().  Leaving
+    incompletely initialized modules in sys.modules is dangerous, as imports of
+    such modules have no way to know that the module object is an unknown (and
+    probably damaged with respect to the module author's intents) state.
+
+    The module's __file__ attribute will be set to the code object's
+    co_filename.
+
+    This function will reload the module if it was already imported.  See
+    PyImport_ReloadModule() for the intended way to reload a module.
+
+    If name points to a dotted name of the form package.module, any package
+    structures not already created will still not be created.
+
+    name is removed from sys.modules in error cases."""
+    return PyImport_ExecCodeModuleEx(space, name, w_code,
+                                     lltype.nullptr(rffi.CCHARP.TO))
+
+
+@cpython_api([rffi.CCHARP, PyObject, rffi.CCHARP], PyObject)
+def PyImport_ExecCodeModuleEx(space, name, w_code, pathname):
+    """Like PyImport_ExecCodeModule(), but the __file__ attribute of
+    the module object is set to pathname if it is non-NULL."""
+    code = space.interp_w(PyCode, w_code)
+    w_name = space.wrap(rffi.charp2str(name))
+    if pathname:
+        pathname = rffi.charp2str(pathname)
+    else:
+        pathname = code.co_filename
+    w_mod = importing.add_module(space, w_name)
+    space.setattr(w_mod, space.wrap('__file__'), space.wrap(pathname))
+    importing.exec_code_module(space, w_mod, code)
+    return w_mod

pypy/module/cpyext/include/pythonrun.h

 
 #define Py_FrozenFlag 0
 
+typedef struct {
+    int cf_flags;  /* bitmask of CO_xxx flags relevant to future */
+} PyCompilerFlags;
+
+#define Py_CompileString(str, filename, start) Py_CompileStringFlags(str, filename, start, NULL)
+
 #ifdef __cplusplus
 }
 #endif

pypy/module/cpyext/stubs.py

     """
     raise NotImplementedError
 
-@cpython_api([rffi.CCHARP, PyObject], PyObject)
-def PyImport_ExecCodeModule(space, name, co):
-    """Given a module name (possibly of the form package.module) and a code
-    object read from a Python bytecode file or obtained from the built-in
-    function compile(), load the module.  Return a new reference to the module
-    object, or NULL with an exception set if an error occurred.  Before Python
-    2.4, the module could still be created in error cases.  Starting with Python
-    2.4, name is removed from sys.modules in error cases, and even if name was
-    already in sys.modules on entry to PyImport_ExecCodeModule().  Leaving
-    incompletely initialized modules in sys.modules is dangerous, as imports of
-    such modules have no way to know that the module object is an unknown (and
-    probably damaged with respect to the module author's intents) state.
-
-    The module's __file__ attribute will be set to the code object's
-    co_filename.
-
-    This function will reload the module if it was already imported.  See
-    PyImport_ReloadModule() for the intended way to reload a module.
-
-    If name points to a dotted name of the form package.module, any package
-    structures not already created will still not be created.
-
-    name is removed from sys.modules in error cases."""
-    raise NotImplementedError
-
-@cpython_api([rffi.CCHARP, PyObject, rffi.CCHARP], PyObject)
-def PyImport_ExecCodeModuleEx(space, name, co, pathname):
-    """Like PyImport_ExecCodeModule(), but the __file__ attribute of
-    the module object is set to pathname if it is non-NULL."""
-    raise NotImplementedError
-
 @cpython_api([], lltype.Signed, error=CANNOT_FAIL)
 def PyImport_GetMagicNumber(space):
     """Return the magic number for Python bytecode files (a.k.a. .pyc and
     returns."""
     raise NotImplementedError
 
-@cpython_api([rffi.CCHARP, rffi.CCHARP, rffi.INT_real], PyObject)
-def Py_CompileString(space, str, filename, start):
-    """This is a simplified interface to Py_CompileStringFlags() below, leaving
-    flags set to NULL."""
-    raise NotImplementedError
-
-@cpython_api([rffi.CCHARP, rffi.CCHARP, rffi.INT_real, PyCompilerFlags], PyObject)
-def Py_CompileStringFlags(space, str, filename, start, flags):
-    """Parse and compile the Python source code in str, returning the resulting code
-    object.  The start token is given by start; this can be used to constrain the
-    code which can be compiled and should be Py_eval_input,
-    Py_file_input, or Py_single_input.  The filename specified by
-    filename is used to construct the code object and may appear in tracebacks or
-    SyntaxError exception messages.  This returns NULL if the code cannot
-    be parsed or compiled."""
-    raise NotImplementedError
-
 @cpython_api([PyCodeObject, PyObject, PyObject], PyObject)
 def PyEval_EvalCode(space, co, globals, locals):
     """This is a simplified interface to PyEval_EvalCodeEx(), with just

pypy/module/cpyext/test/test_eval.py

             return args
         assert module.call_func(f) == (None,)
         assert module.call_method("text") == 2
-        
+
+    def test_CompileString_and_Exec(self):
+        module = self.import_extension('foo', [
+            ("compile_string", "METH_NOARGS",
+             """
+                return Py_CompileString(
+                   "f = lambda x: x+5", "someFile", Py_file_input);
+             """),
+            ("exec_code", "METH_O",
+             """
+                return PyImport_ExecCodeModule("cpyext_test_modname", args);
+             """),
+            ("exec_code_ex", "METH_O",
+             """
+                return PyImport_ExecCodeModuleEx("cpyext_test_modname",
+                                                 args, "otherFile");
+             """),
+            ])
+        code = module.compile_string()
+        assert code.co_filename == "someFile"
+        assert code.co_name == "<module>"
+
+        mod = module.exec_code(code)
+        assert mod.__name__ == "cpyext_test_modname"
+        assert mod.__file__ == "someFile"
+        print dir(mod)
+        print mod.__dict__
+        assert mod.f(42) == 47
+
+        mod = module.exec_code_ex(code)
+        assert mod.__name__ == "cpyext_test_modname"
+        assert mod.__file__ == "otherFile"
+        print dir(mod)
+        print mod.__dict__
+        assert mod.f(42) == 47

pypy/module/imp/importing.py

     if pkgdir is not None:
         space.setattr(w_mod, w('__path__'), space.newlist([w(pkgdir)]))
 
+def add_module(space, w_name):
+    w_mod = check_sys_modules(space, w_name)
+    if w_mod is None:
+        w_mod = space.wrap(Module(space, w_name))
+        space.sys.setmodule(w_mod)
+    return w_mod
+
 def load_c_extension(space, filename, modulename):
     # the next line is mandatory to init cpyext
     space.getbuiltinmodule("cpyext")