diff --git a/mpy-cross/mpconfigport.h b/mpy-cross/mpconfigport.h index 21d3e12ed43d7..c33ab6741d9e5 100644 --- a/mpy-cross/mpconfigport.h +++ b/mpy-cross/mpconfigport.h @@ -83,6 +83,8 @@ #define MICROPY_PY_IO (0) #define MICROPY_PY_SYS (0) +#define MICROPY_PY_FSTRING (1) + // type definitions for the specific machine #ifdef __LP64__ diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 7c09bdbd9218c..47d25919fa248 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -103,6 +103,7 @@ #define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) #define MICROPY_PY_MATH_ISCLOSE (1) #define MICROPY_PY_CMATH (1) +#define MICROPY_PY_FSTRING (1) #define MICROPY_PY_GC (1) #define MICROPY_PY_IO (1) #define MICROPY_PY_IO_IOBASE (1) diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 6f38d90e8d800..efb9471d4e9c0 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -118,6 +118,9 @@ #define MICROPY_PY_MATH_ISCLOSE (1) #define MICROPY_PY_MATH_FACTORIAL (1) #define MICROPY_PY_CMATH (1) +#ifndef MICROPY_PY_FSTRING +#define MICROPY_PY_FSTRING (1) +#endif #define MICROPY_PY_IO (1) #define MICROPY_PY_IO_IOBASE (1) #define MICROPY_PY_IO_FILEIO (MICROPY_VFS_FAT || MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2) diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index f3c61c18f100d..56c8734ac2ee9 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -122,6 +122,7 @@ #define MICROPY_PY_SYS_EXC_INFO (1) #define MICROPY_PY_COLLECTIONS_DEQUE (1) #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) +#define MICROPY_PY_FSTRING (1) #ifndef MICROPY_PY_MATH_SPECIAL_FUNCTIONS #define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) #endif diff --git a/ports/windows/mpconfigport.h b/ports/windows/mpconfigport.h index fa09dda7527e0..9e82f1fe35da9 100644 --- a/ports/windows/mpconfigport.h +++ b/ports/windows/mpconfigport.h @@ -90,6 +90,7 @@ #define MICROPY_PY_SYS_EXC_INFO (1) #define MICROPY_PY_COLLECTIONS_DEQUE (1) #define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1) +#define MICROPY_PY_FSTRING (1) #define MICROPY_PY_MATH_SPECIAL_FUNCTIONS (1) #define MICROPY_PY_MATH_ISCLOSE (1) #define MICROPY_PY_CMATH (1) diff --git a/py/lexer.c b/py/lexer.c index 7d2a251d41d75..0edd86f6aa2b6 100644 --- a/py/lexer.c +++ b/py/lexer.c @@ -62,6 +62,12 @@ STATIC bool is_char_or3(mp_lexer_t *lex, byte c1, byte c2, byte c3) { return lex->chr0 == c1 || lex->chr0 == c2 || lex->chr0 == c3; } +#if MICROPY_PY_FSTRING +STATIC bool is_char_or4(mp_lexer_t *lex, byte c1, byte c2, byte c3, byte c4) { + return lex->chr0 == c1 || lex->chr0 == c2 || lex->chr0 == c3 || lex->chr0 == c4; +} +#endif + STATIC bool is_char_following(mp_lexer_t *lex, byte c) { return lex->chr1 == c; } @@ -105,7 +111,13 @@ STATIC bool is_following_odigit(mp_lexer_t *lex) { STATIC bool is_string_or_bytes(mp_lexer_t *lex) { return is_char_or(lex, '\'', '\"') + #if MICROPY_PY_FSTRING + || (is_char_or4(lex, 'r', 'u', 'b', 'f') && is_char_following_or(lex, '\'', '\"')) + || (((is_char_and(lex, 'r', 'f') || is_char_and(lex, 'f', 'r')) + && is_char_following_following_or(lex, '\'', '\"'))) + #else || (is_char_or3(lex, 'r', 'u', 'b') && is_char_following_or(lex, '\'', '\"')) + #endif || ((is_char_and(lex, 'r', 'b') || is_char_and(lex, 'b', 'r')) && is_char_following_following_or(lex, '\'', '\"')); } @@ -119,6 +131,31 @@ STATIC bool is_tail_of_identifier(mp_lexer_t *lex) { return is_head_of_identifier(lex) || is_digit(lex); } +#if MICROPY_PY_FSTRING +STATIC void swap_char_banks(mp_lexer_t *lex) { + if (lex->vstr_postfix_processing) { + lex->chr3 = lex->chr0; + lex->chr4 = lex->chr1; + lex->chr5 = lex->chr2; + lex->chr0 = lex->vstr_postfix.buf[0]; + lex->chr1 = lex->vstr_postfix.buf[1]; + lex->chr2 = lex->vstr_postfix.buf[2]; + + lex->vstr_postfix_idx = 3; + } else { + // blindly reset to the "backup" bank when done postfix processing + // this restores control to the mp_reader + lex->chr0 = lex->chr3; + lex->chr1 = lex->chr4; + lex->chr2 = lex->chr5; + // willfully ignoring setting chr3-5 here - WARNING consider those garbage data now + + vstr_reset(&lex->vstr_postfix); + lex->vstr_postfix_idx = 0; + } +} +#endif + STATIC void next_char(mp_lexer_t *lex) { if (lex->chr0 == '\n') { // a new line @@ -134,7 +171,19 @@ STATIC void next_char(mp_lexer_t *lex) { lex->chr0 = lex->chr1; lex->chr1 = lex->chr2; - lex->chr2 = lex->reader.readbyte(lex->reader.data); + + #if MICROPY_PY_FSTRING + if (lex->vstr_postfix_processing) { + if (lex->vstr_postfix_idx == lex->vstr_postfix.len) { + lex->chr2 = '\0'; + } else { + lex->chr2 = lex->vstr_postfix.buf[lex->vstr_postfix_idx++]; + } + } else + #endif + { + lex->chr2 = lex->reader.readbyte(lex->reader.data); + } if (lex->chr1 == '\r') { // CR is a new line, converted to LF @@ -149,6 +198,13 @@ STATIC void next_char(mp_lexer_t *lex) { if (lex->chr2 == MP_LEXER_EOF && lex->chr1 != MP_LEXER_EOF && lex->chr1 != '\n') { lex->chr2 = '\n'; } + + #if MICROPY_PY_FSTRING + if (lex->vstr_postfix_processing && lex->chr0 == '\0') { + lex->vstr_postfix_processing = false; + swap_char_banks(lex); + } + #endif } STATIC void indent_push(mp_lexer_t *lex, size_t indent) { @@ -272,7 +328,7 @@ STATIC bool get_hex(mp_lexer_t *lex, size_t num_digits, mp_uint_t *result) { return true; } -STATIC void parse_string_literal(mp_lexer_t *lex, bool is_raw) { +STATIC void parse_string_literal(mp_lexer_t *lex, bool is_raw, bool is_fstring) { // get first quoting character char quote_char = '\''; if (is_char(lex, '\"')) { @@ -293,15 +349,69 @@ STATIC void parse_string_literal(mp_lexer_t *lex, bool is_raw) { } size_t n_closing = 0; + #if MICROPY_PY_FSTRING + bool in_expression = false; + bool expression_eat = true; + #endif + while (!is_end(lex) && (num_quotes > 1 || !is_char(lex, '\n')) && n_closing < num_quotes) { if (is_char(lex, quote_char)) { n_closing += 1; vstr_add_char(&lex->vstr, CUR_CHAR(lex)); } else { n_closing = 0; + + #if MICROPY_PY_FSTRING + if (is_fstring && is_char(lex, '{')) { + vstr_add_char(&lex->vstr, CUR_CHAR(lex)); + in_expression = !in_expression; + expression_eat = in_expression; + + if (lex->vstr_postfix.len == 0) { + vstr_add_str(&lex->vstr_postfix, ".format("); + } + + next_char(lex); + continue; + } + + if (is_fstring && is_char(lex, '}')) { + vstr_add_char(&lex->vstr, CUR_CHAR(lex)); + + if (in_expression) { + in_expression = false; + vstr_add_char(&lex->vstr_postfix, ','); + } + + next_char(lex); + continue; + } + + if (in_expression) { + // throw errors for illegal chars inside f-string expressions + if (is_char(lex, '#') || is_char(lex, '\\')) { + lex->tok_kind = MP_TOKEN_MALFORMED_FSTRING; + return; + } else if (is_char(lex, ':')) { + expression_eat = false; + } + + unichar c = CUR_CHAR(lex); + if (expression_eat) { + vstr_add_char(&lex->vstr_postfix, c); + } else { + vstr_add_char(&lex->vstr, c); + } + + next_char(lex); + continue; + } + #endif + if (is_char(lex, '\\')) { next_char(lex); unichar c = CUR_CHAR(lex); + if (is_raw) { // raw strings allow escaping of quotes, but the backslash is also emitted vstr_add_char(&lex->vstr, '\\'); @@ -450,6 +560,15 @@ STATIC bool skip_whitespace(mp_lexer_t *lex, bool stop_at_newline) { } void mp_lexer_to_next(mp_lexer_t *lex) { + #if MICROPY_PY_FSTRING + if (lex->vstr_postfix.len && !lex->vstr_postfix_processing) { + // end format call injection + vstr_add_char(&lex->vstr_postfix, ')'); + lex->vstr_postfix_processing = true; + swap_char_banks(lex); + } + #endif + // start new token text vstr_reset(&lex->vstr); @@ -505,6 +624,7 @@ void mp_lexer_to_next(mp_lexer_t *lex) { do { // parse type codes bool is_raw = false; + bool is_fstring = false; mp_token_kind_t kind = MP_TOKEN_STRING; int n_char = 0; if (is_char(lex, 'u')) { @@ -523,7 +643,23 @@ void mp_lexer_to_next(mp_lexer_t *lex) { kind = MP_TOKEN_BYTES; n_char = 2; } + #if MICROPY_PY_FSTRING + if (is_char_following(lex, 'f')) { + lex->tok_kind = MP_TOKEN_FSTRING_RAW; + break; + } + #endif } + #if MICROPY_PY_FSTRING + else if (is_char(lex, 'f')) { + if (is_char_following(lex, 'r')) { + lex->tok_kind = MP_TOKEN_FSTRING_RAW; + break; + } + n_char = 1; + is_fstring = true; + } + #endif // Set or check token kind if (lex->tok_kind == MP_TOKEN_END) { @@ -542,13 +678,12 @@ void mp_lexer_to_next(mp_lexer_t *lex) { } // Parse the literal - parse_string_literal(lex, is_raw); + parse_string_literal(lex, is_raw, is_fstring); // Skip whitespace so we can check if there's another string following skip_whitespace(lex, true); } while (is_string_or_bytes(lex)); - } else if (is_head_of_identifier(lex)) { lex->tok_kind = MP_TOKEN_NAME; @@ -702,6 +837,9 @@ mp_lexer_t *mp_lexer_new(qstr src_name, mp_reader_t reader) { lex->num_indent_level = 1; lex->indent_level = m_new(uint16_t, lex->alloc_indent_level); vstr_init(&lex->vstr, 32); + #if MICROPY_PY_FSTRING + vstr_init(&lex->vstr_postfix, 0); + #endif // store sentinel for first indentation level lex->indent_level[0] = 0; diff --git a/py/lexer.h b/py/lexer.h index 91767a44bf991..4ad01c004f09d 100644 --- a/py/lexer.h +++ b/py/lexer.h @@ -44,6 +44,10 @@ typedef enum _mp_token_kind_t { MP_TOKEN_INVALID, MP_TOKEN_DEDENT_MISMATCH, MP_TOKEN_LONELY_STRING_OPEN, + #if MICROPY_PY_FSTRING + MP_TOKEN_MALFORMED_FSTRING, + MP_TOKEN_FSTRING_RAW, + #endif MP_TOKEN_NEWLINE, MP_TOKEN_INDENT, @@ -158,6 +162,7 @@ typedef struct _mp_lexer_t { mp_reader_t reader; // stream source unichar chr0, chr1, chr2; // current cached characters from source + unichar chr3, chr4, chr5; // current cached characters from alt source size_t line; // current source line size_t column; // current source column @@ -173,6 +178,11 @@ typedef struct _mp_lexer_t { size_t tok_column; // token source column mp_token_kind_t tok_kind; // token kind vstr_t vstr; // token data + #if MICROPY_PY_FSTRING + vstr_t vstr_postfix; // postfix to apply to string + bool vstr_postfix_processing; + uint16_t vstr_postfix_idx; + #endif } mp_lexer_t; mp_lexer_t *mp_lexer_new(qstr src_name, mp_reader_t reader); diff --git a/py/mpconfig.h b/py/mpconfig.h index 287b15aaef32f..a794b344a4164 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -1113,6 +1113,12 @@ typedef double mp_float_t; #define MICROPY_PY_COLLECTIONS_NAMEDTUPLE__ASDICT (0) #endif +// Whether to include support for PEP-498 f-strings +#ifndef MICROPY_PY_FSTRING +#define MICROPY_PY_FSTRING (0) +#endif + + // Whether to provide "math" module #ifndef MICROPY_PY_MATH #define MICROPY_PY_MATH (1) diff --git a/py/parse.c b/py/parse.c index b93282165f88a..bcf0ad38d529a 100644 --- a/py/parse.c +++ b/py/parse.c @@ -1155,6 +1155,14 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { } else if (lex->tok_kind == MP_TOKEN_DEDENT_MISMATCH) { exc = mp_obj_new_exception_msg(&mp_type_IndentationError, MP_ERROR_TEXT("unindent doesn't match any outer indent level")); + #if MICROPY_PY_FSTRING + } else if (lex->tok_kind == MP_TOKEN_MALFORMED_FSTRING) { + exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, + MP_ERROR_TEXT("malformed f-string")); + } else if (lex->tok_kind == MP_TOKEN_FSTRING_RAW) { + exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, + MP_ERROR_TEXT("raw f-strings are not supported")); + #endif } else { exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, MP_ERROR_TEXT("invalid syntax")); diff --git a/tests/basics/string_fstring.py b/tests/basics/string_fstring.py new file mode 100644 index 0000000000000..c2d9f73ba7370 --- /dev/null +++ b/tests/basics/string_fstring.py @@ -0,0 +1,89 @@ +# Tests against https://www.python.org/dev/peps/pep-0498/ + +import sys + +print(f'no interpolation') +print(f"no interpolation") + +# Quoth the PEP: +# Backslashes may not appear anywhere within expressions. Comments, using the +# '#' character, are not allowed inside an expression +# +# CPython (3.7.4 on Linux) raises a SyntaxError here: +# >>> f'{#}' +# File "", line 1 +# SyntaxError: f-string expression part cannot include '#' +# >>> f'{\}' +# File "", line 1 +# SyntaxError: f-string expression part cannot include a backslash +# >>> f'{\\}' +# File "", line 1 +# SyntaxError: f-string expression part cannot include a backslash +# >>> f'{\#}' +# File "", line 1 +# SyntaxError: f-string expression part cannot include a backslash + +# Backslashes and comments allowed outside expression +print(f"\\") +print(f'#') + +## But not inside +try: + eval("f'{\}'") +except SyntaxError: + print('SyntaxError') +else: + print('f-string with backslash in expression did not raise SyntaxError') + +try: + eval("f'{#}'") +except SyntaxError: + print('SyntaxError') +else: + print('f-string with \'#\' in expression did not raise SyntaxError') + +# Quoth the PEP: +# While scanning the string for expressions, any doubled braces '{{' or '}}' +# inside literal portions of an f-string are replaced by the corresponding +# single brace. Doubled literal opening braces do not signify the start of an +# expression. A single closing curly brace '}' in the literal portion of a +# string is an error: literal closing curly braces must be doubled '}}' in +# order to represent a single closing brace. +# +# CPython (3.7.4 on Linux) raises a SyntaxError for the last case: +# >>> f'{{}' +# File "", line 1 +# SyntaxError: f-string: single '}' is not allowed + +print(f'{{}}') + +try: + eval("f'{{}'") +except (ValueError, SyntaxError): + # MicroPython incorrectly raises ValueError here. + print('SyntaxError') +else: + print('Expected ValueError for invalid f-string literal bracing') + +x = 1 +print(f'{x}') + +# Quoth the PEP: +# The expressions that are extracted from the string are evaluated in the +# context where the f-string appeared. This means the expression has full +# access to local and global variables. Any valid Python expression can be +# used, including function and method calls. Because the f-strings are +# evaluated where the string appears in the source code, there is no additional +# expressiveness available with f-strings. There are also no additional +# security concerns: you could have also just written the same expression, not +# inside of an f-string: + +def foo(): + return 20 + +print(f'result={foo()}', 'result=20') +print(f'result={foo()}', 'result={}'.format(foo())) +print(f'result={foo()}', 'result={result}'.format(result=foo())) + +# Other tests +print(f'{{{4*10}}}', '{40}') diff --git a/tests/basics/string_fstring.py.exp b/tests/basics/string_fstring.py.exp new file mode 100644 index 0000000000000..2e865a5c18937 --- /dev/null +++ b/tests/basics/string_fstring.py.exp @@ -0,0 +1,13 @@ +no interpolation +no interpolation +\ +# +SyntaxError +SyntaxError +{} +SyntaxError +1 +result=20 result=20 +result=20 result=20 +result=20 result=20 +{40} {40} diff --git a/tests/basics/string_fstring_mp.py b/tests/basics/string_fstring_mp.py new file mode 100644 index 0000000000000..e893ed6ce6e3a --- /dev/null +++ b/tests/basics/string_fstring_mp.py @@ -0,0 +1,42 @@ +# Tests the known limitations in the MicroPython implementation of f-strings. + +# Quoth the PEP: +# Adjacent f-strings and regular strings are concatenated. Regular strings are +# concatenated at compile time, and f-strings are concatenated at run time. For +# example, the expression: +# +# >>> x = 10 +# >>> y = 'hi' +# >>> 'a' 'b' f'{x}' '{c}' f'str<{y:^4}>' 'd' 'e' +# +# yields the value: 'ab10{c}str< hi >de' +# +# Because strings are concatenated at lexer time rather than parser time in +# MicroPython for mostly RAM efficiency reasons (see +# https://github.com/micropython/micropython/commit/534b7c368dc2af7720f3aaed0c936ef46d773957), +# and because f-strings here are implemented as a syntax translation +# (f'{something}' => '{}'.format(something)), this particular functionality is unimplemented, +# and in the above example, the '{c}' portion will trigger a KeyError on String.format() + +x = 10 +y = 'hi' +print(f'h' f'i') +print(f'h' 'i') +print('h' f'i') +print(f'{x:^4}') +print('a' 'b' f'{x}' f'str<{y:^4}>' 'd' 'e') + +## Raw f-strings are not supported in MicroPython +try: + eval("rf'hello'") +except SyntaxError: + print('SyntaxError') +else: + print('Raw f-string (rf) did not raise SyntaxError') + +try: + eval("fr'hello'") +except SyntaxError: + print('SyntaxError') +else: + print('Raw f-string (fr) did not raise SyntaxError') diff --git a/tests/basics/string_fstring_mp.py.exp b/tests/basics/string_fstring_mp.py.exp new file mode 100644 index 0000000000000..894bc8242a80c --- /dev/null +++ b/tests/basics/string_fstring_mp.py.exp @@ -0,0 +1,7 @@ +hi +hi +hi + 10 +ab10str< hi >de +SyntaxError +SyntaxError diff --git a/tests/cmdline/cmd_parsetree.py b/tests/cmdline/cmd_parsetree.py index 50da369543a31..483ea893734b4 100644 --- a/tests/cmdline/cmd_parsetree.py +++ b/tests/cmdline/cmd_parsetree.py @@ -10,3 +10,4 @@ e = b"a very long bytes that will not be interned" f = 123456789012345678901234567890 g = 123 +h = f"fstring: '{b}'" diff --git a/tests/cmdline/cmd_parsetree.py.exp b/tests/cmdline/cmd_parsetree.py.exp index 42a8228fb77e1..5ac17be84e181 100644 --- a/tests/cmdline/cmd_parsetree.py.exp +++ b/tests/cmdline/cmd_parsetree.py.exp @@ -1,6 +1,6 @@ ---------------- -[ 4] rule(1) (n=9) - tok(4) +[ 4] rule(1) (n=10) + tok(6) [ 4] rule(22) (n=4) id(i) [ 4] rule(45) (n=1) @@ -9,7 +9,7 @@ NULL [ 6] rule(5) (n=2) id(a) - tok(14) + tok(16) [ 7] rule(5) (n=2) id(b) str(str) @@ -28,9 +28,9 @@ [ 12] rule(5) (n=2) id(g) int(123) ----------------- -File cmdline/cmd_parsetree.py, code block '' (descriptor: \.\+, bytecode @\.\+ bytes) -Raw bytecode (code_info_size=\\d\+, bytecode_size=\\d\+): +[ 13] rule(5) (n=2) + id(h) +[ 13] rule(44) (n=2) ######## \.\+63 arg names: @@ -46,6 +46,7 @@ arg names: bc=32 line=10 bc=37 line=11 bc=42 line=12 + bc=48 line=13 00 BUILD_TUPLE 0 02 GET_ITER_STACK 03 FOR_ITER 12 @@ -65,8 +66,13 @@ arg names: 39 STORE_NAME f 42 LOAD_CONST_SMALL_INT 123 45 STORE_NAME g -48 LOAD_CONST_NONE -49 RETURN_VALUE +48 LOAD_CONST_OBJ \.\+ +50 LOAD_METHOD format +53 LOAD_NAME b (cache=0) +57 CALL_METHOD n=1 nkw=0 +59 STORE_NAME h +62 LOAD_CONST_NONE +63 RETURN_VALUE mem: total=\\d\+, current=\\d\+, peak=\\d\+ stack: \\d\+ out of \\d\+ GC: total: \\d\+, used: \\d\+, free: \\d\+ diff --git a/tests/feature_check/fstring.py b/tests/feature_check/fstring.py new file mode 100644 index 0000000000000..14792bce0a3ae --- /dev/null +++ b/tests/feature_check/fstring.py @@ -0,0 +1,3 @@ +# check whether f-strings (PEP-498) are supported +a = 1 +print(f"a={a}") diff --git a/tests/feature_check/fstring.py.exp b/tests/feature_check/fstring.py.exp new file mode 100644 index 0000000000000..73cdb8bcc8734 --- /dev/null +++ b/tests/feature_check/fstring.py.exp @@ -0,0 +1 @@ +a=1 diff --git a/tests/run-tests b/tests/run-tests index 102b0f7790e14..a3180ffffdaef 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -235,6 +235,7 @@ def run_tests(pyb, tests, args, base_path="."): skip_const = False skip_revops = False skip_io_module = False + skip_fstring = False skip_endian = False has_complex = True has_coverage = False @@ -289,6 +290,11 @@ def run_tests(pyb, tests, args, base_path="."): if output != b'uio\n': skip_io_module = True + # Check if fstring feature is enabled, and skip such tests if it doesn't + output = run_feature_check(pyb, args, base_path, 'fstring.py') + if output != b'a=1\n': + skip_fstring = True + # Check if emacs repl is supported, and skip such tests if it's not t = run_feature_check(pyb, args, base_path, 'repl_emacs_check.py') if 'True' not in str(t, 'ascii'): @@ -454,6 +460,7 @@ def run_tests(pyb, tests, args, base_path="."): is_async = test_name.startswith(("async_", "uasyncio_")) is_const = test_name.startswith("const") is_io_module = test_name.startswith("io_") + is_fstring = test_name.startswith("string_fstring") skip_it = test_file in skip_tests skip_it |= skip_native and is_native @@ -466,6 +473,7 @@ def run_tests(pyb, tests, args, base_path="."): skip_it |= skip_const and is_const skip_it |= skip_revops and test_name.startswith("class_reverse_op") skip_it |= skip_io_module and is_io_module + skip_it |= skip_fstring and is_fstring if args.list_tests: if not skip_it: diff --git a/tools/tinytest-codegen.py b/tools/tinytest-codegen.py index 116a217b4bbc7..e021a16b3fefd 100755 --- a/tools/tinytest-codegen.py +++ b/tools/tinytest-codegen.py @@ -99,6 +99,9 @@ def script_to_map(test_file): "misc/sys_settrace_loop.py", "misc/sys_settrace_generator.py", "misc/sys_settrace_features.py", + # don't have f-string + "basics/string_fstring.py", + "basics/string_fstring_mp.py", ) output = [] pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy