diff --git a/Grammar/python.gram b/Grammar/python.gram index 15c40b6bbbacdc..3734a271119ddc 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -796,6 +796,7 @@ slice[expr_ty]: | a=named_expression { a } atom[expr_ty]: + | a=NAME b=STRING { _PyPegen_tag_string(p, a, (Token *)b) } | NAME | 'True' { _PyAST_Constant(Py_True, NULL, EXTRA) } | 'False' { _PyAST_Constant(Py_False, NULL, EXTRA) } @@ -870,7 +871,7 @@ lambda_param[arg_ty]: a=NAME { _PyAST_arg(a->v.Name.id, NULL, NULL, EXTRA) } # LITERALS # ======== -strings[expr_ty] (memo): a=STRING+ { _PyPegen_concatenate_strings(p, a) } +strings[expr_ty] (memo): a=STRING+ { _PyPegen_concatenate_strings(p, a, 0) } list[expr_ty]: | '[' a=[star_named_expressions] ']' { _PyAST_List(a, Load, EXTRA) } diff --git a/Include/internal/pycore_ast.h b/Include/internal/pycore_ast.h index 36277efe9c5ca5..c36fbc1f13b2a4 100644 --- a/Include/internal/pycore_ast.h +++ b/Include/internal/pycore_ast.h @@ -341,9 +341,10 @@ enum _expr_kind {BoolOp_kind=1, NamedExpr_kind=2, BinOp_kind=3, UnaryOp_kind=4, ListComp_kind=9, SetComp_kind=10, DictComp_kind=11, GeneratorExp_kind=12, Await_kind=13, Yield_kind=14, YieldFrom_kind=15, Compare_kind=16, Call_kind=17, - FormattedValue_kind=18, JoinedStr_kind=19, Constant_kind=20, - Attribute_kind=21, Subscript_kind=22, Starred_kind=23, - Name_kind=24, List_kind=25, Tuple_kind=26, Slice_kind=27}; + FormattedValue_kind=18, JoinedStr_kind=19, TagString_kind=20, + Constant_kind=21, Attribute_kind=22, Subscript_kind=23, + Starred_kind=24, Name_kind=25, List_kind=26, Tuple_kind=27, + Slice_kind=28}; struct _expr { enum _expr_kind kind; union { @@ -443,6 +444,11 @@ struct _expr { asdl_expr_seq *values; } JoinedStr; + struct { + expr_ty tag; + expr_ty str; + } TagString; + struct { constant value; string kind; @@ -771,6 +777,8 @@ expr_ty _PyAST_FormattedValue(expr_ty value, int conversion, expr_ty end_lineno, int end_col_offset, PyArena *arena); expr_ty _PyAST_JoinedStr(asdl_expr_seq * values, int lineno, int col_offset, int end_lineno, int end_col_offset, PyArena *arena); +expr_ty _PyAST_TagString(expr_ty tag, expr_ty str, int lineno, int col_offset, + int end_lineno, int end_col_offset, PyArena *arena); expr_ty _PyAST_Constant(constant value, string kind, int lineno, int col_offset, int end_lineno, int end_col_offset, PyArena *arena); diff --git a/Include/internal/pycore_ast_state.h b/Include/internal/pycore_ast_state.h index da78bba3b69bdf..41954346e607da 100644 --- a/Include/internal/pycore_ast_state.h +++ b/Include/internal/pycore_ast_state.h @@ -132,6 +132,7 @@ struct ast_state { PyObject *Sub_singleton; PyObject *Sub_type; PyObject *Subscript_type; + PyObject *TagString_type; PyObject *TryStar_type; PyObject *Try_type; PyObject *Tuple_type; @@ -232,6 +233,7 @@ struct ast_state { PyObject *slice; PyObject *step; PyObject *stmt_type; + PyObject *str; PyObject *subject; PyObject *tag; PyObject *target; diff --git a/Parser/Python.asdl b/Parser/Python.asdl index e9423a7c984f21..9136144cc090d4 100644 --- a/Parser/Python.asdl +++ b/Parser/Python.asdl @@ -77,6 +77,7 @@ module Python | Call(expr func, expr* args, keyword* keywords) | FormattedValue(expr value, int conversion, expr? format_spec) | JoinedStr(expr* values) + | TagString(expr tag, expr str) | Constant(constant value, string? kind) -- the following expression can appear in assignment context diff --git a/Parser/action_helpers.c b/Parser/action_helpers.c index d1be679aff2e7b..71524e39555916 100644 --- a/Parser/action_helpers.c +++ b/Parser/action_helpers.c @@ -869,8 +869,106 @@ _PyPegen_seq_delete_starred_exprs(Parser *p, asdl_seq *kwargs) return new_seq; } +static expr_ty +lambdafy(Parser *p, expr_ty arg) +{ + arguments_ty args = _PyPegen_empty_arguments(p); + if (args == NULL) + return NULL; + return _PyAST_Lambda(args, arg, + arg->lineno, arg->col_offset, arg->end_lineno, arg->end_col_offset, + p->arena); +} + +expr_ty +_PyPegen_tag_string(Parser *p, expr_ty tag, Token *tok) +{ + if (tag->end_lineno != tok->lineno || + tag->end_col_offset != tok->col_offset) { + RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, + tag->end_lineno, tag->end_col_offset, + tok->lineno, tok->col_offset, + "cannot have space between tag and string"); + } + asdl_generic_seq *tokens = _Py_asdl_generic_seq_new(1, p->arena); + if (tokens == NULL) + return NULL; + asdl_seq_SET(tokens, 0, tok); + expr_ty str = _PyPegen_concatenate_strings(p, (asdl_seq *)tokens, 1); + if (str == NULL) + return NULL; + if (str->kind == JoinedStr_kind) { + // Transform FormattedValue items into thunks (for now, tuples) + asdl_expr_seq *values = str->v.JoinedStr.values; + int nvalues = asdl_seq_LEN(values); + expr_ty none = NULL; + for (int i = 0; i < nvalues; i++) { + expr_ty value = asdl_seq_GET(values, i); + if (value->kind == FormattedValue_kind) { + if (none == NULL) { + none = _PyAST_Constant(Py_None, NULL, + str->lineno, str->col_offset, + str->end_lineno, str->end_col_offset, + p->arena); + if (none == NULL) + return NULL; + } + expr_ty expr = value->v.FormattedValue.value; + expr_ty lambda = lambdafy(p, expr); + if (lambda == NULL) + return NULL; + constant rawstr = _PyAST_ExprAsUnicode(expr); + if (rawstr == NULL) + return NULL; + expr_ty raw = _PyAST_Constant(rawstr, NULL, + expr->lineno, expr->col_offset, + expr->end_lineno, expr->end_col_offset, + p->arena); + if (raw == NULL) + return NULL; + expr_ty conv = none; + int conversion = value->v.FormattedValue.conversion; + if (conversion >= 0) { + char buf[1]; + buf[0] = conversion; + constant uconv = _PyUnicode_FromASCII(buf, 1); + if (uconv == NULL) + return NULL; + conv = _PyAST_Constant(uconv, NULL, + expr->lineno, expr->col_offset, + expr->end_lineno, expr->end_col_offset, + p->arena); + if (conv == NULL) + return NULL; + } + expr_ty spec = value->v.FormattedValue.format_spec; + if (spec == NULL) { + spec = none; + } + asdl_expr_seq *elts = _Py_asdl_expr_seq_new(4, p->arena); + if (elts == NULL) + return NULL; + asdl_seq_SET(elts, 0, lambda); + asdl_seq_SET(elts, 1, raw); + asdl_seq_SET(elts, 2, conv); + asdl_seq_SET(elts, 3, spec); + expr_ty tuple = _PyAST_Tuple(elts, Load, + value->lineno, value->col_offset, + value->end_lineno, value->end_col_offset, + p->arena); + if (tuple == NULL) + return NULL; + asdl_seq_SET(values, i, tuple); + } + } + } + return _PyAST_TagString(tag, str, + tag->lineno, tag->col_offset, str->end_lineno, str->end_col_offset, + p->arena); +} + expr_ty -_PyPegen_concatenate_strings(Parser *p, asdl_seq *strings) +_PyPegen_concatenate_strings(Parser *p, asdl_seq *strings, int tagged) { Py_ssize_t len = asdl_seq_LEN(strings); assert(len > 0); @@ -893,7 +991,7 @@ _PyPegen_concatenate_strings(Parser *p, asdl_seq *strings) const char *fstr; Py_ssize_t fstrlen = -1; - if (_PyPegen_parsestr(p, &this_bytesmode, &this_rawmode, &s, &fstr, &fstrlen, t) != 0) { + if (_PyPegen_parsestr(p, &this_bytesmode, &this_rawmode, &s, &fstr, &fstrlen, t, tagged) != 0) { goto error; } diff --git a/Parser/parser.c b/Parser/parser.c index adc8d509eb7d7d..2fa4b362da794c 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -14111,6 +14111,7 @@ slice_rule(Parser *p) } // atom: +// | NAME STRING // | NAME // | 'True' // | 'False' @@ -14143,6 +14144,33 @@ atom_rule(Parser *p) UNUSED(_start_lineno); // Only used by EXTRA macro int _start_col_offset = p->tokens[_mark]->col_offset; UNUSED(_start_col_offset); // Only used by EXTRA macro + { // NAME STRING + if (p->error_indicator) { + p->level--; + return NULL; + } + D(fprintf(stderr, "%*c> atom[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME STRING")); + expr_ty a; + expr_ty b; + if ( + (a = _PyPegen_name_token(p)) // NAME + && + (b = _PyPegen_string_token(p)) // STRING + ) + { + D(fprintf(stderr, "%*c+ atom[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME STRING")); + _res = _PyPegen_tag_string ( p , a , ( Token* ) b ); + if (_res == NULL && PyErr_Occurred()) { + p->error_indicator = 1; + p->level--; + return NULL; + } + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s atom[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME STRING")); + } { // NAME if (p->error_indicator) { p->level--; @@ -15460,7 +15488,7 @@ strings_rule(Parser *p) ) { D(fprintf(stderr, "%*c+ strings[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "STRING+")); - _res = _PyPegen_concatenate_strings ( p , a ); + _res = _PyPegen_concatenate_strings ( p , a , 0 ); if (_res == NULL && PyErr_Occurred()) { p->error_indicator = 1; p->level--; diff --git a/Parser/pegen.h b/Parser/pegen.h index fe0c327b875566..b813feeea8130d 100644 --- a/Parser/pegen.h +++ b/Parser/pegen.h @@ -316,7 +316,8 @@ asdl_keyword_seq *_PyPegen_seq_delete_starred_exprs(Parser *, asdl_seq *); expr_ty _PyPegen_collect_call_seqs(Parser *, asdl_expr_seq *, asdl_seq *, int lineno, int col_offset, int end_lineno, int end_col_offset, PyArena *arena); -expr_ty _PyPegen_concatenate_strings(Parser *p, asdl_seq *); +expr_ty _PyPegen_concatenate_strings(Parser *p, asdl_seq *, int); +expr_ty _PyPegen_tag_string(Parser *p, expr_ty, Token *); expr_ty _PyPegen_ensure_imaginary(Parser *p, expr_ty); expr_ty _PyPegen_ensure_real(Parser *p, expr_ty); asdl_seq *_PyPegen_join_sequences(Parser *, asdl_seq *, asdl_seq *); diff --git a/Parser/string_parser.c b/Parser/string_parser.c index 9c12d8ca101d00..e2a0839b365de7 100644 --- a/Parser/string_parser.c +++ b/Parser/string_parser.c @@ -166,7 +166,7 @@ decode_bytes_with_escapes(Parser *p, const char *s, Py_ssize_t len, Token *t) string object. Return 0 if no errors occurred. */ int _PyPegen_parsestr(Parser *p, int *bytesmode, int *rawmode, PyObject **result, - const char **fstr, Py_ssize_t *fstrlen, Token *t) + const char **fstr, Py_ssize_t *fstrlen, Token *t, int tagged) { const char *s = PyBytes_AsString(t->bytes); if (s == NULL) { @@ -175,12 +175,16 @@ _PyPegen_parsestr(Parser *p, int *bytesmode, int *rawmode, PyObject **result, size_t len; int quote = Py_CHARMASK(*s); - int fmode = 0; + int fmode = tagged; *bytesmode = 0; - *rawmode = 0; + *rawmode = tagged; *result = NULL; *fstr = NULL; if (Py_ISALPHA(quote)) { + if (tagged) { + RAISE_SYNTAX_ERROR("Cannot combine tag and letter prefix"); + return -1; + } while (!*bytesmode || !*rawmode) { if (quote == 'b' || quote == 'B') { quote =(unsigned char)*++s; diff --git a/Parser/string_parser.h b/Parser/string_parser.h index 4a22f3d3086f47..bde64bde551c33 100644 --- a/Parser/string_parser.h +++ b/Parser/string_parser.h @@ -35,7 +35,7 @@ typedef struct { void _PyPegen_FstringParser_Init(FstringParser *); int _PyPegen_parsestr(Parser *, int *, int *, PyObject **, - const char **, Py_ssize_t *, Token *); + const char **, Py_ssize_t *, Token *, int); int _PyPegen_FstringParser_ConcatFstring(Parser *, FstringParser *, const char **, const char *, int, int, Token *, Token *, Token *); diff --git a/Python/Python-ast.c b/Python/Python-ast.c index da79463375a1a1..7863066fa73c2b 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -146,6 +146,7 @@ void _PyAST_Fini(PyInterpreterState *interp) Py_CLEAR(state->Sub_singleton); Py_CLEAR(state->Sub_type); Py_CLEAR(state->Subscript_type); + Py_CLEAR(state->TagString_type); Py_CLEAR(state->TryStar_type); Py_CLEAR(state->Try_type); Py_CLEAR(state->Tuple_type); @@ -246,6 +247,7 @@ void _PyAST_Fini(PyInterpreterState *interp) Py_CLEAR(state->slice); Py_CLEAR(state->step); Py_CLEAR(state->stmt_type); + Py_CLEAR(state->str); Py_CLEAR(state->subject); Py_CLEAR(state->tag); Py_CLEAR(state->target); @@ -343,6 +345,7 @@ static int init_identifiers(struct ast_state *state) if ((state->simple = PyUnicode_InternFromString("simple")) == NULL) return 0; if ((state->slice = PyUnicode_InternFromString("slice")) == NULL) return 0; if ((state->step = PyUnicode_InternFromString("step")) == NULL) return 0; + if ((state->str = PyUnicode_InternFromString("str")) == NULL) return 0; if ((state->subject = PyUnicode_InternFromString("subject")) == NULL) return 0; if ((state->tag = PyUnicode_InternFromString("tag")) == NULL) return 0; if ((state->target = PyUnicode_InternFromString("target")) == NULL) return 0; @@ -598,6 +601,10 @@ static const char * const FormattedValue_fields[]={ static const char * const JoinedStr_fields[]={ "values", }; +static const char * const TagString_fields[]={ + "tag", + "str", +}; static const char * const Constant_fields[]={ "value", "kind", @@ -1326,6 +1333,7 @@ init_types(struct ast_state *state) " | Call(expr func, expr* args, keyword* keywords)\n" " | FormattedValue(expr value, int conversion, expr? format_spec)\n" " | JoinedStr(expr* values)\n" + " | TagString(expr tag, expr str)\n" " | Constant(constant value, string? kind)\n" " | Attribute(expr value, identifier attr, expr_context ctx)\n" " | Subscript(expr value, expr slice, expr_context ctx)\n" @@ -1423,6 +1431,10 @@ init_types(struct ast_state *state) JoinedStr_fields, 1, "JoinedStr(expr* values)"); if (!state->JoinedStr_type) return 0; + state->TagString_type = make_type(state, "TagString", state->expr_type, + TagString_fields, 2, + "TagString(expr tag, expr str)"); + if (!state->TagString_type) return 0; state->Constant_type = make_type(state, "Constant", state->expr_type, Constant_fields, 2, "Constant(constant value, string? kind)"); @@ -3039,6 +3051,34 @@ _PyAST_JoinedStr(asdl_expr_seq * values, int lineno, int col_offset, int return p; } +expr_ty +_PyAST_TagString(expr_ty tag, expr_ty str, int lineno, int col_offset, int + end_lineno, int end_col_offset, PyArena *arena) +{ + expr_ty p; + if (!tag) { + PyErr_SetString(PyExc_ValueError, + "field 'tag' is required for TagString"); + return NULL; + } + if (!str) { + PyErr_SetString(PyExc_ValueError, + "field 'str' is required for TagString"); + return NULL; + } + p = (expr_ty)_PyArena_Malloc(arena, sizeof(*p)); + if (!p) + return NULL; + p->kind = TagString_kind; + p->v.TagString.tag = tag; + p->v.TagString.str = str; + p->lineno = lineno; + p->col_offset = col_offset; + p->end_lineno = end_lineno; + p->end_col_offset = end_col_offset; + return p; +} + expr_ty _PyAST_Constant(constant value, string kind, int lineno, int col_offset, int end_lineno, int end_col_offset, PyArena *arena) @@ -4544,6 +4584,21 @@ ast2obj_expr(struct ast_state *state, void* _o) goto failed; Py_DECREF(value); break; + case TagString_kind: + tp = (PyTypeObject *)state->TagString_type; + result = PyType_GenericNew(tp, NULL, NULL); + if (!result) goto failed; + value = ast2obj_expr(state, o->v.TagString.tag); + if (!value) goto failed; + if (PyObject_SetAttr(result, state->tag, value) == -1) + goto failed; + Py_DECREF(value); + value = ast2obj_expr(state, o->v.TagString.str); + if (!value) goto failed; + if (PyObject_SetAttr(result, state->str, value) == -1) + goto failed; + Py_DECREF(value); + break; case Constant_kind: tp = (PyTypeObject *)state->Constant_type; result = PyType_GenericNew(tp, NULL, NULL); @@ -9333,6 +9388,54 @@ obj2ast_expr(struct ast_state *state, PyObject* obj, expr_ty* out, PyArena* if (*out == NULL) goto failed; return 0; } + tp = state->TagString_type; + isinstance = PyObject_IsInstance(obj, tp); + if (isinstance == -1) { + return 1; + } + if (isinstance) { + expr_ty tag; + expr_ty str; + + if (_PyObject_LookupAttr(obj, state->tag, &tmp) < 0) { + return 1; + } + if (tmp == NULL) { + PyErr_SetString(PyExc_TypeError, "required field \"tag\" missing from TagString"); + return 1; + } + else { + int res; + if (Py_EnterRecursiveCall(" while traversing 'TagString' node")) { + goto failed; + } + res = obj2ast_expr(state, tmp, &tag, arena); + Py_LeaveRecursiveCall(); + if (res != 0) goto failed; + Py_CLEAR(tmp); + } + if (_PyObject_LookupAttr(obj, state->str, &tmp) < 0) { + return 1; + } + if (tmp == NULL) { + PyErr_SetString(PyExc_TypeError, "required field \"str\" missing from TagString"); + return 1; + } + else { + int res; + if (Py_EnterRecursiveCall(" while traversing 'TagString' node")) { + goto failed; + } + res = obj2ast_expr(state, tmp, &str, arena); + Py_LeaveRecursiveCall(); + if (res != 0) goto failed; + Py_CLEAR(tmp); + } + *out = _PyAST_TagString(tag, str, lineno, col_offset, end_lineno, + end_col_offset, arena); + if (*out == NULL) goto failed; + return 0; + } tp = state->Constant_type; isinstance = PyObject_IsInstance(obj, tp); if (isinstance == -1) { @@ -12002,6 +12105,9 @@ astmodule_exec(PyObject *m) if (PyModule_AddObjectRef(m, "JoinedStr", state->JoinedStr_type) < 0) { return -1; } + if (PyModule_AddObjectRef(m, "TagString", state->TagString_type) < 0) { + return -1; + } if (PyModule_AddObjectRef(m, "Constant", state->Constant_type) < 0) { return -1; } diff --git a/Python/ast.c b/Python/ast.c index 607281e2685535..1217ddde240d5c 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -321,6 +321,10 @@ validate_expr(struct validator *state, expr_ty exp, expr_context_ty ctx) case JoinedStr_kind: ret = validate_exprs(state, exp->v.JoinedStr.values, Load, 0); break; + case TagString_kind: + ret = validate_expr(state, exp->v.TagString.tag, Load) && + validate_expr(state, exp->v.TagString.str, Load); + break; case FormattedValue_kind: if (validate_expr(state, exp->v.FormattedValue.value, Load) == 0) return 0; diff --git a/Python/ast_opt.c b/Python/ast_opt.c index b1d807bcf10ae1..c6f636c3a88cd5 100644 --- a/Python/ast_opt.c +++ b/Python/ast_opt.c @@ -782,6 +782,10 @@ astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state) case JoinedStr_kind: CALL_SEQ(astfold_expr, expr, node_->v.JoinedStr.values); break; + case TagString_kind: + CALL(astfold_expr, expr_ty, node_->v.TagString.tag); + CALL(astfold_expr, expr_ty, node_->v.TagString.str); + break; case Attribute_kind: CALL(astfold_expr, expr_ty, node_->v.Attribute.value); break; diff --git a/Python/ast_unparse.c b/Python/ast_unparse.c index 6565b6b33ebd52..dca9b2817e115f 100644 --- a/Python/ast_unparse.c +++ b/Python/ast_unparse.c @@ -21,7 +21,7 @@ expr_as_unicode(expr_ty e, int level); static int append_ast_expr(_PyUnicodeWriter *writer, expr_ty e, int level); static int -append_joinedstr(_PyUnicodeWriter *writer, expr_ty e, bool is_format_spec); +append_joinedstr(_PyUnicodeWriter *writer, expr_ty e, bool is_format_spec, bool is_tag_str); static int append_formattedvalue(_PyUnicodeWriter *writer, expr_ty e); static int @@ -597,6 +597,41 @@ append_fstring_unicode(_PyUnicodeWriter *writer, PyObject *unicode) return result; } +static int +append_interpolation(_PyUnicodeWriter *writer, expr_ty e) +{ + APPEND_STR("{"); + if (e->kind == Tuple_kind) { + asdl_expr_seq *elts = e->v.Tuple.elts; + if (asdl_seq_LEN(elts) == 4) { + expr_ty raw = asdl_seq_GET(elts, 1); + if (raw->kind == Constant_kind) { + constant c = raw->v.Constant.value; + if (PyUnicode_CheckExact(c)) { + if (-1 == _PyUnicodeWriter_WriteStr(writer, c)) + return -1; + } + } + expr_ty conv = asdl_seq_GET(elts, 2); + if (conv->kind == Constant_kind) { + constant c = conv->v.Constant.value; + if (PyUnicode_CheckExact(c)) { + APPEND_STR("!"); + if (-1 == _PyUnicodeWriter_WriteStr(writer, c)) + return -1; + } + } + expr_ty spec = asdl_seq_GET(elts, 3); + if (spec->kind == JoinedStr_kind) { + APPEND_STR(":"); + if (-1 == append_joinedstr(writer, spec, true, false)) + return -1; + } + } + } + APPEND_STR_FINISH("}"); +} + static int append_fstring_element(_PyUnicodeWriter *writer, expr_ty e, bool is_format_spec) { @@ -604,9 +639,11 @@ append_fstring_element(_PyUnicodeWriter *writer, expr_ty e, bool is_format_spec) case Constant_kind: return append_fstring_unicode(writer, e->v.Constant.value); case JoinedStr_kind: - return append_joinedstr(writer, e, is_format_spec); + return append_joinedstr(writer, e, is_format_spec, false); case FormattedValue_kind: return append_formattedvalue(writer, e); + case Tuple_kind: + return append_interpolation(writer, e); default: PyErr_SetString(PyExc_SystemError, "unknown expression kind inside f-string"); @@ -640,16 +677,19 @@ build_fstring_body(asdl_expr_seq *values, bool is_format_spec) } static int -append_joinedstr(_PyUnicodeWriter *writer, expr_ty e, bool is_format_spec) +append_joinedstr(_PyUnicodeWriter *writer, expr_ty e, bool is_format_spec, bool is_tag_str) { - int result = -1; PyObject *body = build_fstring_body(e->v.JoinedStr.values, is_format_spec); if (!body) { return -1; } + int result = 0; if (!is_format_spec) { - if (-1 != append_charp(writer, "f") && + if (!is_tag_str) { + result = append_charp(writer, "f"); + } + if (-1 != result && -1 != append_repr(writer, body)) { result = 0; @@ -662,6 +702,18 @@ append_joinedstr(_PyUnicodeWriter *writer, expr_ty e, bool is_format_spec) return result; } +static int +append_tagstring(_PyUnicodeWriter *writer, expr_ty e) +{ + APPEND_EXPR(e->v.TagString.tag, 0); + expr_ty str = e->v.TagString.str; + if (str->kind == JoinedStr_kind) { + if (-1 == append_joinedstr(writer, str, false, true)) + return -1; + } + return 0; +} + static int append_formattedvalue(_PyUnicodeWriter *writer, expr_ty e) { @@ -886,7 +938,9 @@ append_ast_expr(_PyUnicodeWriter *writer, expr_ty e, int level) } return append_ast_constant(writer, e->v.Constant.value); case JoinedStr_kind: - return append_joinedstr(writer, e, false); + return append_joinedstr(writer, e, false, false); + case TagString_kind: + return append_tagstring(writer, e); case FormattedValue_kind: return append_formattedvalue(writer, e); /* The following exprs can be assignment targets. */ diff --git a/Python/compile.c b/Python/compile.c index 10d6307a48406f..5ab49fd79b50ee 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -4908,6 +4908,28 @@ compiler_joined_str(struct compiler *c, expr_ty e) return 1; } +static int +compiler_tag_string(struct compiler *c, expr_ty e) +{ + if (e->kind == TagString_kind) { + expr_ty tag = e->v.TagString.tag; + expr_ty str = e->v.TagString.str; + if (tag->kind == Name_kind) { + if (str->kind == JoinedStr_kind) { + // Generate code for tag(str1, str2, ...) + asdl_keyword_seq *keywords = + _Py_asdl_keyword_seq_new(0, c->c_arena); + if (keywords == NULL) + return 0; + ADDOP(c, PUSH_NULL); + VISIT(c, expr, tag); + return compiler_call_helper(c, 0, str->v.JoinedStr.values, keywords); + } + } + } + return compiler_error(c, "More complicated tag-string not yet supported"); +} + /* Used to implement f-strings. Format a single value. */ static int compiler_formatted_value(struct compiler *c, expr_ty e) @@ -5835,6 +5857,9 @@ compiler_visit_expr1(struct compiler *c, expr_ty e) break; case JoinedStr_kind: return compiler_joined_str(c, e); + case TagString_kind: + return compiler_tag_string(c, e); + break; case FormattedValue_kind: return compiler_formatted_value(c, e); /* The following exprs can be assignment targets. */ diff --git a/Python/symtable.c b/Python/symtable.c index 0b259b08b61f97..1690795733c804 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1686,6 +1686,10 @@ symtable_visit_expr(struct symtable *st, expr_ty e) case JoinedStr_kind: VISIT_SEQ(st, expr, e->v.JoinedStr.values); break; + case TagString_kind: + VISIT(st, expr, e->v.TagString.tag); + VISIT(st, expr, e->v.TagString.str); + break; case Constant_kind: /* Nothing to do here. */ break;