diff --git a/Lib/test/test_capi/test_tokenizer.py b/Lib/test/test_capi/test_tokenizer.py index 2fe1fef241e90a..e986e0f6b74f40 100644 --- a/Lib/test/test_capi/test_tokenizer.py +++ b/Lib/test/test_capi/test_tokenizer.py @@ -9,9 +9,6 @@ class TokenizerTests(unittest.TestCase): def test_source(self): _testinternalcapi.test_tokenizer_source() - def test_cursor(self): - _testinternalcapi.test_tokenizer_cursor() - if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index c1ef1a73f05c20..debd9a41063e49 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -1679,6 +1679,12 @@ def __repr__(self): self.assertEqual(f'{" # nooo "=}', '" # nooo "=\' # nooo \'') self.assertEqual(f'{" \" # nooo \" "=}', '" \\" # nooo \\" "=\' " # nooo " \'') + self.assertEqual(f'{"""a" # inside"""=}', + '"""a" # inside"""=\'a" # inside\'') + self.assertEqual(f"{'''a' # inside'''=}", + "'''a' # inside'''=\"a' # inside\"") + self.assertEqual(f'{"""a""""#" # outside +=}', '"""a""""#" \n=\'a#\'') self.assertEqual(f'{ # some comment goes here """hello"""=}', ' \n """hello"""=\'hello\'') diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py index 7e02191db86be5..0b5bdcdeda3c19 100644 --- a/Lib/test/test_tokenize.py +++ b/Lib/test/test_tokenize.py @@ -2427,6 +2427,31 @@ def test_stop_iteration_skips_encoded_readline_codec_lookup(self): (token.ENDMARKER, "", (1, 0), (1, 0), ""), ) + def test_fstring_offsets_survive_buffer_reallocation(self): + padding = " " * 9000 + expression_line = ")=:>{2}}\n" + physical_lines = [ + 'f"""\n', + "{(\n", + padding + "1\n", + expression_line, + '"""\n', + ] + source = "".join(physical_lines) + chunks = iter([ + "".join(physical_lines[:2]), + "".join(physical_lines[2:4]), + physical_lines[4], + "", + ]) + + expected = self._get_tokens(source, extra_tokens=True) + tokens = list(tokenize._generate_tokens_from_c_tokenizer( + chunks.__next__, + extra_tokens=True, + )) + self.assertEqual(tokens, expected) + def test_extra_tokens_relaxes_lexer_errors(self): cases = [ ( diff --git a/Lib/test/test_tstring.py b/Lib/test/test_tstring.py index 74653c77c55de1..b60b50e446e365 100644 --- a/Lib/test/test_tstring.py +++ b/Lib/test/test_tstring.py @@ -287,5 +287,13 @@ def test_triple_quoted(self): ) self.assertEqual(fstring(t), "\n Hello,\n Python\n ") + t = t'{"""a" # inside"""}' + self.assertEqual(t.interpolations[0].expression, + '"""a" # inside"""') + + t = t'{"""a""""#" # outside +}' + self.assertEqual(t.interpolations[0].expression, '"""a""""#"') + if __name__ == '__main__': unittest.main() diff --git a/Makefile.pre.in b/Makefile.pre.in index adcfe4c5259eb2..982b2d66216c98 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -399,7 +399,6 @@ TOKENIZER_OBJS= \ Parser/lexer/number.o \ Parser/lexer/state.o \ Parser/lexer/string.o \ - Parser/tokenizer/cursor.o \ Parser/tokenizer/decoder.o \ Parser/tokenizer/reader.o \ Parser/tokenizer/source.o \ @@ -415,7 +414,6 @@ TOKENIZER_HEADERS= \ Parser/lexer/lexer.h \ Parser/lexer/lexer_internal.h \ Parser/lexer/state.h \ - Parser/tokenizer/cursor.h \ Parser/tokenizer/reader.h \ Parser/tokenizer/reader_internal.h \ Parser/tokenizer/source.h \ @@ -3462,7 +3460,7 @@ MODULE__SOCKET_DEPS=$(srcdir)/Modules/socketmodule.h $(srcdir)/Modules/addrinfo. MODULE__SSL_DEPS=$(srcdir)/Modules/_ssl.h $(srcdir)/Modules/_ssl/cert.c $(srcdir)/Modules/_ssl/debughelpers.c $(srcdir)/Modules/_ssl/misc.c $(srcdir)/Modules/_ssl_data_111.h $(srcdir)/Modules/_ssl_data_300.h $(srcdir)/Modules/socketmodule.h MODULE__TESTCAPI_DEPS=$(srcdir)/Modules/_testcapi/parts.h $(srcdir)/Modules/_testcapi/util.h MODULE__TESTLIMITEDCAPI_DEPS=$(srcdir)/Modules/_testlimitedcapi/testcapi_long.h $(srcdir)/Modules/_testlimitedcapi/parts.h $(srcdir)/Modules/_testlimitedcapi/util.h -MODULE__TESTINTERNALCAPI_DEPS=$(srcdir)/Modules/_testinternalcapi/parts.h $(srcdir)/Parser/tokenizer/cursor.h $(srcdir)/Parser/tokenizer/source.h $(srcdir)/Python/ceval.h $(srcdir)/Modules/_testinternalcapi/test_targets.h $(srcdir)/Modules/_testinternalcapi/test_cases.c.h +MODULE__TESTINTERNALCAPI_DEPS=$(srcdir)/Modules/_testinternalcapi/parts.h $(srcdir)/Parser/tokenizer/source.h $(srcdir)/Python/ceval.h $(srcdir)/Modules/_testinternalcapi/test_targets.h $(srcdir)/Modules/_testinternalcapi/test_cases.c.h MODULE__SQLITE3_DEPS=$(srcdir)/Modules/_sqlite/connection.h $(srcdir)/Modules/_sqlite/cursor.h $(srcdir)/Modules/_sqlite/microprotocols.h $(srcdir)/Modules/_sqlite/module.h $(srcdir)/Modules/_sqlite/prepare_protocol.h $(srcdir)/Modules/_sqlite/row.h $(srcdir)/Modules/_sqlite/util.h MODULE__ZSTD_DEPS=$(srcdir)/Modules/_zstd/_zstdmodule.h $(srcdir)/Modules/_zstd/buffer.h $(srcdir)/Modules/_zstd/zstddict.h diff --git a/Modules/_testinternalcapi/tokenizer.c b/Modules/_testinternalcapi/tokenizer.c index 0b292410d3eb4e..46ac3c40724cff 100644 --- a/Modules/_testinternalcapi/tokenizer.c +++ b/Modules/_testinternalcapi/tokenizer.c @@ -1,6 +1,6 @@ #include "parts.h" -#include "../../Parser/tokenizer/cursor.h" +#include "../../Parser/tokenizer/source.h" static int check(int condition, const char *message) @@ -23,16 +23,6 @@ check_system_error(int failed, const char *message) return 0; } -static int -same_cursor(const _PyTok_Cursor *left, const _PyTok_Cursor *right) -{ - return left->source == right->source && - left->pos == right->pos && - left->line_start == right->line_start && - left->line_end == right->line_end && - left->lineno == right->lineno; -} - static PyObject * test_tokenizer_source(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) @@ -40,80 +30,24 @@ test_tokenizer_source(PyObject *Py_UNUSED(module), _PyTok_SourceText source; _PyTok_SourceInit(&source); - _PyTok_Loc loc; - _PyTok_Line line; - if (check(_PyTok_SourceLocation( - &source, 0, _PYTOK_AFFINITY_RIGHT, &loc) == 0, - "cannot locate empty source") < 0 || - check(loc.lineno == 1 && loc.byte_col == 0, - "wrong empty source location") < 0 || - check(_PyTok_SourceLine(&source, 1, &line) == 0, - "cannot find empty source line") < 0 || - check(line.start == 0 && line.end == 0, - "wrong empty source line") < 0 || - check_system_error( - _PyTok_SourceAppendLine(&source, "", 0, 0) < 0, - "accepted empty source line") < 0 || + if (check_system_error( + _PyTok_SourceAppendLine(&source, "", 0, 0) < 0, + "accepted empty source line") < 0 || check_system_error( _PyTok_SourceAppendLine(&source, "a\nb\n", 4, 0) < 0, "accepted multiple source lines") < 0 || check_system_error( _PyTok_SourceAppendLine(&source, "a", 1, 1) < 0, - "accepted missing implicit newline") < 0) { - goto error; - } - - if (check(_PyTok_SourceAppendLine(&source, "alpha\n", 6, 0) == 0, - "wrong first source offset") < 0 || + "accepted missing implicit newline") < 0 || + check(_PyTok_SourceAppendLine( + &source, "alpha\n", 6, 0) == 0, + "wrong first source offset") < 0 || check(_PyTok_SourceAppendLine( &source, "\xce\xb2\n", 3, 1) == 6, "wrong second source offset") < 0 || - check(_PyTok_SourceAppendLine( - &source, "nul\0x\n", 6, 0) == 9, - "wrong third source offset") < 0) { - goto error; - } - - int marker_line = 257; - int final_line = 300; - _PyTok_Off marker_start = -1; - for (int lineno = 4; lineno <= final_line; lineno++) { - const char *text = lineno == marker_line ? "marker\n" : "x\n"; - Py_ssize_t len = (Py_ssize_t)strlen(text); - _PyTok_Off start = _PyTok_SourceAppendLine( - &source, text, len, lineno == final_line); - if (start < 0) { - goto error; - } - if (lineno == marker_line) { - marker_start = start; - } - } - - if (check(source.nlines == final_line, "wrong source line count") < 0 || - check(_PyTok_SourceLine(&source, marker_line, &line) == 0, - "cannot find late source line") < 0 || - check(line.start == marker_start && - line.end == marker_start + 7, - "wrong late source line") < 0 || - check(!line.implicit_newline && !line.contains_nul, - "wrong late source flags") < 0 || - check(_PyTok_SourceLine(&source, 2, &line) == 0, - "cannot find second source line") < 0 || - check(line.start == 6 && line.end == 9 && - line.implicit_newline && !line.contains_nul, - "wrong second source line") < 0 || check(!_PyTok_SourceLineIsImplicit(&source, 1) && _PyTok_SourceLineIsImplicit(&source, 2), - "wrong early implicit newline flags") < 0 || - check(_PyTok_SourceLine(&source, 3, &line) == 0, - "cannot find third source line") < 0 || - check(line.contains_nul, "missing null byte flag") < 0 || - check(_PyTok_SourceLine(&source, final_line, &line) == 0, - "cannot find final source line") < 0 || - check(line.implicit_newline && - _PyTok_SourceLineIsImplicit(&source, final_line), - "missing late implicit newline flag") < 0) { + "wrong implicit newline flags") < 0) { goto error; } @@ -123,178 +57,21 @@ test_tokenizer_source(PyObject *Py_UNUSED(module), if (check(view != NULL && view_len == 2 && memcmp(view, "\xce\xb2", 2) == 0, "wrong source span view") < 0 || - check(_PyTok_SourceLocation( - &source, marker_start, - _PYTOK_AFFINITY_LEFT, &loc) == 0, - "cannot locate left line boundary") < 0 || - check(loc.lineno == marker_line - 1 && loc.byte_col == 2, - "wrong left boundary location") < 0 || - check(_PyTok_SourceLocation( - &source, marker_start, - _PYTOK_AFFINITY_RIGHT, &loc) == 0, - "cannot locate right line boundary") < 0 || - check(loc.lineno == marker_line && loc.byte_col == 0, - "wrong right boundary location") < 0 || - check(_PyTok_SourceLocation( - &source, marker_start + 1, - _PYTOK_AFFINITY_RIGHT, &loc) == 0, - "cannot locate late source byte") < 0 || - check(loc.lineno == marker_line && loc.byte_col == 1, - "wrong late source location") < 0) { - goto error; - } - - if (check(_PyTok_SourceLocation( - &source, source.len, _PYTOK_AFFINITY_LEFT, &loc) == 0, - "cannot locate left EOF") < 0 || - check(loc.lineno == final_line && loc.byte_col == 2, - "wrong left EOF location") < 0 || - check(_PyTok_SourceLocation( - &source, source.len, - _PYTOK_AFFINITY_RIGHT, &loc) == 0, - "cannot locate right EOF") < 0 || - check(loc.lineno == final_line + 1 && loc.byte_col == 0, - "wrong right EOF location") < 0 || - check(_PyTok_SourceLine(&source, final_line + 1, &line) == 0, - "cannot find virtual EOF line") < 0 || - check(line.start == source.len && line.end == source.len, - "wrong virtual EOF line") < 0 || - check(!_PyTok_SourceLineIsImplicit(&source, 0) && - !_PyTok_SourceLineIsImplicit( - &source, final_line + 1), - "virtual or invalid line is implicit") < 0) { - goto error; - } - - view = _PyTok_SourceSpanView( - &source, _PyTok_SpanFromBounds(0, source.len + 1), &view_len); - if (check_system_error(view == NULL, "accepted invalid source span") < 0 || check_system_error( - _PyTok_SourceLocation( - &source, source.len + 1, - _PYTOK_AFFINITY_RIGHT, &loc) < 0, - "accepted invalid source offset") < 0 || - check_system_error( - _PyTok_SourceLine(&source, final_line + 2, &line) < 0, - "accepted invalid source line") < 0) { + _PyTok_SourceSpanView( + &source, _PyTok_SpanFromBounds(0, source.len + 1), + &view_len) == NULL, + "accepted invalid source span") < 0) { goto error; } _PyTok_SourceClear(&source); - _PyTok_SourceInit(&source); if (_PyTok_SourceAppendLine(&source, "tail", 4, 0) < 0 || check_system_error( _PyTok_SourceAppendLine(&source, "x\n", 2, 0) < 0, - "appended after unterminated source line") < 0 || - check(_PyTok_SourceLocation( - &source, source.len, - _PYTOK_AFFINITY_RIGHT, &loc) == 0, - "cannot locate unterminated EOF") < 0 || - check(loc.lineno == 1 && loc.byte_col == 4, - "wrong unterminated EOF location") < 0) { - goto error; - } - - _PyTok_SourceClear(&source); - Py_RETURN_NONE; - -error: - _PyTok_SourceClear(&source); - return NULL; -} - -static PyObject * -test_tokenizer_cursor(PyObject *Py_UNUSED(module), - PyObject *Py_UNUSED(args)) -{ - _PyTok_SourceText source; - _PyTok_SourceInit(&source); - if (_PyTok_SourceAppendLine(&source, "ab\n", 3, 0) < 0 || - _PyTok_SourceAppendLine(&source, "cd\n", 3, 0) < 0) { - goto error; - } - - _PyTok_Cursor cursor; - _PyTok_CursorInit(&cursor, &source); - if (_PyTok_CursorSetOffset(&cursor, source.len) < 0 || - check(cursor.lineno == 3 && cursor.pos == source.len, - "wrong cursor at virtual EOF") < 0 || - _PyTok_CursorSetLine(&cursor, 1) < 0) { - goto error; - } - - char large[BUFSIZ + 1]; - memset(large, 'z', sizeof(large)); - large[sizeof(large) - 1] = '\n'; - if (_PyTok_SourceAppendLine(&source, large, sizeof(large), 0) < 0) { - goto error; - } - - if (check(_PyTok_CursorPeek(&cursor, 0) == 'a', - "wrong cursor peek after relocation") < 0 || - check(_PyTok_CursorPeek(&cursor, 1) == 'b', - "wrong distant cursor peek") < 0 || - check(_PyTok_CursorAdvance(&cursor) == 'a', - "wrong first cursor byte") < 0 || - check(_PyTok_CursorAdvance(&cursor) == 'b', - "wrong second cursor byte") < 0 || - check(_PyTok_CursorAdvance(&cursor) == '\n', - "wrong final cursor byte") < 0 || - check(_PyTok_CursorAdvance(&cursor) == EOF, - "cursor advanced past line") < 0 || - check(_PyTok_CursorSetOffset(&cursor, 2) == 0, - "cannot seek cursor offset") < 0 || - check(_PyTok_CursorAdvance(&cursor) == '\n', - "wrong cursor byte after seek") < 0 || - check(_PyTok_CursorSetOffset(&cursor, 3) == 0, - "cannot seek line boundary") < 0 || - check(cursor.lineno == 2 && cursor.line_start == 3 && - _PyTok_CursorAdvance(&cursor) == 'c', - "wrong cursor at line boundary") < 0 || - check(_PyTok_CursorSetLine(&cursor, 3) == 0, - "cannot advance cursor to final line") < 0 || - check(cursor.line_start == 6 && - _PyTok_CursorAdvance(&cursor) == 'z', - "wrong cursor byte on final line") < 0) { - goto error; - } - - _PyTok_Cursor saved = cursor; - if (check_system_error( - _PyTok_CursorSetOffset(&cursor, source.len + 1) < 0, - "accepted invalid cursor offset") < 0 || - check(same_cursor(&cursor, &saved), - "invalid offset changed cursor") < 0 || - check_system_error( - _PyTok_CursorSetLine(&cursor, source.nlines + 2) < 0, - "accepted invalid cursor line") < 0 || - check(same_cursor(&cursor, &saved), - "invalid line changed cursor") < 0 || - check(_PyTok_CursorSetOffset(&cursor, source.len) == 0, - "cannot set cursor to EOF") < 0 || - check(cursor.lineno == 4 && cursor.pos == source.len, - "wrong cursor at EOF") < 0) { - goto error; - } - -#if SIZEOF_VOID_P > 4 - char byte = 0; - _PyTok_SourceText huge_source = { - .bytes = &byte, - .len = (_PyTok_Off)INT_MAX + 1, - }; - _PyTok_Cursor huge_cursor = { - .source = &huge_source, - .pos = INT_MAX, - .line_end = (_PyTok_Off)INT_MAX + 1, - .lineno = 1, - }; - if (check(_PyTok_CursorAdvance(&huge_cursor) == EOF && - huge_cursor.pos == INT_MAX, - "cursor advanced past maximum column") < 0) { + "appended after unterminated source line") < 0) { goto error; } -#endif _PyTok_SourceClear(&source); Py_RETURN_NONE; @@ -306,7 +83,6 @@ test_tokenizer_cursor(PyObject *Py_UNUSED(module), static PyMethodDef test_methods[] = { {"test_tokenizer_source", test_tokenizer_source, METH_NOARGS}, - {"test_tokenizer_cursor", test_tokenizer_cursor, METH_NOARGS}, {NULL}, }; diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 33647ec284061f..93dd56a8ef166f 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -423,7 +423,6 @@ - @@ -593,7 +592,6 @@ - diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 434dd13267fe93..f97b51a0e48a3e 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -333,9 +333,6 @@ Parser - - Parser - Parser @@ -1361,9 +1358,6 @@ Parser - - Parser - Parser diff --git a/Parser/lexer/buffer.c b/Parser/lexer/buffer.c index cd6885a7d01040..97f5ad1f2872bd 100644 --- a/Parser/lexer/buffer.c +++ b/Parser/lexer/buffer.c @@ -1,62 +1,32 @@ #include "Python.h" -#include "errcode.h" - +#include "buffer.h" #include "state.h" -/* Traverse and remember all f-string buffers, in order to be able to restore - them after reallocating tok->buf */ void -_PyLexer_remember_fstring_buffers(struct tok_state *tok) +_PyLexer_SnapshotBuffer(struct tok_state *tok, const char *base, + _PyLexer_BufferSnapshot *snapshot) { - int index; - tokenizer_mode *mode; - - for (index = tok->tok_mode_stack_index; index >= 0; --index) { - mode = &(tok->tok_mode_stack[index]); - mode->start_offset = mode->start == NULL ? -1 : mode->start - tok->buf; - mode->multi_line_start_offset = mode->multi_line_start == NULL ? -1 : mode->multi_line_start - tok->buf; - } + snapshot->buf = tok->buf - base; + snapshot->cur = tok->cur - tok->buf; + snapshot->inp = tok->inp - tok->buf; + snapshot->start = tok->start == NULL ? -1 : tok->start - tok->buf; + snapshot->line_start = tok->line_start == NULL + ? -1 : tok->line_start - tok->buf; + snapshot->multi_line_start = tok->multi_line_start == NULL + ? -1 : tok->multi_line_start - tok->buf; } -/* Traverse and restore all f-string buffers after reallocating tok->buf */ void -_PyLexer_restore_fstring_buffers(struct tok_state *tok) -{ - int index; - tokenizer_mode *mode; - - for (index = tok->tok_mode_stack_index; index >= 0; --index) { - mode = &(tok->tok_mode_stack[index]); - mode->start = mode->start_offset < 0 ? NULL : tok->buf + mode->start_offset; - mode->multi_line_start = mode->multi_line_start_offset < 0 ? NULL : tok->buf + mode->multi_line_start_offset; - } -} - -int -_PyLexer_tok_reserve_buf(struct tok_state *tok, Py_ssize_t size) +_PyLexer_RestoreBuffer(struct tok_state *tok, char *base, + const _PyLexer_BufferSnapshot *snapshot) { - Py_ssize_t cur = tok->cur - tok->buf; - Py_ssize_t oldsize = tok->inp - tok->buf; - Py_ssize_t newsize = oldsize + Py_MAX(size, oldsize >> 1); - if (newsize > tok->end - tok->buf) { - char *newbuf = tok->buf; - Py_ssize_t start = tok->start == NULL ? -1 : tok->start - tok->buf; - Py_ssize_t line_start = tok->start == NULL ? -1 : tok->line_start - tok->buf; - Py_ssize_t multi_line_start = tok->multi_line_start - tok->buf; - _PyLexer_remember_fstring_buffers(tok); - newbuf = (char *)PyMem_Realloc(newbuf, newsize); - if (newbuf == NULL) { - tok->done = E_NOMEM; - return 0; - } - tok->buf = newbuf; - tok->cur = tok->buf + cur; - tok->inp = tok->buf + oldsize; - tok->end = tok->buf + newsize; - tok->start = start < 0 ? NULL : tok->buf + start; - tok->line_start = line_start < 0 ? NULL : tok->buf + line_start; - tok->multi_line_start = multi_line_start < 0 ? NULL : tok->buf + multi_line_start; - _PyLexer_restore_fstring_buffers(tok); - } - return 1; + tok->buf = base + snapshot->buf; + tok->cur = tok->buf + snapshot->cur; + tok->inp = tok->buf + snapshot->inp; + tok->start = snapshot->start < 0 + ? NULL : tok->buf + snapshot->start; + tok->line_start = snapshot->line_start < 0 + ? NULL : tok->buf + snapshot->line_start; + tok->multi_line_start = snapshot->multi_line_start < 0 + ? NULL : tok->buf + snapshot->multi_line_start; } diff --git a/Parser/lexer/buffer.h b/Parser/lexer/buffer.h index bb218162ff4845..06c54ff8da944a 100644 --- a/Parser/lexer/buffer.h +++ b/Parser/lexer/buffer.h @@ -3,8 +3,20 @@ #include "pyport.h" -void _PyLexer_remember_fstring_buffers(struct tok_state *tok); -void _PyLexer_restore_fstring_buffers(struct tok_state *tok); -int _PyLexer_tok_reserve_buf(struct tok_state *tok, Py_ssize_t size); +struct tok_state; + +typedef struct { + Py_ssize_t buf; + Py_ssize_t cur; + Py_ssize_t inp; + Py_ssize_t start; + Py_ssize_t line_start; + Py_ssize_t multi_line_start; +} _PyLexer_BufferSnapshot; + +void _PyLexer_SnapshotBuffer( + struct tok_state *, const char *, _PyLexer_BufferSnapshot *); +void _PyLexer_RestoreBuffer( + struct tok_state *, char *, const _PyLexer_BufferSnapshot *); #endif diff --git a/Parser/lexer/lexer.c b/Parser/lexer/lexer.c index a96362c8961023..79b4de007c6daf 100644 --- a/Parser/lexer/lexer.c +++ b/Parser/lexer/lexer.c @@ -12,8 +12,21 @@ #define MAKE_TOKEN(token_type) _PyLexer_token_setup(tok, token, token_type, p_start, p_end) -#define MAKE_TYPE_COMMENT_TOKEN(token_type, col_offset, end_col_offset) (\ - _PyLexer_type_comment_token_setup(tok, token, token_type, col_offset, end_col_offset, p_start, p_end)) + +static int +type_comment_token_setup(struct tok_state *tok, struct token *token, int type, + int col_offset, int end_col_offset, + const char *start, const char *end) +{ + _PyLexer_token_setup(tok, token, type, start, end); + token->start_loc = (_PyTok_Loc){tok->lineno, col_offset}; + token->end_loc = (_PyTok_Loc){tok->lineno, end_col_offset}; + return type; +} + +#define MAKE_TYPE_COMMENT_TOKEN(token_type, col_offset, end_col_offset) \ + type_comment_token_setup(tok, token, token_type, col_offset, \ + end_col_offset, p_start, p_end) /* Spaces in this constant are treated as "zero or more spaces or tabs" when tokenizing. */ @@ -542,8 +555,8 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str int cursor_in_format_with_debug = cursor == 1 && (current_tok->in_debug || in_format_spec); int cursor_valid = cursor == 0 || cursor_in_format_with_debug; - if ((cursor_valid) && !_PyLexer_update_ftstring_expr(tok, c)) { - return MAKE_TOKEN(ENDMARKER); + if (cursor_valid) { + _PyLexer_update_ftstring_expr(tok, c); } if ((cursor_valid) && c != '{' && _PyLexer_set_ftstring_expr(tok, token, c)) { return MAKE_TOKEN(ERRORTOKEN); diff --git a/Parser/lexer/lexer.h b/Parser/lexer/lexer.h index 1d97ac57b745b0..7a851c34af3d47 100644 --- a/Parser/lexer/lexer.h +++ b/Parser/lexer/lexer.h @@ -3,8 +3,36 @@ #include "state.h" -int _PyLexer_update_ftstring_expr(struct tok_state *tok, char cur); - int _PyTokenizer_Get(struct tok_state *, struct token *); +static inline const char * +_PyLexer_SpanView(const struct tok_state *tok, _PyTok_Span span, + Py_ssize_t *length) +{ + assert(length != NULL); + assert(_PyTok_SpanIsValid(span)); + assert(tok->buf != NULL); + assert(tok->inp >= tok->buf); + if (span.start >= tok->buf_offset && + span.end - tok->buf_offset <= tok->inp - tok->buf) { + *length = span.end - span.start; + return tok->buf + (span.start - tok->buf_offset); + } + return _PyTok_SourceSpanView(&tok->source, span, length); +} + +/* The view points into the current input window. The next + _PyTokenizer_Get() call may discard it. */ +static inline const char * +_PyToken_TextView(const struct tok_state *tok, const struct token *token, + Py_ssize_t *length) +{ + assert(length != NULL); + if (!_PyTok_SpanIsValid(token->span)) { + *length = 0; + return ""; + } + return _PyLexer_SpanView(tok, token->span, length); +} + #endif diff --git a/Parser/lexer/lexer_internal.h b/Parser/lexer/lexer_internal.h index c6d3b9045c7292..01825bb0bae3be 100644 --- a/Parser/lexer/lexer_internal.h +++ b/Parser/lexer/lexer_internal.h @@ -46,6 +46,7 @@ TOK_NEXT_MODE(struct tok_state *tok) int _PyLexer_nextc(struct tok_state *); void _PyLexer_backup(struct tok_state *, int); +void _PyLexer_update_ftstring_expr(struct tok_state *, char); int _PyLexer_set_ftstring_expr(struct tok_state *, struct token *, char); int _PyLexer_check_string_prefixes(struct tok_state *, int, int, int, int, int); int _PyLexer_scan_number(struct tok_state *, struct token *, int, int); diff --git a/Parser/lexer/state.c b/Parser/lexer/state.c index 2a6408bef927a3..3702e38b8ff74c 100644 --- a/Parser/lexer/state.c +++ b/Parser/lexer/state.c @@ -26,7 +26,6 @@ _PyTokenizer_tok_new(void) tok->interactive_src_start = NULL; tok->interactive_src_end = NULL; tok->start = NULL; - tok->end = NULL; tok->done = E_OK; tok->fp = NULL; tok->tabsize = TABSIZE; @@ -61,24 +60,6 @@ _PyTokenizer_tok_new(void) return tok; } -static void -free_fstring_expressions(struct tok_state *tok) -{ - int index; - tokenizer_mode *mode; - - for (index = tok->tok_mode_stack_index; index >= 0; --index) { - mode = &(tok->tok_mode_stack[index]); - if (mode->last_expr_buffer != NULL) { - PyMem_Free(mode->last_expr_buffer); - mode->last_expr_buffer = NULL; - mode->last_expr_size = 0; - mode->last_expr_end = -1; - mode->in_format_spec = 0; - } - } -} - /* Free a tok_state structure */ void _PyTokenizer_Free(struct tok_state *tok) @@ -90,7 +71,6 @@ _PyTokenizer_Free(struct tok_state *tok) Py_XDECREF(tok->module); _PyTok_ReaderFree(tok); _PyTok_SourceClear(&tok->source); - free_fstring_expressions(tok); PyMem_Free(tok); } @@ -101,41 +81,46 @@ _PyToken_Free(struct token *token) { void _PyToken_Init(struct token *token) { +#ifdef Py_DEBUG + token->span = (_PyTok_Span){-1, -1}; + token->start_loc = (_PyTok_Loc){-1, -1}; + token->end_loc = (_PyTok_Loc){-1, -1}; +#endif token->metadata = NULL; } -int -_PyLexer_type_comment_token_setup(struct tok_state *tok, struct token *token, int type, int col_offset, - int end_col_offset, const char *start, const char *end) +static inline _PyTok_Span +buffer_span(const struct tok_state *tok, const char *start, const char *end) { - token->level = tok->level; - token->lineno = token->end_lineno = tok->lineno; - token->col_offset = col_offset; - token->end_col_offset = end_col_offset; - token->start = start; - token->end = end; - return type; + if (start == NULL) { + assert(end == NULL); + return (_PyTok_Span){-1, -1}; + } + assert(end != NULL); + const char *base = tok->buf; + assert(base != NULL); + assert(tok->inp >= base); + Py_ssize_t start_offset = start - base; + Py_ssize_t end_offset = end - base; + assert(start_offset >= 0 && start_offset <= end_offset); + assert(end_offset <= tok->inp - base); + assert(tok->buf_offset <= PY_SSIZE_T_MAX - end_offset); + return _PyTok_SpanFromBounds( + tok->buf_offset + start_offset, tok->buf_offset + end_offset); } int _PyLexer_token_setup(struct tok_state *tok, struct token *token, int type, const char *start, const char *end) { - assert((start == NULL && end == NULL) || (start != NULL && end != NULL)); token->level = tok->level; - if (ISSTRINGLIT(type)) { - token->lineno = tok->first_lineno; - } - else { - token->lineno = tok->lineno; - } - token->end_lineno = tok->lineno; - token->col_offset = token->end_col_offset = -1; - token->start = start; - token->end = end; + token->span = buffer_span(tok, start, end); + int lineno = ISSTRINGLIT(type) ? tok->first_lineno : tok->lineno; + token->start_loc = (_PyTok_Loc){lineno, -1}; + token->end_loc = (_PyTok_Loc){tok->lineno, -1}; if (start != NULL && end != NULL) { - token->col_offset = tok->starting_col_offset; - token->end_col_offset = tok->col_offset; + token->start_loc.byte_col = tok->starting_col_offset; + token->end_loc.byte_col = tok->col_offset; } return type; } diff --git a/Parser/lexer/state.h b/Parser/lexer/state.h index 0824785195491e..9a2c442898ba38 100644 --- a/Parser/lexer/state.h +++ b/Parser/lexer/state.h @@ -23,8 +23,9 @@ enum interactive_underflow_t { struct token { int level; - int lineno, col_offset, end_lineno, end_col_offset; - const char *start, *end; + _PyTok_Span span; + _PyTok_Loc start_loc; + _PyTok_Loc end_loc; PyObject *metadata; }; @@ -49,16 +50,11 @@ typedef struct _tokenizer_mode { char quote; int quote_size; int raw; - const char* start; - const char* multi_line_start; + _PyTok_Off start; + _PyTok_Off multi_line_start; int first_line; - Py_ssize_t start_offset; - Py_ssize_t multi_line_start_offset; - - Py_ssize_t last_expr_size; - Py_ssize_t last_expr_end; - char* last_expr_buffer; + _PyTok_Span debug_expr; int in_debug; int in_format_spec; @@ -67,15 +63,15 @@ typedef struct _tokenizer_mode { /* Tokenizer state */ struct tok_state { - /* Input state; buf <= cur <= inp <= end */ + /* Input state; buf <= cur <= inp */ /* NB an entire line is held in the buffer */ - char *buf; /* Input buffer, or NULL; malloc'ed if fp != NULL or readline != NULL */ + char *buf; /* Owned for file/readline input; source-backed otherwise. */ char *cur; /* Next character in buffer */ char *inp; /* End of data in buffer */ + _PyTok_Off buf_offset; /* Logical offset of buf[0]. */ int fp_interactive; /* If the file descriptor is interactive */ char *interactive_src_start; /* The start of the source parsed so far in interactive mode */ char *interactive_src_end; /* The end of the source parsed so far in interactive mode */ - const char *end; /* End of input buffer if buf != NULL */ const char *start; /* Start of current token if not NULL */ int done; /* E_OK normally, E_EOF at EOF, otherwise error code */ /* NB If done != E_OK, cur must be == inp!!! */ @@ -128,8 +124,6 @@ struct tok_state { #endif }; -int _PyLexer_type_comment_token_setup(struct tok_state *tok, struct token *token, int type, int col_offset, - int end_col_offset, const char *start, const char *end); int _PyLexer_token_setup(struct tok_state *tok, struct token *token, int type, const char *start, const char *end); struct tok_state *_PyTokenizer_tok_new(void); diff --git a/Parser/lexer/string.c b/Parser/lexer/string.c index d67c48f7f678ed..c2c2b50fd053ca 100644 --- a/Parser/lexer/string.c +++ b/Parser/lexer/string.c @@ -7,6 +7,73 @@ #define MAKE_TOKEN(token_type) _PyLexer_token_setup(tok, token, token_type, p_start, p_end) +static _PyTok_Off +current_offset(const struct tok_state *tok, const char *position) +{ + assert(position >= tok->buf && position <= tok->inp); + return tok->buf_offset + (position - tok->buf); +} + +static char * +offset_pointer(const struct tok_state *tok, _PyTok_Off offset) +{ + if (offset >= tok->buf_offset && + offset - tok->buf_offset <= tok->inp - tok->buf) { + return tok->buf + (offset - tok->buf_offset); + } + assert(offset >= 0 && offset <= tok->source.len); + return tok->source.bytes + offset; +} + +static Py_ssize_t +strip_expr_comments(const char *expr, Py_ssize_t len, char *result) +{ + Py_ssize_t output = 0; + char quote = 0; + int quote_size = 0; + + for (Py_ssize_t i = 0; i < len;) { + char c = expr[i]; + if (quote != 0) { + if (c == '\\' && i + 1 < len) { + result[output] = c; + result[output + 1] = expr[i + 1]; + output += 2; + i += 2; + continue; + } + if (c == quote) { + if (quote_size == 1) { + quote = 0; + } + else if (i + 2 < len && expr[i + 1] == quote && + expr[i + 2] == quote) { + memcpy(result + output, expr + i, 3); + output += 3; + i += 3; + quote = 0; + continue; + } + } + } + else if (c == '#') { + while (i < len && expr[i] != '\n') { + i++; + } + continue; + } + else if (c == '\'' || c == '"') { + quote = c; + quote_size = i + 2 < len && expr[i + 1] == c && + expr[i + 2] == c ? 3 : 1; + } + result[output] = c; + output++; + i++; + } + return output; +} + int _PyLexer_set_ftstring_expr(struct tok_state* tok, struct token *token, char c) { assert(token != NULL); @@ -16,101 +83,26 @@ _PyLexer_set_ftstring_expr(struct tok_state* tok, struct token *token, char c) { if (!(tok_mode->in_debug || tok_mode->string_kind == TSTRING) || token->metadata) { return 0; } - PyObject *res = NULL; - - // Look for a # character outside of string literals - int hash_detected = 0; - int in_string = 0; - char quote_char = 0; - - for (Py_ssize_t i = 0; i < tok_mode->last_expr_size - tok_mode->last_expr_end; i++) { - char ch = tok_mode->last_expr_buffer[i]; - - // Skip escaped characters - if (ch == '\\') { - i++; - continue; - } - - // Handle quotes - if (ch == '"' || ch == '\'') { - // The following if/else block works becase there is an off number - // of quotes in STRING tokens and the lexer only ever reaches this - // function with valid STRING tokens. - // For example: """hello""" - // First quote: in_string = 1 - // Second quote: in_string = 0 - // Third quote: in_string = 1 - if (!in_string) { - in_string = 1; - quote_char = ch; - } - else if (ch == quote_char) { - in_string = 0; - } - continue; - } - - // Check for # outside strings - if (ch == '#' && !in_string) { - hash_detected = 1; - break; - } + Py_ssize_t expr_len; + const char *expr = _PyLexer_SpanView( + tok, tok_mode->debug_expr, &expr_len); + if (expr == NULL) { + return -1; + } + PyObject *res; + if (memchr(expr, '#', expr_len) == NULL) { + res = PyUnicode_DecodeUTF8(expr, expr_len, NULL); } - // If we found a # character in the expression, we need to handle comments - if (hash_detected) { - // Allocate buffer for processed result - char *result = (char *)PyMem_Malloc((tok_mode->last_expr_size - tok_mode->last_expr_end + 1) * sizeof(char)); - if (!result) { + else { + char *stripped = PyMem_Malloc((size_t)expr_len); + if (stripped == NULL) { + PyErr_NoMemory(); return -1; } - - Py_ssize_t i = 0; // Input position - Py_ssize_t j = 0; // Output position - in_string = 0; // Whether we're in a string - quote_char = 0; // Current string quote char - - // Process each character - while (i < tok_mode->last_expr_size - tok_mode->last_expr_end) { - char ch = tok_mode->last_expr_buffer[i]; - - // Handle string quotes - if (ch == '"' || ch == '\'') { - // See comment above to understand this part - if (!in_string) { - in_string = 1; - quote_char = ch; - } else if (ch == quote_char) { - in_string = 0; - } - result[j++] = ch; - } - // Skip comments - else if (ch == '#' && !in_string) { - while (i < tok_mode->last_expr_size - tok_mode->last_expr_end && - tok_mode->last_expr_buffer[i] != '\n') { - i++; - } - if (i < tok_mode->last_expr_size - tok_mode->last_expr_end) { - result[j++] = '\n'; - } - } - // Copy other chars - else { - result[j++] = ch; - } - i++; - } - - result[j] = '\0'; // Null-terminate the result string - res = PyUnicode_DecodeUTF8(result, j, NULL); - PyMem_Free(result); - } else { - res = PyUnicode_DecodeUTF8( - tok_mode->last_expr_buffer, - tok_mode->last_expr_size - tok_mode->last_expr_end, - NULL - ); + Py_ssize_t stripped_len = strip_expr_comments( + expr, expr_len, stripped); + res = PyUnicode_DecodeUTF8(stripped, stripped_len, NULL); + PyMem_Free(stripped); } if (!res) { @@ -120,59 +112,28 @@ _PyLexer_set_ftstring_expr(struct tok_state* tok, struct token *token, char c) { return 0; } -int +void _PyLexer_update_ftstring_expr(struct tok_state *tok, char cur) { - assert(tok->cur != NULL); - - Py_ssize_t size = strlen(tok->cur); tokenizer_mode *tok_mode = TOK_GET_MODE(tok); switch (cur) { - case 0: - if (!tok_mode->last_expr_buffer || tok_mode->last_expr_end >= 0) { - return 1; - } - char *new_buffer = PyMem_Realloc( - tok_mode->last_expr_buffer, - tok_mode->last_expr_size + size - ); - if (new_buffer == NULL) { - PyMem_Free(tok_mode->last_expr_buffer); - goto error; - } - tok_mode->last_expr_buffer = new_buffer; - strncpy(tok_mode->last_expr_buffer + tok_mode->last_expr_size, tok->cur, size); - tok_mode->last_expr_size += size; - break; case '{': - if (tok_mode->last_expr_buffer != NULL) { - PyMem_Free(tok_mode->last_expr_buffer); - } - tok_mode->last_expr_buffer = PyMem_Malloc(size); - if (tok_mode->last_expr_buffer == NULL) { - goto error; - } - tok_mode->last_expr_size = size; - tok_mode->last_expr_end = -1; - strncpy(tok_mode->last_expr_buffer, tok->cur, size); + tok_mode->debug_expr = (_PyTok_Span){ + current_offset(tok, tok->cur), -1}; break; case '}': case '!': - tok_mode->last_expr_end = strlen(tok->start); + tok_mode->debug_expr.end = current_offset(tok, tok->start); break; case ':': - if (tok_mode->last_expr_end == -1) { - tok_mode->last_expr_end = strlen(tok->start); + if (tok_mode->debug_expr.end < 0) { + tok_mode->debug_expr.end = current_offset(tok, tok->start); } break; default: Py_UNREACHABLE(); } - return 1; -error: - tok->done = E_NOMEM; - return 0; } int @@ -263,14 +224,10 @@ _PyLexer_scan_fstring_start(struct tok_state *tok, struct token *token, int c) the_current_tok->kind = TOK_FSTRING_MODE; the_current_tok->quote = quote; the_current_tok->quote_size = quote_size; - the_current_tok->start = tok->start; - the_current_tok->multi_line_start = tok->line_start; + the_current_tok->start = current_offset(tok, tok->start); + the_current_tok->multi_line_start = current_offset(tok, tok->line_start); the_current_tok->first_line = tok->lineno; - the_current_tok->start_offset = -1; - the_current_tok->multi_line_start_offset = -1; - the_current_tok->last_expr_buffer = NULL; - the_current_tok->last_expr_size = 0; - the_current_tok->last_expr_end = -1; + the_current_tok->debug_expr = (_PyTok_Span){-1, -1}; the_current_tok->in_format_spec = 0; the_current_tok->in_debug = 0; @@ -460,13 +417,6 @@ _PyLexer_get_fstring_mode(struct tok_state *tok, tokenizer_mode* current_tok, st } } - if (current_tok->last_expr_buffer != NULL) { - PyMem_Free(current_tok->last_expr_buffer); - current_tok->last_expr_buffer = NULL; - current_tok->last_expr_size = 0; - current_tok->last_expr_end = -1; - } - p_start = tok->start; p_end = tok->cur; tok->tok_mode_stack_index--; @@ -518,9 +468,9 @@ _PyLexer_get_fstring_mode(struct tok_state *tok, tokenizer_mode* current_tok, st // shift the tok_state's location into // the start of string, and report the error // from the initial quote character - tok->cur = (char *)current_tok->start; - tok->cur++; - tok->line_start = current_tok->multi_line_start; + tok->cur = offset_pointer(tok, current_tok->start) + 1; + tok->line_start = offset_pointer( + tok, current_tok->multi_line_start); int start = tok->lineno; tokenizer_mode *the_current_tok = TOK_GET_MODE(tok); @@ -551,9 +501,7 @@ _PyLexer_get_fstring_mode(struct tok_state *tok, tokenizer_mode* current_tok, st } if (c == '{') { - if (!_PyLexer_update_ftstring_expr(tok, c)) { - return MAKE_TOKEN(ENDMARKER); - } + _PyLexer_update_ftstring_expr(tok, c); int peek = tok_nextc(tok); if (peek != '{' || in_format_spec) { tok_backup(tok, peek); diff --git a/Parser/pegen.c b/Parser/pegen.c index fcec810037e98d..d86dd22444e6a7 100644 --- a/Parser/pegen.c +++ b/Parser/pegen.c @@ -171,18 +171,17 @@ growable_comment_array_deallocate(growable_comment_array *arr) { } static int -_get_keyword_or_name_type(Parser *p, struct token *new_token) +_get_keyword_or_name_type(Parser *p, const char *text, Py_ssize_t length) { - Py_ssize_t name_len = new_token->end_col_offset - new_token->col_offset; - assert(name_len > 0); + assert(length > 0); - if (name_len >= p->n_keyword_lists || - p->keywords[name_len] == NULL || - p->keywords[name_len]->type == -1) { + if (length >= p->n_keyword_lists || + p->keywords[length] == NULL || + p->keywords[length]->type == -1) { return NAME; } - for (KeywordToken *k = p->keywords[name_len]; k != NULL && k->type != -1; k++) { - if (strncmp(k->str, new_token->start, (size_t)name_len) == 0) { + for (KeywordToken *k = p->keywords[length]; k != NULL && k->type != -1; k++) { + if (memcmp(k->str, text, (size_t)length) == 0) { return k->type; } } @@ -193,8 +192,11 @@ static int initialize_token(Parser *p, Token *parser_token, struct token *new_token, int token_type) { assert(parser_token != NULL); - parser_token->type = (token_type == NAME) ? _get_keyword_or_name_type(p, new_token) : token_type; - parser_token->bytes = PyBytes_FromStringAndSize(new_token->start, new_token->end - new_token->start); + Py_ssize_t length; + const char *text = _PyToken_TextView(p->tok, new_token, &length); + parser_token->type = token_type == NAME + ? _get_keyword_or_name_type(p, text, length) : token_type; + parser_token->bytes = PyBytes_FromStringAndSize(text, length); if (parser_token->bytes == NULL) { return -1; } @@ -214,12 +216,14 @@ initialize_token(Parser *p, Token *parser_token, struct token *new_token, int to } parser_token->level = new_token->level; - parser_token->lineno = new_token->lineno; - parser_token->col_offset = p->tok->lineno == p->starting_lineno ? p->starting_col_offset + new_token->col_offset - : new_token->col_offset; - parser_token->end_lineno = new_token->end_lineno; - parser_token->end_col_offset = p->tok->lineno == p->starting_lineno ? p->starting_col_offset + new_token->end_col_offset - : new_token->end_col_offset; + parser_token->lineno = new_token->start_loc.lineno; + parser_token->col_offset = p->tok->lineno == p->starting_lineno + ? p->starting_col_offset + new_token->start_loc.byte_col + : new_token->start_loc.byte_col; + parser_token->end_lineno = new_token->end_loc.lineno; + parser_token->end_col_offset = p->tok->lineno == p->starting_lineno + ? p->starting_col_offset + new_token->end_loc.byte_col + : new_token->end_loc.byte_col; p->fill += 1; @@ -261,13 +265,14 @@ _PyPegen_fill_token(Parser *p) // Record and skip '# type: ignore' comments while (type == TYPE_IGNORE) { - Py_ssize_t len = new_token.end_col_offset - new_token.col_offset; + Py_ssize_t len; + const char *text = _PyToken_TextView(p->tok, &new_token, &len); char *tag = PyMem_Malloc((size_t)len + 1); if (tag == NULL) { PyErr_NoMemory(); goto error; } - strncpy(tag, new_token.start, (size_t)len); + memcpy(tag, text, (size_t)len); tag[len] = '\0'; // Ownership of tag passes to the growable array if (!growable_comment_array_add(&p->type_ignore_comments, p->tok->lineno, tag)) { diff --git a/Parser/tokenizer/cursor.c b/Parser/tokenizer/cursor.c deleted file mode 100644 index 698a26a740fd24..00000000000000 --- a/Parser/tokenizer/cursor.c +++ /dev/null @@ -1,81 +0,0 @@ -#include "Python.h" - -#include "cursor.h" - -static void -set_line(_PyTok_Cursor *cursor, int lineno, _PyTok_Off start, - _PyTok_Off end) -{ - cursor->pos = start; - cursor->line_start = start; - cursor->line_end = end; - cursor->lineno = lineno; -} - -int -_PyTok_CursorSetLine(_PyTok_Cursor *cursor, int lineno) -{ - if (cursor->source == NULL) { - PyErr_SetString(PyExc_SystemError, "cursor has no tokenizer source"); - return -1; - } - const _PyTok_SourceText *source = cursor->source; - if (lineno > 0 && cursor->lineno == lineno - 1 && - lineno <= source->nlines) { - _PyTok_Off start = cursor->line_end; - _PyTok_Off end = source->len; - if (lineno < source->nlines) { - end = _PyTok_SourceFindLineEnd(source, start); - if (end < 0) { - return -1; - } - } - set_line(cursor, lineno, start, end); - return 0; - } - - _PyTok_Line line; - if (_PyTok_SourceLine(source, lineno, &line) < 0) { - return -1; - } - set_line(cursor, lineno, line.start, line.end); - return 0; -} - -int -_PyTok_CursorSetOffset(_PyTok_Cursor *cursor, _PyTok_Off offset) -{ - if (cursor->source == NULL) { - PyErr_SetString(PyExc_SystemError, "cursor has no tokenizer source"); - return -1; - } - const _PyTok_SourceText *source = cursor->source; - int stays_on_line = cursor->lineno > 0 && - offset >= cursor->line_start && offset < cursor->line_end; - if (!stays_on_line && cursor->lineno > 0 && - offset == cursor->line_end && offset == source->len && - (offset == 0 || source->bytes[offset - 1] != '\n')) { - stays_on_line = 1; - } - if (stays_on_line) { - cursor->pos = offset; - return 0; - } - - _PyTok_Loc loc; - if (_PyTok_SourceLocation( - source, offset, _PYTOK_AFFINITY_RIGHT, &loc) < 0) { - return -1; - } - _PyTok_Off start = offset - loc.byte_col; - _PyTok_Off end = source->len; - if (loc.lineno < source->nlines) { - end = _PyTok_SourceFindLineEnd(source, start); - if (end < 0) { - return -1; - } - } - set_line(cursor, loc.lineno, start, end); - cursor->pos = offset; - return 0; -} diff --git a/Parser/tokenizer/cursor.h b/Parser/tokenizer/cursor.h deleted file mode 100644 index d0fd9cf80b77b7..00000000000000 --- a/Parser/tokenizer/cursor.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef Py_TOKENIZER_CURSOR_H -#define Py_TOKENIZER_CURSOR_H - -#include "source.h" - -typedef struct { - /* The source must remain initialized at this address while in use. */ - const _PyTok_SourceText *source; - _PyTok_Off pos; - _PyTok_Off line_start; - _PyTok_Off line_end; - int lineno; -} _PyTok_Cursor; - -/* Move to the start of a 1-based line. Both setters preserve the cursor on - error. */ -PyAPI_FUNC(int) _PyTok_CursorSetLine(_PyTok_Cursor *, int); -/* Move to an offset. A line boundary selects the following line. */ -PyAPI_FUNC(int) _PyTok_CursorSetOffset(_PyTok_Cursor *, _PyTok_Off); - -static inline void -_PyTok_CursorInit(_PyTok_Cursor *cursor, const _PyTok_SourceText *source) -{ - *cursor = (_PyTok_Cursor){ - .source = source, - }; -} - -/* Read one byte from the current line, including its terminating newline. - EOF marks the line boundary, not necessarily the end of the source. It is - also returned if advancing would make the byte column unrepresentable. */ -static inline int -_PyTok_CursorAdvance(_PyTok_Cursor *cursor) -{ - assert(cursor->source != NULL); - assert(cursor->pos >= cursor->line_start); - assert(cursor->pos <= cursor->line_end); - assert(cursor->line_end <= cursor->source->len); - if (cursor->pos >= cursor->line_end) { - return EOF; - } - if (cursor->pos - cursor->line_start >= INT_MAX) { - return EOF; - } - return Py_CHARMASK(cursor->source->bytes[cursor->pos++]); -} - -/* Return the byte at a nonnegative distance within the current line, or EOF - if the distance reaches or crosses the line boundary. */ -static inline int -_PyTok_CursorPeek(const _PyTok_Cursor *cursor, int distance) -{ - assert(cursor->source != NULL); - assert(cursor->pos >= cursor->line_start); - assert(cursor->pos <= cursor->line_end); - assert(cursor->line_end <= cursor->source->len); - assert(distance >= 0); - if (distance < 0 || - distance >= cursor->line_end - cursor->pos) { - return EOF; - } - return Py_CHARMASK(cursor->source->bytes[cursor->pos + distance]); -} - -#endif diff --git a/Parser/tokenizer/reader.c b/Parser/tokenizer/reader.c index 82b824f56374fc..f93090dd30e343 100644 --- a/Parser/tokenizer/reader.c +++ b/Parser/tokenizer/reader.c @@ -13,6 +13,12 @@ # include #endif +static inline int +reader_is_streaming(_PyTok_ReaderKind kind) +{ + return kind == _PYTOK_READER_FILE || kind == _PYTOK_READER_READLINE; +} + void _PyTok_ReaderFree(struct tok_state *tok) { @@ -28,14 +34,27 @@ _PyTok_ReaderFree(struct tok_state *tok) } PyMem_Free(reader->file_buffer); PyMem_Free(reader->decoded); - if (reader->kind != _PYTOK_READER_PREPARED) { + if (reader_is_streaming(reader->kind)) { PyMem_Free(tok->buf); - tok->buf = NULL; } + tok->buf = NULL; PyMem_Free(reader); tok->reader = NULL; } +static int +resize_buffer(char **buffer, Py_ssize_t *capacity, Py_ssize_t new_capacity) +{ + char *resized = PyMem_Realloc(*buffer, new_capacity); + if (resized == NULL) { + PyErr_NoMemory(); + return -1; + } + *buffer = resized; + *capacity = new_capacity; + return 0; +} + static int reserve_buffer(char **buffer, Py_ssize_t *capacity, Py_ssize_t needed) { @@ -50,13 +69,31 @@ reserve_buffer(char **buffer, Py_ssize_t *capacity, Py_ssize_t needed) } cap *= 2; } - char *resized = PyMem_Realloc(*buffer, cap); - if (resized == NULL) { - PyErr_NoMemory(); + return resize_buffer(buffer, capacity, cap); +} + +static int +reserve_input_buffer(struct tok_state *tok, Py_ssize_t needed) +{ + _PyTok_Reader *reader = tok->reader; + if (needed <= reader->input_buffer_cap) { + return 0; + } + assert(tok->buf != NULL); + assert(tok->cur >= tok->buf && tok->cur <= tok->inp); + assert(tok->inp - tok->buf <= reader->input_buffer_cap); + Py_ssize_t used = tok->inp - tok->buf; + Py_ssize_t growth = used >> 1; + Py_ssize_t capacity = used > PY_SSIZE_T_MAX - growth + ? needed : Py_MAX(needed, used + growth); + _PyLexer_BufferSnapshot snapshot; + _PyLexer_SnapshotBuffer(tok, tok->buf, &snapshot); + char *buffer = tok->buf; + if (resize_buffer( + &buffer, &reader->input_buffer_cap, capacity) < 0) { return -1; } - *buffer = resized; - *capacity = cap; + _PyLexer_RestoreBuffer(tok, buffer, &snapshot); return 0; } @@ -529,19 +566,31 @@ reader_next(struct tok_state *tok, _PyTok_Chunk *chunk) Py_UNREACHABLE(); } +static void +reset_streaming_buffer(struct tok_state *tok) +{ + assert(tok->buf != NULL); + assert(tok->cur >= tok->buf && tok->cur <= tok->inp); + Py_ssize_t consumed = tok->inp - tok->buf; + assert(tok->buf_offset <= PY_SSIZE_T_MAX - consumed); + tok->buf_offset += consumed; + tok->cur = tok->inp = tok->buf; +} + int _PyTok_ReaderUnderflow(struct tok_state *tok) { - int prepared = tok->reader->kind == _PYTOK_READER_PREPARED; + _PyTok_ReaderKind kind = tok->reader->kind; + int prepared = kind == _PYTOK_READER_PREPARED; + int streaming = reader_is_streaming(kind); int reset_buffer = !prepared && tok->start == NULL && !INSIDE_FSTRING(tok); - if (reset_buffer && tok->reader->kind != _PYTOK_READER_INTERACTIVE) { - tok->cur = tok->inp = tok->buf; - } - _PyTok_Chunk chunk; _PyTok_ReadResult result = reader_next(tok, &chunk); if (result != _PYTOK_READ_LINE) { + if (reset_buffer && streaming) { + reset_streaming_buffer(tok); + } if (result == _PYTOK_READ_EOF) { tok->done = E_EOF; } @@ -558,34 +607,68 @@ _PyTok_ReaderUnderflow(struct tok_state *tok) ? E_NOMEM : E_ERROR; } } - if (tok->reader->kind == _PYTOK_READER_INTERACTIVE && + if (kind == _PYTOK_READER_INTERACTIVE && result != _PYTOK_READ_STOPPED) { PySys_WriteStderr("\n"); } return 0; } - Py_ssize_t copy_len = chunk.len; - if (tok->reader->kind == _PYTOK_READER_INTERACTIVE && + Py_ssize_t scan_len = chunk.len; + if (kind == _PYTOK_READER_INTERACTIVE && chunk.implicit_newline) { - copy_len--; + scan_len--; } - if (reset_buffer && tok->reader->kind == _PYTOK_READER_INTERACTIVE) { - tok->cur = tok->inp = tok->buf; - } - if (!prepared && !_PyLexer_tok_reserve_buf(tok, copy_len + 1)) { - _PyTok_ChunkClear(&chunk); - tok->input_error = 1; - return 0; + if (streaming) { + if (reset_buffer) { + reset_streaming_buffer(tok); + } + Py_ssize_t used = tok->inp - tok->buf; + int overflow = scan_len > PY_SSIZE_T_MAX - used - 1 || + tok->buf_offset > PY_SSIZE_T_MAX - used - scan_len; + if (overflow) { + PyErr_NoMemory(); + } + if (overflow || reserve_input_buffer(tok, used + scan_len + 1) < 0) { + _PyTok_ChunkClear(&chunk); + tok->done = E_NOMEM; + tok->input_error = 1; + return 0; + } + memcpy(tok->inp, chunk.data, (size_t)scan_len); + tok->inp += scan_len; + *tok->inp = '\0'; } - if (tok->reader->kind == _PYTOK_READER_INTERACTIVE && - _PyTok_SourceAppendLine(&tok->source, chunk.data, chunk.len, - chunk.implicit_newline) < 0) { - _PyTok_ChunkClear(&chunk); - tok->done = PyErr_ExceptionMatches(PyExc_MemoryError) - ? E_NOMEM : E_ERROR; - tok->input_error = 1; - return 0; + else if (!prepared) { + int source_will_grow = + chunk.len > tok->source.cap - tok->source.len - 1; + _PyLexer_BufferSnapshot snapshot; + if (!reset_buffer && source_will_grow) { + _PyLexer_SnapshotBuffer( + tok, tok->source.bytes, &snapshot); + } + _PyTok_Off source_start = _PyTok_SourceAppendLine( + &tok->source, chunk.data, chunk.len, + chunk.implicit_newline); + if (source_start < 0) { + _PyTok_ChunkClear(&chunk); + tok->done = PyErr_ExceptionMatches(PyExc_MemoryError) + ? E_NOMEM : E_ERROR; + tok->input_error = 1; + return 0; + } + if (reset_buffer) { + tok->buf = tok->cur = tok->source.bytes + source_start; + tok->buf_offset = source_start; + tok->line_start = tok->buf; + tok->start = NULL; + tok->multi_line_start = NULL; + } + else if (source_will_grow) { + _PyLexer_RestoreBuffer( + tok, tok->source.bytes, &snapshot); + } + tok->inp = tok->source.bytes + source_start + scan_len; } if (tok->fp_interactive) { tok->interactive_src_start = tok->source.bytes; @@ -594,24 +677,14 @@ _PyTok_ReaderUnderflow(struct tok_state *tok) if (prepared) { if (tok->start == NULL) { tok->buf = tok->cur; + tok->buf_offset = chunk.data - tok->source.bytes; } tok->inp = chunk.data + chunk.len; } - else { - memcpy(tok->inp, chunk.data, (size_t)copy_len); - tok->inp += copy_len; - *tok->inp = '\0'; - } tok->implicit_newline = chunk.implicit_newline; - if (!prepared && tok->tok_mode_stack_index && - !_PyLexer_update_ftstring_expr(tok, 0)) { - _PyTok_ChunkClear(&chunk); - tok->input_error = 1; - return 0; - } ADVANCE_LINENO(); - if (tok->reader->kind == _PYTOK_READER_FILE && + if (kind == _PYTOK_READER_FILE && (tok->encoding == NULL || strcmp(tok->encoding, "utf-8") == 0) && !_PyTokenizer_ensure_utf8(tok->cur, tok, tok->lineno)) { _PyTok_ChunkClear(&chunk); @@ -639,14 +712,15 @@ tokenizer_new_with_reader(_PyTok_ReaderKind kind) if (kind == _PYTOK_READER_PREPARED) { return tok; } - tok->buf = PyMem_Malloc(BUFSIZ); - if (tok->buf == NULL) { - PyErr_NoMemory(); - _PyTokenizer_Free(tok); - return NULL; + if (reader_is_streaming(kind)) { + if (reserve_buffer( + &tok->buf, &tok->reader->input_buffer_cap, BUFSIZ) < 0) { + _PyTokenizer_Free(tok); + return NULL; + } + tok->cur = tok->inp = tok->buf; + tok->buf[0] = '\0'; } - tok->cur = tok->inp = tok->buf; - tok->end = tok->buf + BUFSIZ; return tok; } @@ -664,7 +738,6 @@ tokenizer_from_string(const char *input, int utf8_only, int exec_input, return NULL; } tok->buf = tok->cur = tok->inp = tok->str; - tok->end = tok->buf; return tok; } diff --git a/Parser/tokenizer/reader_internal.h b/Parser/tokenizer/reader_internal.h index 121d0f96f6698a..49a6f04ec60af2 100644 --- a/Parser/tokenizer/reader_internal.h +++ b/Parser/tokenizer/reader_internal.h @@ -44,6 +44,8 @@ typedef struct _PyTok_Reader { PyObject *decoder; const char *nextprompt; + Py_ssize_t input_buffer_cap; + char *file_buffer; Py_ssize_t file_buffer_cap; _PyTok_Chunk prefetched_lines[2]; diff --git a/Parser/tokenizer/source.c b/Parser/tokenizer/source.c index c0f7925e33f8b9..e876be6e002651 100644 --- a/Parser/tokenizer/source.c +++ b/Parser/tokenizer/source.c @@ -2,8 +2,6 @@ #include "source.h" -#define LINE_CHECKPOINT_INTERVAL 256 - void _PyTok_SourceInit(_PyTok_SourceText *source) { @@ -14,7 +12,6 @@ void _PyTok_SourceClear(_PyTok_SourceText *source) { PyMem_Free(source->bytes); - PyMem_Free(source->line_checkpoints); PyMem_Free(source->implicit_lines); _PyTok_SourceInit(source); } @@ -58,34 +55,6 @@ reserve_bytes(_PyTok_SourceText *source, Py_ssize_t needed) return 0; } -static int -reserve_checkpoints(_PyTok_SourceText *source, int needed) -{ - if (needed <= source->checkpoints_cap) { - return 0; - } - int cap; - if (source->checkpoints_cap == 0) { - cap = 16; - } - else if (source->checkpoints_cap <= INT_MAX / 2) { - cap = source->checkpoints_cap * 2; - } - else { - PyErr_NoMemory(); - return -1; - } - _PyTok_Off *checkpoints = source->line_checkpoints; - PyMem_Resize(checkpoints, _PyTok_Off, cap); - if (checkpoints == NULL) { - PyErr_NoMemory(); - return -1; - } - source->line_checkpoints = checkpoints; - source->checkpoints_cap = cap; - return 0; -} - static int reserve_implicit_lines(_PyTok_SourceText *source, int nlines) { @@ -148,11 +117,7 @@ _PyTok_SourceAppendLine(_PyTok_SourceText *source, const char *bytes, return -1; } int nlines = source->nlines + 1; - int checkpoint = ((nlines - 1) % LINE_CHECKPOINT_INTERVAL) == 0; - int checkpoint_count = (nlines - 1) / LINE_CHECKPOINT_INTERVAL + 1; - if ((checkpoint && - reserve_checkpoints(source, checkpoint_count) < 0) || - (implicit_newline && reserve_implicit_lines(source, nlines) < 0) || + if ((implicit_newline && reserve_implicit_lines(source, nlines) < 0) || reserve_bytes(source, source->len + len + 1) < 0) { return -1; } @@ -161,9 +126,6 @@ _PyTok_SourceAppendLine(_PyTok_SourceText *source, const char *bytes, memcpy(source->bytes + start, bytes, len); source->len += len; source->bytes[source->len] = '\0'; - if (checkpoint) { - source->line_checkpoints[checkpoint_count - 1] = start; - } if (implicit_newline) { source->implicit_lines[(nlines - 1) / 8] |= (unsigned char)(1U << ((nlines - 1) & 7)); @@ -194,121 +156,3 @@ _PyTok_SourceLineIsImplicit(const _PyTok_SourceText *source, int lineno) return (source->implicit_lines[(lineno - 1) / 8] >> ((lineno - 1) & 7)) & 1; } - -static int -source_ends_in_newline(const _PyTok_SourceText *source) -{ - return source->len > 0 && source->bytes[source->len - 1] == '\n'; -} - -static int -eof_lineno(const _PyTok_SourceText *source) -{ - if (source->nlines == 0) { - return 1; - } - return source->nlines + source_ends_in_newline(source); -} - -int -_PyTok_SourceLine(const _PyTok_SourceText *source, int lineno, - _PyTok_Line *line) -{ - if (line == NULL || lineno < 1 || lineno > eof_lineno(source)) { - PyErr_SetString(PyExc_SystemError, "invalid tokenizer source line"); - return -1; - } - if (lineno > source->nlines) { - *line = (_PyTok_Line){ - .start = source->len, - .end = source->len, - }; - return 0; - } - - int checkpoint = (lineno - 1) / LINE_CHECKPOINT_INTERVAL; - int current = checkpoint * LINE_CHECKPOINT_INTERVAL + 1; - _PyTok_Off start = source->line_checkpoints[checkpoint]; - while (current < lineno) { - start = _PyTok_SourceFindLineEnd(source, start); - if (start < 0) { - return -1; - } - current++; - } - _PyTok_Off end = source->len; - if (lineno < source->nlines) { - end = _PyTok_SourceFindLineEnd(source, start); - if (end < 0) { - return -1; - } - } - *line = (_PyTok_Line){ - .start = start, - .end = end, - .implicit_newline = _PyTok_SourceLineIsImplicit(source, lineno), - .contains_nul = memchr( - source->bytes + start, 0, end - start) != NULL, - }; - return 0; -} - -int -_PyTok_SourceLocation(const _PyTok_SourceText *source, _PyTok_Off offset, - _PyTok_Affinity affinity, _PyTok_Loc *loc) -{ - if (offset < 0 || offset > source->len || loc == NULL || - (affinity != _PYTOK_AFFINITY_LEFT && - affinity != _PYTOK_AFFINITY_RIGHT)) { - PyErr_SetString(PyExc_SystemError, "invalid tokenizer source offset"); - return -1; - } - if (source->nlines == 0 || - (offset == source->len && source_ends_in_newline(source) && - affinity == _PYTOK_AFFINITY_RIGHT)) { - *loc = (_PyTok_Loc){eof_lineno(source), 0}; - return 0; - } - - _PyTok_Off key = offset; - if (affinity == _PYTOK_AFFINITY_LEFT && key > 0) { - key--; - } - int low = 0; - int high = (source->nlines - 1) / LINE_CHECKPOINT_INTERVAL + 1; - while (low < high) { - int middle = low + (high - low) / 2; - if (source->line_checkpoints[middle] <= key) { - low = middle + 1; - } - else { - high = middle; - } - } - int checkpoint = low - 1; - if (checkpoint < 0) { - PyErr_SetString(PyExc_SystemError, "corrupt tokenizer source line index"); - return -1; - } - int lineno = checkpoint * LINE_CHECKPOINT_INTERVAL + 1; - _PyTok_Off start = source->line_checkpoints[checkpoint]; - while (lineno < source->nlines) { - _PyTok_Off end = _PyTok_SourceFindLineEnd(source, start); - if (end < 0) { - return -1; - } - if (offset < end || - (offset == end && affinity == _PYTOK_AFFINITY_LEFT)) { - break; - } - start = end; - lineno++; - } - _PyTok_Off byte_col = offset - start; - if (byte_col > INT_MAX) { - PyErr_SetString(PyExc_OverflowError, "tokenizer column is too large"); - return -1; - } - *loc = (_PyTok_Loc){lineno, (int)byte_col}; - return 0; -} diff --git a/Parser/tokenizer/source.h b/Parser/tokenizer/source.h index b42ecda1b31aa5..fac17183ceb734 100644 --- a/Parser/tokenizer/source.h +++ b/Parser/tokenizer/source.h @@ -5,7 +5,8 @@ typedef Py_ssize_t _PyTok_Off; -/* Half-open byte offsets into a _PyTok_SourceText. */ +/* Spans use half-open logical byte offsets into decoded input. Their backing + storage may retain only the current input window. */ typedef struct { _PyTok_Off start; _PyTok_Off end; @@ -17,32 +18,17 @@ typedef struct { int byte_col; } _PyTok_Loc; -typedef enum { - _PYTOK_AFFINITY_LEFT, - _PYTOK_AFFINITY_RIGHT, -} _PyTok_Affinity; - -/* The half-open range includes the terminating newline when present. */ -typedef struct { - _PyTok_Off start; - _PyTok_Off end; - unsigned implicit_newline : 1; - unsigned contains_nul : 1; -} _PyTok_Line; - typedef struct { char *bytes; _PyTok_Off len; _PyTok_Off cap; - _PyTok_Off *line_checkpoints; unsigned char *implicit_lines; int nlines; - int checkpoints_cap; Py_ssize_t implicit_cap; } _PyTok_SourceText; PyAPI_FUNC(void) _PyTok_SourceInit(_PyTok_SourceText *); -/* Clear invalidates all cursors, spans, and views for the source. */ +/* Clear invalidates all spans and views for the source. */ PyAPI_FUNC(void) _PyTok_SourceClear(_PyTok_SourceText *); /* Append one nonempty logical line and return its start offset. The input may contain one newline, as its final byte. An unterminated line must be the @@ -54,17 +40,9 @@ PyAPI_FUNC(_PyTok_Off) _PyTok_SourceAppendLine( /* The returned view is invalidated by SourceAppendLine and SourceClear. */ PyAPI_FUNC(const char *) _PyTok_SourceSpanView( const _PyTok_SourceText *, _PyTok_Span, Py_ssize_t *); -/* Look up a 1-based line. Empty and newline-terminated sources have an empty - virtual line at EOF. */ -PyAPI_FUNC(int) _PyTok_SourceLine( - const _PyTok_SourceText *, int, _PyTok_Line *); /* Return false for invalid line numbers and the virtual EOF line. */ PyAPI_FUNC(int) _PyTok_SourceLineIsImplicit( const _PyTok_SourceText *, int); -/* At a line boundary, left affinity selects the preceding line at its end; - right affinity selects the following line at byte column zero. */ -PyAPI_FUNC(int) _PyTok_SourceLocation( - const _PyTok_SourceText *, _PyTok_Off, _PyTok_Affinity, _PyTok_Loc *); static inline _PyTok_Span _PyTok_SpanFromBounds(_PyTok_Off start, _PyTok_Off end) @@ -78,22 +56,4 @@ _PyTok_SpanIsValid(_PyTok_Span span) return span.start >= 0 && span.end >= span.start; } -static inline _PyTok_Off -_PyTok_SourceFindLineEnd(const _PyTok_SourceText *source, _PyTok_Off start) -{ - if (source->bytes == NULL || start < 0 || start >= source->len) { - PyErr_SetString(PyExc_SystemError, - "corrupt tokenizer source line index"); - return -1; - } - const char *newline = memchr( - source->bytes + start, '\n', source->len - start); - if (newline == NULL) { - PyErr_SetString(PyExc_SystemError, - "corrupt tokenizer source line index"); - return -1; - } - return newline - source->bytes + 1; -} - #endif diff --git a/Python/Python-tokenize.c b/Python/Python-tokenize.c index 762b7b3e4c8d71..eee1fc86ded781 100644 --- a/Python/Python-tokenize.c +++ b/Python/Python-tokenize.c @@ -203,14 +203,15 @@ _get_current_line(tokenizeriterobject *it, const char *line_start, Py_ssize_t si } static void -_get_col_offsets(tokenizeriterobject *it, struct token token, const char *line_start, - PyObject *line, int line_changed, Py_ssize_t lineno, Py_ssize_t end_lineno, +_get_col_offsets(tokenizeriterobject *it, const char *token_start, + const char *token_end, const char *line_start, PyObject *line, + int line_changed, Py_ssize_t lineno, Py_ssize_t end_lineno, Py_ssize_t *col_offset, Py_ssize_t *end_col_offset) { _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(it); Py_ssize_t byte_offset = -1; - if (token.start != NULL && token.start >= line_start) { - byte_offset = token.start - line_start; + if (token_start != NULL && token_start >= line_start) { + byte_offset = token_start - line_start; if (line_changed) { *col_offset = _PyPegen_byte_offset_to_character_offset_line(line, 0, byte_offset); it->byte_col_offset_diff = byte_offset - *col_offset; @@ -220,15 +221,13 @@ _get_col_offsets(tokenizeriterobject *it, struct token token, const char *line_s } } - if (token.end != NULL && token.end >= it->tok->line_start) { - Py_ssize_t end_byte_offset = token.end - it->tok->line_start; + if (token_end != NULL && token_end >= it->tok->line_start) { + Py_ssize_t end_byte_offset = token_end - it->tok->line_start; if (lineno == end_lineno) { - // If the whole token is at the same line, we can just use the token.start - // buffer for figuring out the new column offset, since using line is not - // performant for very long lines. + // Avoid rescanning the prefix of a very long line. Py_ssize_t token_col_offset = _PyPegen_byte_offset_to_character_offset_line(line, byte_offset, end_byte_offset); *end_col_offset = *col_offset + token_col_offset; - it->byte_col_offset_diff += token.end - token.start - token_col_offset; + it->byte_col_offset_diff += token_end - token_start - token_col_offset; } else { *end_col_offset = _PyPegen_byte_offset_to_character_offset_raw(it->tok->line_start, end_byte_offset); @@ -263,12 +262,18 @@ tokenizeriter_next(PyObject *op) it->done = 1; goto exit; } - PyObject *str = NULL; - if (token.start == NULL || token.end == NULL) { + const char *token_start = NULL; + const char *token_end = NULL; + PyObject *str; + if (!_PyTok_SpanIsValid(token.span)) { str = Py_GetConstant(Py_CONSTANT_EMPTY_STR); } else { - str = PyUnicode_FromStringAndSize(token.start, token.end - token.start); + Py_ssize_t token_length; + token_start = _PyToken_TextView( + it->tok, &token, &token_length); + token_end = token_start + token_length; + str = PyUnicode_FromStringAndSize(token_start, token_length); } if (str == NULL) { goto exit; @@ -297,11 +302,11 @@ tokenizeriter_next(PyObject *op) goto exit; } - Py_ssize_t lineno = ISSTRINGLIT(type) ? it->tok->first_lineno : it->tok->lineno; - Py_ssize_t end_lineno = it->tok->lineno; + Py_ssize_t lineno = token.start_loc.lineno; + Py_ssize_t end_lineno = token.end_loc.lineno; Py_ssize_t col_offset = -1; Py_ssize_t end_col_offset = -1; - _get_col_offsets(it, token, line_start, line, line_changed, + _get_col_offsets(it, token_start, token_end, line_start, line, line_changed, lineno, end_lineno, &col_offset, &end_col_offset); if (it->tok->tok_extra_tokens) { @@ -317,7 +322,8 @@ tokenizeriter_next(PyObject *op) else if (type == NEWLINE) { Py_DECREF(str); if (!it->tok->implicit_newline) { - if (it->tok->start[0] == '\r') { + assert(token_start != NULL); + if (token_start[0] == '\r') { str = PyUnicode_FromString("\r\n"); } else { str = PyUnicode_FromString("\n");