From b8a099a0e03d0110367739133b1dfccdcef47a55 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 11 Nov 2008 22:10:45 +0100 Subject: [PATCH] partial implementation of the "exec" statement - 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 | 45 ++++++++++++++++++++++++++++++ Cython/Compiler/Nodes.py | 40 ++++++++++++++++++++++++++ Cython/Compiler/Parsing.py | 17 ++++++++++++ tests/run/exectest.pyx | 57 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 tests/run/exectest.pyx diff --git a/Cython/Compiler/Builtin.py b/Cython/Compiler/Builtin.py index 6605e486..6b71a03d 100644 --- a/Cython/Compiler/Builtin.py +++ b/Cython/Compiler/Builtin.py @@ -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, diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 10700cd2..9a7c6dbd 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -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 # diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py index 45fa1015..7d1bfa91 100644 --- a/Cython/Compiler/Parsing.py +++ b/Cython/Compiler/Parsing.py @@ -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 index 00000000..8e607d9b --- /dev/null +++ b/tests/run/exectest.pyx @@ -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 -- 2.26.2