partial implementation of the "exec" statement
authorStefan Behnel <scoder@users.berlios.de>
Tue, 11 Nov 2008 21:10:45 +0000 (22:10 +0100)
committerStefan Behnel <scoder@users.berlios.de>
Tue, 11 Nov 2008 21:10:45 +0000 (22:10 +0100)
- only supports code in strings, not files (should be easy to add)
- only the "exec XYZ in GLOBALS [,LOCALS]" form is supported, not the execution in the current namespace (would require a mapping representation of it)
- also includes an incomplete (3-args only) and untested implementation for the exec() function in Py3

Cython/Compiler/Builtin.py
Cython/Compiler/Nodes.py
Cython/Compiler/Parsing.py
tests/run/exectest.pyx [new file with mode: 0644]

index 6605e48677f714b90563ab7bb9981ec4179aecef..6b71a03d430578a50f0d5aae0be3bb2a8f642e29 100644 (file)
@@ -6,6 +6,7 @@ from Symtab import BuiltinScope, StructOrUnionScope
 from Cython.Utils import UtilityCode
 from TypeSlots import Signature
 import PyrexTypes
+import Naming
 
 builtin_function_table = [
     # name,        args,   return,  C API func,           py equiv = "*"
@@ -16,6 +17,7 @@ builtin_function_table = [
     ('delattr',    "OO",   "r",     "PyObject_DelAttr"),
     ('dir',        "O",    "O",     "PyObject_Dir"),
     ('divmod',     "OO",   "O",     "PyNumber_Divmod"),
+    ('exec',       "OOO",  "O",     "__Pyx_PyRun"),
     #('eval',      "",     "",      ""),
     #('execfile',  "",     "",      ""),
     #('filter',    "",     "",      ""),
@@ -154,6 +156,48 @@ bad:
 }
 """)
 
+pyexec_utility_code = UtilityCode(
+proto = """
+static PyObject* __Pyx_PyRun(PyObject*, PyObject*, PyObject*);
+""",
+impl = """
+static PyObject* __Pyx_PyRun(PyObject* o, PyObject* globals, PyObject* locals) {
+    PyObject* result;
+    PyObject* s = 0;
+
+    if (!locals && !globals) {
+        globals = PyModule_GetDict(%s);""" % Naming.module_cname + """
+        if (!globals)
+            goto bad;
+        locals = globals;
+    } else if (!locals) {
+        locals = globals;
+    } else if (!globals) {
+        globals = locals;
+    }
+
+    if (PyUnicode_Check(o)) {
+        s = PyUnicode_AsUTF8String(o);
+        if (!s) goto bad;
+        o = s;
+    } else if (!PyString_Check(o)) {
+        /* FIXME: support file objects and code objects */
+        PyErr_SetString(PyExc_TypeError,
+            "exec currently requires a string as code input.");
+        goto bad;
+    }
+
+    result = PyRun_String(
+        PyString_AS_STRING(o), Py_file_input, globals, locals);
+
+    Py_XDECREF(s);
+    return result;
+bad:
+    Py_XDECREF(s);
+    return 0;
+}
+""")
+
 intern_utility_code = UtilityCode(
 proto = """
 #if PY_MAJOR_VERSION >= 3
@@ -273,6 +317,7 @@ Py_XDECREF(__Pyx_PyFrozenSet_Type); __Pyx_PyFrozenSet_Type = NULL;
 """)
 
 builtin_utility_code = {
+    'exec'      : pyexec_utility_code,
     'getattr3'  : getattr3_utility_code,
     'intern'    : intern_utility_code,
     'set'       : py23_set_utility_code,
index 10700cd2ed1cfb8b7fb807cfcfffcedab41e507e..9a7c6dbda2044341ab9d3a6e4c3c4a749fd56a72 100644 (file)
@@ -5,6 +5,7 @@
 import string, sys, os, time, copy
 
 import Code
+import Builtin
 from Errors import error, warning, InternalError
 import Naming
 import PyrexTypes
@@ -3056,6 +3057,45 @@ class PrintStatNode(StatNode):
         self.arg_tuple.annotate(code)
 
 
+class ExecStatNode(StatNode):
+    #  exec statement
+    #
+    #  args     [ExprNode]
+
+    child_attrs = ["args"]
+
+    def analyse_expressions(self, env):
+        for i, arg in enumerate(self.args):
+            arg.analyse_expressions(env)
+            arg = arg.coerce_to_pyobject(env)
+            arg.release_temp(env)
+            self.args[i] = arg
+        self.temp_result = env.allocate_temp_pyobject()
+        env.release_temp(self.temp_result)
+        env.use_utility_code(Builtin.pyexec_utility_code)
+        self.gil_check(env)
+
+    gil_message = "Python exec statement"
+
+    def generate_execution_code(self, code):
+        args = []
+        for arg in self.args:
+            arg.generate_evaluation_code(code)
+            args.append( arg.py_result() )
+        args = tuple(args + ['0', '0'][:3-len(args)])
+        code.putln("%s = __Pyx_PyRun(%s, %s, %s);" % (
+                (self.temp_result,) + args))
+        for arg in self.args:
+            arg.generate_disposal_code(code)
+        code.putln(
+            code.error_goto_if_null(self.temp_result, self.pos))
+        code.put_decref_clear(self.temp_result, py_object_type)
+
+    def annotate(self, code):
+        for arg in self.args:
+            arg.annotate(code)
+
+
 class DelStatNode(StatNode):
     #  del statement
     #
index 45fa1015474e208a60c20198a947e2e9dbe32ff2..7d1bfa915071256342750d2eb8f887aff8ff481a 100644 (file)
@@ -902,6 +902,21 @@ def p_print_statement(s):
     return Nodes.PrintStatNode(pos,
         arg_tuple = arg_tuple, append_newline = not ends_with_comma)
 
+def p_exec_statement(s):
+    # s.sy == 'exec'
+    pos = s.position()
+    s.next()
+    args = [ p_bit_expr(s) ]
+    if s.sy == 'in':
+        s.next()
+        args.append(p_simple_expr(s))
+        if s.sy == ',':
+            s.next()
+            args.append(p_simple_expr(s))
+    else:
+        error(pos, "'exec' currently requires a target mapping (globals/locals)")
+    return Nodes.ExecStatNode(pos, args = args)
+
 def p_del_statement(s):
     # s.sy == 'del'
     pos = s.position()
@@ -1316,6 +1331,8 @@ def p_simple_statement(s, first_statement = 0):
         node = p_global_statement(s)
     elif s.sy == 'print':
         node = p_print_statement(s)
+    elif s.sy == 'exec':
+        node = p_exec_statement(s)
     elif s.sy == 'del':
         node = p_del_statement(s)
     elif s.sy == 'break':
diff --git a/tests/run/exectest.pyx b/tests/run/exectest.pyx
new file mode 100644 (file)
index 0000000..8e607d9
--- /dev/null
@@ -0,0 +1,57 @@
+__doc__ = """# no unicode string, not tested in Python3!
+#>>> a
+#>>> test_module_scope()
+#>>> a
+
+>>> test_dict_scope1()
+2
+
+>>> d = {}
+>>> test_dict_scope2(d)
+>>> print d['b']
+2
+
+>>> d1 = {}
+>>> test_dict_scope3(d1, d1)
+>>> print d1['b']
+2
+
+>>> d1, d2 = {}, {}
+>>> test_dict_scope3(d1, d2)
+>>> print d1.get('b'), d2.get('b')
+None 2
+
+>>> d1, d2 = {}, {}
+>>> test_dict_scope3(d1, d2)
+>>> print d1.get('b'), d2.get('b')
+None 2
+
+>>> d1, d2 = dict(a=11), dict(c=5)
+>>> test_dict_scope_ref(d1, d2)
+>>> print d1.get('b'), d2.get('b')
+None 16
+
+>>> d = dict(a=11, c=5)
+>>> test_dict_scope_ref(d, d)
+>>> print d['b']
+16
+"""
+
+#def test_module_scope():
+#    global a
+#    exec "a=1+1"
+#    return __dict__['a']
+
+def test_dict_scope1():
+    cdef dict d = {}
+    exec "b=1+1" in d
+    return d['b']
+
+def test_dict_scope2(d):
+    exec "b=1+1" in d
+
+def test_dict_scope3(d1, d2):
+    exec "b=1+1" in d1, d2
+
+def test_dict_scope_ref(d1, d2):
+    exec "b=a+c" in d1, d2