PEP 343 -- the with-statement.

This was started by Mike Bland and completed by Guido
(with help from Neal).

This still needs a __future__ statement added;
Thomas is working on Michael's patch for that aspect.

There's a small amount of code cleanup and refactoring
in ast.c, compile.c and ceval.c (I fixed the lltrace
behavior when EXT_POP is used -- however I had to make
lltrace a static global).
This commit is contained in:
Guido van Rossum 2006-02-27 22:32:47 +00:00
parent 5fec904f84
commit c2e20744b2
23 changed files with 1853 additions and 816 deletions

View file

@ -84,6 +84,12 @@ char *If_fields[]={
"body",
"orelse",
};
PyTypeObject *With_type;
char *With_fields[]={
"context_expr",
"optional_vars",
"body",
};
PyTypeObject *Raise_type;
char *Raise_fields[]={
"type",
@ -465,6 +471,8 @@ static int init_types(void)
if (!While_type) return 0;
If_type = make_type("If", stmt_type, If_fields, 3);
if (!If_type) return 0;
With_type = make_type("With", stmt_type, With_fields, 3);
if (!With_type) return 0;
Raise_type = make_type("Raise", stmt_type, Raise_fields, 3);
if (!Raise_type) return 0;
TryExcept_type = make_type("TryExcept", stmt_type, TryExcept_fields, 3);
@ -999,6 +1007,29 @@ If(expr_ty test, asdl_seq * body, asdl_seq * orelse, int lineno, PyArena *arena)
return p;
}
stmt_ty
With(expr_ty context_expr, expr_ty optional_vars, asdl_seq * body, int lineno,
PyArena *arena)
{
stmt_ty p;
if (!context_expr) {
PyErr_SetString(PyExc_ValueError,
"field context_expr is required for With");
return NULL;
}
p = (stmt_ty)PyArena_Malloc(arena, sizeof(*p));
if (!p) {
PyErr_NoMemory();
return NULL;
}
p->kind = With_kind;
p->v.With.context_expr = context_expr;
p->v.With.optional_vars = optional_vars;
p->v.With.body = body;
p->lineno = lineno;
return p;
}
stmt_ty
Raise(expr_ty type, expr_ty inst, expr_ty tback, int lineno, PyArena *arena)
{
@ -2062,6 +2093,26 @@ ast2obj_stmt(void* _o)
goto failed;
Py_DECREF(value);
break;
case With_kind:
result = PyType_GenericNew(With_type, NULL, NULL);
if (!result) goto failed;
value = ast2obj_expr(o->v.With.context_expr);
if (!value) goto failed;
if (PyObject_SetAttrString(result, "context_expr", value) == -1)
goto failed;
Py_DECREF(value);
value = ast2obj_expr(o->v.With.optional_vars);
if (!value) goto failed;
if (PyObject_SetAttrString(result, "optional_vars", value) ==
-1)
goto failed;
Py_DECREF(value);
value = ast2obj_list(o->v.With.body, ast2obj_stmt);
if (!value) goto failed;
if (PyObject_SetAttrString(result, "body", value) == -1)
goto failed;
Py_DECREF(value);
break;
case Raise_kind:
result = PyType_GenericNew(Raise_type, NULL, NULL);
if (!result) goto failed;
@ -2922,6 +2973,7 @@ init_ast(void)
if(PyDict_SetItemString(d, "For", (PyObject*)For_type) < 0) return;
if(PyDict_SetItemString(d, "While", (PyObject*)While_type) < 0) return;
if(PyDict_SetItemString(d, "If", (PyObject*)If_type) < 0) return;
if(PyDict_SetItemString(d, "With", (PyObject*)With_type) < 0) return;
if(PyDict_SetItemString(d, "Raise", (PyObject*)Raise_type) < 0) return;
if(PyDict_SetItemString(d, "TryExcept", (PyObject*)TryExcept_type) < 0)
return;

View file

@ -314,7 +314,7 @@ get_operator(const node *n)
}
}
/* Set the context ctx for expr_ty e returning 0 on success, -1 on error.
/* Set the context ctx for expr_ty e returning 1 on success, 0 on error.
Only sets context for expr kinds that "can appear in assignment context"
(according to ../Parser/Python.asdl). For other expr kinds, it sets
@ -339,7 +339,7 @@ set_context(expr_ty e, expr_context_ty ctx, const node *n)
a little more complex than necessary as a result. It also means
that expressions in an augmented assignment have no context.
Consider restructuring so that augmented assignment uses
set_context(), too
set_context(), too.
*/
assert(ctx != AugStore && ctx != AugLoad);
@ -2713,6 +2713,46 @@ ast_for_try_stmt(struct compiling *c, const node *n)
return TryFinally(body, finally, LINENO(n), c->c_arena);
}
static expr_ty
ast_for_with_var(struct compiling *c, const node *n)
{
REQ(n, with_var);
if (strcmp(STR(CHILD(n, 0)), "as") != 0) {
ast_error(n, "expected \"with [expr] as [var]\"");
return NULL;
}
return ast_for_expr(c, CHILD(n, 1));
}
/* with_stmt: 'with' test [ with_var ] ':' suite */
static stmt_ty
ast_for_with_stmt(struct compiling *c, const node *n)
{
expr_ty context_expr, optional_vars = NULL;
int suite_index = 3; /* skip 'with', test, and ':' */
asdl_seq *suite_seq;
assert(TYPE(n) == with_stmt);
context_expr = ast_for_expr(c, CHILD(n, 1));
if (TYPE(CHILD(n, 2)) == with_var) {
optional_vars = ast_for_with_var(c, CHILD(n, 2));
if (!optional_vars) {
return NULL;
}
if (!set_context(optional_vars, Store, n)) {
return NULL;
}
suite_index = 4;
}
suite_seq = ast_for_suite(c, CHILD(n, suite_index));
if (!suite_seq) {
return NULL;
}
return With(context_expr, optional_vars, suite_seq, LINENO(n), c->c_arena);
}
static stmt_ty
ast_for_classdef(struct compiling *c, const node *n)
{
@ -2813,6 +2853,8 @@ ast_for_stmt(struct compiling *c, const node *n)
return ast_for_for_stmt(c, ch);
case try_stmt:
return ast_for_try_stmt(c, ch);
case with_stmt:
return ast_for_with_stmt(c, ch);
case funcdef:
return ast_for_funcdef(c, ch);
case classdef:

View file

@ -97,6 +97,7 @@ static PyObject *load_args(PyObject ***, int);
#define CALL_FLAG_KW 2
#ifdef LLTRACE
static int lltrace;
static int prtrace(PyObject *, char *);
#endif
static int call_trace(Py_tracefunc, PyObject *, PyFrameObject *,
@ -540,9 +541,6 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throw)
unsigned char *first_instr;
PyObject *names;
PyObject *consts;
#ifdef LLTRACE
int lltrace;
#endif
#if defined(Py_DEBUG) || defined(LLTRACE)
/* Make it easier to find out where we are with a debugger */
char *filename;
@ -661,10 +659,12 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throw)
#define STACKADJ(n) { (void)(BASIC_STACKADJ(n), \
lltrace && prtrace(TOP(), "stackadj")); \
assert(STACK_LEVEL() <= f->f_stacksize); }
#define EXT_POP(STACK_POINTER) (lltrace && prtrace(*(STACK_POINTER), "ext_pop"), *--(STACK_POINTER))
#else
#define PUSH(v) BASIC_PUSH(v)
#define POP() BASIC_POP()
#define STACKADJ(n) BASIC_STACKADJ(n)
#define EXT_POP(STACK_POINTER) (*--(STACK_POINTER))
#endif
/* Local variable macros */
@ -2172,6 +2172,43 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throw)
STACK_LEVEL());
continue;
case WITH_CLEANUP:
{
/* TOP is the context.__exit__ bound method.
Below that are 1-3 values indicating how/why
we entered the finally clause:
- SECOND = None
- (SECOND, THIRD) = (WHY_RETURN or WHY_CONTINUE), retval
- SECOND = WHY_*; no retval below it
- (SECOND, THIRD, FOURTH) = exc_info()
In the last case, we must call
TOP(SECOND, THIRD, FOURTH)
otherwise we must call
TOP(None, None, None)
but we must preserve the stack entries below TOP.
The code here just sets the stack up for the call;
separate CALL_FUNCTION(3) and POP_TOP opcodes are
emitted by the compiler.
*/
x = TOP();
u = SECOND();
if (PyInt_Check(u) || u == Py_None) {
u = v = w = Py_None;
}
else {
v = THIRD();
w = FOURTH();
}
Py_INCREF(u);
Py_INCREF(v);
Py_INCREF(w);
PUSH(u);
PUSH(v);
PUSH(w);
break;
}
case CALL_FUNCTION:
{
PyObject **sp;
@ -2511,9 +2548,9 @@ fast_yield:
return retval;
}
/* this is gonna seem *real weird*, but if you put some other code between
/* This is gonna seem *real weird*, but if you put some other code between
PyEval_EvalFrame() and PyEval_EvalCodeEx() you will need to adjust
the test in the if statement in Misc/gdbinit:pystack* */
the test in the if statements in Misc/gdbinit (pystack and pystackv). */
PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
@ -3473,8 +3510,6 @@ PyEval_GetFuncDesc(PyObject *func)
}
}
#define EXT_POP(STACK_POINTER) (*--(STACK_POINTER))
static void
err_args(PyObject *func, int flags, int nargs)
{

View file

@ -191,6 +191,8 @@ static void compiler_pop_fblock(struct compiler *, enum fblocktype,
static int inplace_binop(struct compiler *, operator_ty);
static int expr_constant(expr_ty e);
static int compiler_with(struct compiler *, stmt_ty);
static PyCodeObject *assemble(struct compiler *, int addNone);
static PyObject *__doc__;
@ -289,6 +291,7 @@ PyAST_Compile(mod_ty mod, const char *filename, PyCompilerFlags *flags,
error:
compiler_free(&c);
assert(!PyErr_Occurred());
return co;
}
@ -1157,6 +1160,18 @@ compiler_exit_scope(struct compiler *c)
}
/* Allocate a new "anonymous" local variable.
Used by list comprehensions and with statements.
*/
static PyObject *
compiler_new_tmpname(struct compiler *c)
{
char tmpname[256];
PyOS_snprintf(tmpname, sizeof(tmpname), "_[%d]", ++c->u->u_tmpname);
return PyString_FromString(tmpname);
}
/* Allocate a new block and return a pointer to it.
Returns NULL on error.
*/
@ -1360,7 +1375,8 @@ opcode_stack_effect(int opcode, int oparg)
return -1;
case BREAK_LOOP:
return 0;
case WITH_CLEANUP:
return 3;
case LOAD_LOCALS:
return 1;
case RETURN_VALUE:
@ -2663,6 +2679,8 @@ compiler_visit_stmt(struct compiler *c, stmt_ty s)
break;
case Continue_kind:
return compiler_continue(c);
case With_kind:
return compiler_with(c, s);
}
return 1;
}
@ -3124,7 +3142,6 @@ compiler_listcomp_generator(struct compiler *c, PyObject *tmpname,
static int
compiler_listcomp(struct compiler *c, expr_ty e)
{
char tmpname[256];
identifier tmp;
int rc = 0;
static identifier append;
@ -3136,8 +3153,7 @@ compiler_listcomp(struct compiler *c, expr_ty e)
if (!append)
return 0;
}
PyOS_snprintf(tmpname, sizeof(tmpname), "_[%d]", ++c->u->u_tmpname);
tmp = PyString_FromString(tmpname);
tmp = compiler_new_tmpname(c);
if (!tmp)
return 0;
ADDOP_I(c, BUILD_LIST, 0);
@ -3291,6 +3307,148 @@ expr_constant(expr_ty e)
}
}
/*
Implements the with statement from PEP 343.
The semantics outlined in that PEP are as follows:
with EXPR as VAR:
BLOCK
It is implemented roughly as:
context = (EXPR).__context__()
exit = context.__exit__ # not calling it
value = context.__enter__()
try:
VAR = value # if VAR present in the syntax
BLOCK
finally:
if an exception was raised:
exc = copy of (exception, instance, traceback)
else:
exc = (None, None, None)
exit(*exc)
*/
static int
compiler_with(struct compiler *c, stmt_ty s)
{
static identifier context_attr, enter_attr, exit_attr;
basicblock *block, *finally;
identifier tmpexit, tmpvalue = NULL;
assert(s->kind == With_kind);
if (!context_attr) {
context_attr = PyString_InternFromString("__context__");
if (!context_attr)
return 0;
}
if (!enter_attr) {
enter_attr = PyString_InternFromString("__enter__");
if (!enter_attr)
return 0;
}
if (!exit_attr) {
exit_attr = PyString_InternFromString("__exit__");
if (!exit_attr)
return 0;
}
block = compiler_new_block(c);
finally = compiler_new_block(c);
if (!block || !finally)
return 0;
/* Create a temporary variable to hold context.__exit__ */
tmpexit = compiler_new_tmpname(c);
if (tmpexit == NULL)
return 0;
PyArena_AddPyObject(c->c_arena, tmpexit);
if (s->v.With.optional_vars) {
/* Create a temporary variable to hold context.__enter__().
We need to do this rather than preserving it on the stack
because SETUP_FINALLY remembers the stack level.
We need to do the assignment *inside* the try/finally
so that context.__exit__() is called when the assignment
fails. But we need to call context.__enter__() *before*
the try/finally so that if it fails we won't call
context.__exit__().
*/
tmpvalue = compiler_new_tmpname(c);
if (tmpvalue == NULL)
return 0;
PyArena_AddPyObject(c->c_arena, tmpvalue);
}
/* Evaluate (EXPR).__context__() */
VISIT(c, expr, s->v.With.context_expr);
ADDOP_O(c, LOAD_ATTR, context_attr, names);
ADDOP_I(c, CALL_FUNCTION, 0);
/* Squirrel away context.__exit__ */
ADDOP(c, DUP_TOP);
ADDOP_O(c, LOAD_ATTR, exit_attr, names);
if (!compiler_nameop(c, tmpexit, Store))
return 0;
/* Call context.__enter__() */
ADDOP_O(c, LOAD_ATTR, enter_attr, names);
ADDOP_I(c, CALL_FUNCTION, 0);
if (s->v.With.optional_vars) {
/* Store it in tmpvalue */
if (!compiler_nameop(c, tmpvalue, Store))
return 0;
}
else {
/* Discard result from context.__enter__() */
ADDOP(c, POP_TOP);
}
/* Start the try block */
ADDOP_JREL(c, SETUP_FINALLY, finally);
compiler_use_next_block(c, block);
if (!compiler_push_fblock(c, FINALLY_TRY, block)) {
return 0;
}
if (s->v.With.optional_vars) {
/* Bind saved result of context.__enter__() to VAR */
if (!compiler_nameop(c, tmpvalue, Load) ||
!compiler_nameop(c, tmpvalue, Del))
return 0;
VISIT(c, expr, s->v.With.optional_vars);
}
/* BLOCK code */
VISIT_SEQ(c, stmt, s->v.With.body);
/* End of try block; start the finally block */
ADDOP(c, POP_BLOCK);
compiler_pop_fblock(c, FINALLY_TRY, block);
ADDOP_O(c, LOAD_CONST, Py_None, consts);
compiler_use_next_block(c, finally);
if (!compiler_push_fblock(c, FINALLY_END, finally))
return 0;
/* Finally block starts; push tmpexit and issue our magic opcode. */
if (!compiler_nameop(c, tmpexit, Load) ||
!compiler_nameop(c, tmpexit, Del))
return 0;
ADDOP(c, WITH_CLEANUP);
ADDOP_I(c, CALL_FUNCTION, 3);
ADDOP(c, POP_TOP);
/* Finally block ends. */
ADDOP(c, END_FINALLY);
compiler_pop_fblock(c, FINALLY_END, finally);
return 1;
}
static int
compiler_visit_expr(struct compiler *c, expr_ty e)
{

File diff suppressed because it is too large Load diff

View file

@ -54,9 +54,10 @@ extern time_t PyOS_GetLastModificationTime(char *, FILE *);
Python 2.4b1: 62061
Python 2.5a0: 62071
Python 2.5a0: 62081 (ast-branch)
Python 2.5a0: 62091 (with)
.
*/
#define MAGIC (62081 | ((long)'\r'<<16) | ((long)'\n'<<24))
#define MAGIC (62091 | ((long)'\r'<<16) | ((long)'\n'<<24))
/* Magic word as global; note that _PyImport_Init() can change the
value of this global to accommodate for alterations of how the

View file

@ -890,6 +890,21 @@ error:
} \
}
static int
symtable_new_tmpname(struct symtable *st)
{
char tmpname[256];
identifier tmp;
PyOS_snprintf(tmpname, sizeof(tmpname), "_[%d]",
++st->st_cur->ste_tmpname);
tmp = PyString_InternFromString(tmpname);
if (!symtable_add_def(st, tmp, DEF_LOCAL))
return 0;
Py_DECREF(tmp);
return 1;
}
static int
symtable_visit_stmt(struct symtable *st, stmt_ty s)
{
@ -1051,6 +1066,17 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
case Continue_kind:
/* nothing to do here */
break;
case With_kind:
if (!symtable_new_tmpname(st))
return 0;
VISIT(st, expr, s->v.With.context_expr);
if (s->v.With.optional_vars) {
if (!symtable_new_tmpname(st))
return 0;
VISIT(st, expr, s->v.With.optional_vars);
}
VISIT_SEQ(st, stmt, s->v.With.body);
break;
}
return 1;
}
@ -1093,26 +1119,16 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
VISIT_SEQ(st, expr, e->v.Dict.keys);
VISIT_SEQ(st, expr, e->v.Dict.values);
break;
case ListComp_kind: {
char tmpname[256];
identifier tmp;
PyOS_snprintf(tmpname, sizeof(tmpname), "_[%d]",
++st->st_cur->ste_tmpname);
tmp = PyString_InternFromString(tmpname);
if (!symtable_add_def(st, tmp, DEF_LOCAL))
case ListComp_kind:
if (!symtable_new_tmpname(st))
return 0;
Py_DECREF(tmp);
VISIT(st, expr, e->v.ListComp.elt);
VISIT_SEQ(st, comprehension, e->v.ListComp.generators);
break;
}
case GeneratorExp_kind: {
if (!symtable_visit_genexp(st, e)) {
case GeneratorExp_kind:
if (!symtable_visit_genexp(st, e))
return 0;
}
break;
}
case Yield_kind:
if (e->v.Yield.value)
VISIT(st, expr, e->v.Yield.value);