diff --git a/Include/opcode.h b/Include/opcode.h --- a/Include/opcode.h +++ b/Include/opcode.h @@ -122,6 +122,7 @@ #define BUILD_TUPLE_UNPACK 152 #define BUILD_SET_UNPACK 153 #define SETUP_ASYNC_WITH 154 +#define FORMAT_VALUE 155 /* EXCEPT_HANDLER is a special, implicit block type which is created when entering an except handler. It is not an opcode but we define it here diff --git a/Lib/opcode.py b/Lib/opcode.py --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -214,4 +214,6 @@ def_op('BUILD_TUPLE_UNPACK', 152) def_op('BUILD_SET_UNPACK', 153) +def_op('FORMAT_VALUE', 155) + del def_op, name_op, jrel_op, jabs_op diff --git a/Python/ceval.c b/Python/ceval.c --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3363,6 +3363,67 @@ DISPATCH(); } + TARGET(FORMAT_VALUE) { + /* Handles f-string value formatting. */ + PyObject *result; + PyObject *fmt_spec; + PyObject *value; + PyObject *(*conv_fn)(PyObject *) = NULL; + + value = POP(); + fmt_spec = (oparg & 0x4) ? POP() : NULL; + + /* See if any conversion is specified. */ + switch (oparg & 0x3) { + case 0x0: + /* No conversion. */ + break; + case 0x1: + /* str(value). Skip this if we're already a string. */ + if (!PyUnicode_CheckExact(value)) + conv_fn = PyObject_Str; + break; + case 0x2: + /* repr(value) */ + conv_fn = PyObject_Repr; + break; + case 0x3: + /* ascii(value) */ + conv_fn = PyObject_ASCII; + break; + /* No need for a default, all possible values have a case. */ + } + + /* If there's a conversion function, call it. */ + if (conv_fn) { + result = conv_fn(value); + Py_DECREF(value); + if (!result) { + Py_XDECREF(fmt_spec); + goto error; + } + value = result; + } + + /* If the value is a unicode object, and there's no fmt_spec, then + we know the result of format() is the string itself. Skip the + format step. */ + if (PyUnicode_CheckExact(value) && fmt_spec == NULL) { + /* Do nothing, just transfer ownership. */ + result = value; + } else { + /* Actually call format(). */ + result = PyObject_Format(value, fmt_spec); + Py_DECREF(value); + Py_XDECREF(fmt_spec); + if (!result) + goto error; + } + + PUSH(result); + DISPATCH(); + } + TARGET(EXTENDED_ARG) { opcode = NEXTOP(); oparg = oparg<<16 | NEXTARG(); diff --git a/Python/compile.c b/Python/compile.c --- a/Python/compile.c +++ b/Python/compile.c @@ -1067,6 +1067,8 @@ return 1; case GET_YIELD_FROM_ITER: return 0; + case FORMAT_VALUE: + return (oparg & 0x04) ? -1 : 0; default: return PY_INVALID_STACK_EFFECT; } @@ -3247,77 +3249,53 @@ static int compiler_formatted_value(struct compiler *c, expr_ty e) { - PyObject *conversion_name = NULL; - - static PyObject *format_string; - static PyObject *str_string; - static PyObject *repr_string; - static PyObject *ascii_string; - - if (!format_string) { - format_string = PyUnicode_InternFromString("format"); - if (!format_string) - return 0; + /* Our opcode arg encodes 2 pieces of information: the conversion + character, and whether or not a format_spec was provided. + + Convert the conversion char to 2 bits: + None: 000 0x0 + !s : 001 0x1 + !r : 010 0x2 + !a : 011 0x3 + + next bit is whether or not we have a format spec: + yes : 100 0x4 + no : 000 0x0 + */ + + int arg; + switch (e->v.FormattedValue.conversion) { + case 's': + arg = 0x1; + break; + case 'r': + arg = 0x2; + break; + case 'a': + arg = 0x3; + break; + case -1: + arg = 0x0; + break; + default: + PyErr_SetString(PyExc_SystemError, + "Unrecognized conversion character"); + return 0; } - if (!str_string) { - str_string = PyUnicode_InternFromString("str"); - if (!str_string) - return 0; + if (e->v.FormattedValue.format_spec) { + /* Evaluate the format spec, and update our opcode arg. */ + VISIT(c, expr, e->v.FormattedValue.format_spec); + arg |= 0x4; + } else { + /*No format spec specified, no need to update arg. */ } - if (!repr_string) { - repr_string = PyUnicode_InternFromString("repr"); - if (!repr_string) - return 0; - } - if (!ascii_string) { - ascii_string = PyUnicode_InternFromString("ascii"); - if (!ascii_string) - return 0; - } - - ADDOP_NAME(c, LOAD_GLOBAL, format_string, names); - - /* If needed, convert via str, repr, or ascii. */ - if (e->v.FormattedValue.conversion != -1) { - switch (e->v.FormattedValue.conversion) { - case 's': - conversion_name = str_string; - break; - case 'r': - conversion_name = repr_string; - break; - case 'a': - conversion_name = ascii_string; - break; - default: - PyErr_SetString(PyExc_SystemError, - "Unrecognized conversion character"); - return 0; - } - ADDOP_NAME(c, LOAD_GLOBAL, conversion_name, names); - } - - /* Evaluate the value. */ + /* Evaluate the expression to be formatted . */ VISIT(c, expr, e->v.FormattedValue.value); - /* If needed, convert via str, repr, or ascii. */ - if (conversion_name) { - /* Call the function we previously pushed. */ - ADDOP_I(c, CALL_FUNCTION, 1); - } - - /* If we have a format spec, use format(value, format_spec). Otherwise, - use the single argument form. */ - if (e->v.FormattedValue.format_spec) { - VISIT(c, expr, e->v.FormattedValue.format_spec); - ADDOP_I(c, CALL_FUNCTION, 2); - } else { - /* No format spec specified, call format(value). */ - ADDOP_I(c, CALL_FUNCTION, 1); - } - + /* And push our opcode and arg */ + ADDOP_I(c, FORMAT_VALUE, arg); return 1; } diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -154,7 +154,7 @@ &&TARGET_BUILD_TUPLE_UNPACK, &&TARGET_BUILD_SET_UNPACK, &&TARGET_SETUP_ASYNC_WITH, - &&_unknown_opcode, + &&TARGET_FORMAT_VALUE, &&_unknown_opcode, &&_unknown_opcode, &&_unknown_opcode,