https://github.com/python/cpython/commit/f892b11f22f513c7297e403535fa8511fd0bb0fe
commit: f892b11f22f513c7297e403535fa8511fd0bb0fe
branch: main
author: Pablo Galindo Salgado <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-10-13T13:01:17Z
summary:
gh-153569: Simplify tokenizer source views and reader ownership (#158849)
Move source access and logical spans into the tokenizer source API, and keep
input and encoding ownership in the reader. Export the incremental decoder
helper for parser generator extensions.
files:
M Include/internal/pycore_codecs.h
M Parser/lexer/lexer.c
M Parser/lexer/lexer_internal.h
M Parser/lexer/state.c
M Parser/lexer/state.h
M Parser/lexer/string.c
M Parser/tokenizer/api.c
M Parser/tokenizer/decoder.c
M Parser/tokenizer/helpers.c
M Parser/tokenizer/reader.c
M Parser/tokenizer/reader_internal.h
M Parser/tokenizer/source.h
M Parser/tokenizer/tokenizer.h
M Python/Python-tokenize.c
diff --git a/Include/internal/pycore_codecs.h b/Include/internal/pycore_codecs.h
index bfa10eadf735731..4f8ee383efb22ad 100644
--- a/Include/internal/pycore_codecs.h
+++ b/Include/internal/pycore_codecs.h
@@ -59,10 +59,9 @@ extern PyObject* _PyCodec_DecodeText(
const char *encoding,
const char *errors);
-/* These two aren't actually text encoding specific, but _io.TextIOWrapper
- * is the only current API consumer.
- */
-extern PyObject* _PyCodecInfo_GetIncrementalDecoder(
+/* Helpers to create incremental codecs from a codec info object. */
+// Export for test_peg_generator.
+PyAPI_FUNC(PyObject*) _PyCodecInfo_GetIncrementalDecoder(
PyObject *codec_info,
const char *errors);
diff --git a/Parser/lexer/lexer.c b/Parser/lexer/lexer.c
index a4fdb9484abd8de..83071e9e0b8ddf0 100644
--- a/Parser/lexer/lexer.c
+++ b/Parser/lexer/lexer.c
@@ -13,12 +13,6 @@
tokenizing. */
static const char* type_comment_prefix = "# type: ";
-static inline int
-contains_null_bytes(const char* str, size_t size)
-{
- return memchr(str, 0, size) != NULL;
-}
-
int
_PyLexer_refill(struct tok_state *tok)
{
@@ -29,8 +23,9 @@ _PyLexer_refill(struct tok_state *tok)
#if defined(Py_DEBUG)
if (tok->debug) {
fprintf(stderr, "line[%d] = ", tok->lineno);
- _PyTokenizer_print_escape(stderr, _PyLexer_BufferPointer(tok,
tok->cur),
- tok->inp - tok->cur);
+ _PyTokenizer_print_escape(
+ stderr, _PyTok_SourcePointer(&tok->source, tok->cur),
+ tok->inp - tok->cur);
fprintf(stderr, " tok->done = %d\n", tok->done);
}
#endif
@@ -39,8 +34,8 @@ _PyLexer_refill(struct tok_state *tok)
return 0;
}
tok->line_start = tok->cur;
- if (contains_null_bytes(_PyLexer_BufferPointer(tok, tok->line_start),
- tok->inp - tok->line_start)) {
+ _PyTok_Span line = {tok->line_start, tok->inp};
+ if (_PyTok_SourceFindByte(&tok->source, line, '\0') >= 0) {
_PyTokenizer_syntaxerror(tok, "source code cannot contain null bytes");
tok->cur = tok->inp;
return 0;
@@ -56,7 +51,7 @@ _PyLexer_backup(struct tok_state *tok, int c)
if (--tok->cur < tok->buf_offset) {
Py_FatalError("tokenizer beginning of buffer");
}
- if ((int)(unsigned char)*_PyLexer_BufferPointer(tok, tok->cur) !=
Py_CHARMASK(c)) {
+ if (_PyTok_SourceByte(&tok->source, tok->cur) != Py_CHARMASK(c)) {
Py_FatalError("tok_backup: wrong character");
}
}
@@ -74,7 +69,8 @@ verify_identifier(struct tok_state *tok)
PyObject *s;
if (tok_failed(tok))
return 0;
- s = PyUnicode_DecodeUTF8(_PyLexer_BufferPointer(tok, tok->start), tok->cur
- tok->start, NULL);
+ s = PyUnicode_DecodeUTF8(_PyTok_SourcePointer(&tok->source, tok->start),
+ tok->cur - tok->start, NULL);
if (s == NULL) {
if (PyErr_ExceptionMatches(PyExc_UnicodeDecodeError)) {
tok->done = E_DECODE;
@@ -105,13 +101,13 @@ verify_identifier(struct tok_state *tok)
Py_DECREF(s);
if (Py_UNICODE_ISPRINTABLE(ch)) {
_PyTokenizer_syntaxerror_at(
- tok, _PyLexer_BufferPointer(tok, tok->line_start),
+ tok, _PyTok_SourcePointer(&tok->source, tok->line_start),
error_cursor - tok->line_start, tok->lineno, -1, -1,
"invalid character '%c' (U+%04X)", ch, ch);
}
else {
_PyTokenizer_syntaxerror_at(
- tok, _PyLexer_BufferPointer(tok, tok->line_start),
+ tok, _PyTok_SourcePointer(&tok->source, tok->line_start),
error_cursor - tok->line_start, tok->lineno, -1, -1,
"invalid non-printable character U+%04X", ch);
}
@@ -172,10 +168,6 @@ _PyLexer_get_normal(struct tok_state *tok, ftstring_state
*current, struct token
/* Skip comment, unless it's a type comment */
if (c == '#') {
- const char* p = NULL;
- const char *prefix, *type_start;
- int current_starting_col_offset;
-
while (c != EOF && c != '\n' && c != '\r') {
c = tok_nextc(tok);
}
@@ -192,23 +184,20 @@ _PyLexer_get_normal(struct tok_state *tok, ftstring_state
*current, struct token
}
}
- if (tok->tok_extra_tokens) {
- p = _PyLexer_BufferPointer(tok, tok->start);
- }
-
+ _PyTok_Off comment = tok->start;
if (tok->type_comments) {
- p = _PyLexer_BufferPointer(tok, tok->start);
- current_starting_col_offset = tok->start_loc.byte_col;
- prefix = type_comment_prefix;
- while (*prefix && p < _PyLexer_BufferPointer(tok, tok->cur)) {
+ const char *prefix = type_comment_prefix;
+ while (*prefix && comment < tok->cur) {
if (*prefix == ' ') {
- while (*p == ' ' || *p == '\t') {
- p++;
- current_starting_col_offset++;
+ while (comment < tok->cur) {
+ int ch = _PyTok_SourceByte(&tok->source, comment);
+ if (ch != ' ' && ch != '\t') {
+ break;
+ }
+ comment++;
}
- } else if (*prefix == *p) {
- p++;
- current_starting_col_offset++;
+ } else if (*prefix == _PyTok_SourceByte(&tok->source,
comment)) {
+ comment++;
} else {
break;
}
@@ -218,35 +207,26 @@ _PyLexer_get_normal(struct tok_state *tok, ftstring_state
*current, struct token
/* This is a type comment if we matched all of
type_comment_prefix. */
if (!*prefix) {
- int is_type_ignore = 1;
- // +6 in order to skip the word 'ignore'
- const char *ignore_end = p + 6;
- const int ignore_end_col_offset = current_starting_col_offset
+ 6;
tok_backup(tok, c); /* don't eat the newline or EOF */
-
- type_start = p;
-
/* A TYPE_IGNORE is "type: ignore" followed by the end of the
token
* or anything ASCII and non-alphanumeric. */
- is_type_ignore = (
- _PyLexer_BufferPointer(tok, tok->cur) >= ignore_end &&
memcmp(p, "ignore", 6) == 0
- && !(_PyLexer_BufferPointer(tok, tok->cur) > ignore_end
- && ((unsigned char)ignore_end[0] >= 128 ||
Py_ISALNUM(ignore_end[0]))));
+ int is_type_ignore = tok->cur - comment >= 6 &&
+ memcmp(_PyTok_SourcePointer(&tok->source, comment),
+ "ignore", 6) == 0;
+ if (is_type_ignore && comment + 6 < tok->cur) {
+ int ch = _PyTok_SourceByte(&tok->source, comment + 6);
+ is_type_ignore = ch < 128 && !Py_ISALNUM(ch);
+ }
int type = is_type_ignore ? TYPE_IGNORE : TYPE_COMMENT;
- int start_col_offset = is_type_ignore
- ? ignore_end_col_offset : current_starting_col_offset;
+ p_start = comment + (is_type_ignore ? 6 : 0);
p_end = tok->cur;
- if (is_type_ignore) {
- p_start = _PyLexer_BufferOffset(tok, ignore_end);
-
- /* If this type ignore is the only thing on the line,
consume the newline also. */
- if (blankline) {
- tok_nextc(tok);
- tok->layout.at_bol = 1;
- }
- } else {
- p_start = _PyLexer_BufferOffset(tok, type_start);
+ int start_col_offset = tok->start_loc.byte_col +
+ (int)(p_start - tok->start);
+ /* Consume the newline after a standalone type ignore. */
+ if (is_type_ignore && blankline) {
+ tok_nextc(tok);
+ tok->layout.at_bol = 1;
}
_PyLexer_token_setup(tok, token, type, p_start, p_end);
token->start_loc = (_PyTok_Loc){tok->lineno, start_col_offset};
@@ -257,7 +237,7 @@ _PyLexer_get_normal(struct tok_state *tok, ftstring_state
*current, struct token
}
if (tok->tok_extra_tokens) {
tok_backup(tok, c); /* don't eat the newline or EOF */
- p_start = _PyLexer_BufferOffset(tok, p);
+ p_start = comment;
p_end = tok->cur;
tok->layout.comment_newline = blankline;
return MAKE_TOKEN(COMMENT);
diff --git a/Parser/lexer/lexer_internal.h b/Parser/lexer/lexer_internal.h
index 652512a3e88c22d..922a23bd97ef620 100644
--- a/Parser/lexer/lexer_internal.h
+++ b/Parser/lexer/lexer_internal.h
@@ -40,14 +40,11 @@ tok_nextc(struct tok_state *tok)
return EOF;
}
}
- assert(tok->cur >= tok->source.base_offset);
- assert(tok->cur - tok->source.base_offset < tok->source.len);
if (tok->cur - tok->line_start >= INT_MAX) {
tok->done = E_COLUMNOVERFLOW;
return EOF;
}
- return Py_CHARMASK(
- tok->source.bytes[tok->cur++ - tok->source.base_offset]);
+ return _PyTok_SourceByte(&tok->source, tok->cur++);
}
/* Return -1 on error, otherwise whether the line is blank. */
diff --git a/Parser/lexer/state.c b/Parser/lexer/state.c
index 75ff26f16d47ba7..71f1c86461c4bfb 100644
--- a/Parser/lexer/state.c
+++ b/Parser/lexer/state.c
@@ -58,9 +58,6 @@ _PyLexer_PopFTString(struct tok_state *tok)
void
_PyTokenizer_Free(struct tok_state *tok)
{
- if (tok->encoding != NULL) {
- PyMem_Free(tok->encoding);
- }
Py_XDECREF(tok->filename);
Py_XDECREF(tok->module);
_PyTok_ReaderFree(tok);
diff --git a/Parser/lexer/state.h b/Parser/lexer/state.h
index 3b5d47e12a6c38f..51ad278b13c40db 100644
--- a/Parser/lexer/state.h
+++ b/Parser/lexer/state.h
@@ -80,7 +80,6 @@ struct tok_state {
_PyTok_SourceText source;
int done; /* E_OK normally, E_EOF at EOF, otherwise error code */
/* NB If done != E_OK, cur must be == inp!!! */
- FILE *fp; /* Rest of input; NULL if tokenizing a string */
lexer_layout_state layout;
int lineno; /* Current line number */
_PyTok_Loc start_loc;
@@ -92,9 +91,6 @@ struct tok_state {
int parencolstack[MAXLEVEL];
PyObject *filename;
PyObject *module;
- /* Stuff for PEP 0263 */
- char *encoding; /* Source encoding. */
-
struct _PyTok_Reader *reader;
int type_comments; /* Whether to look for type comments */
@@ -135,33 +131,6 @@ _PyLexer_FTStringBracketDepth(const struct tok_state *tok,
return tok->level - state->paren_level;
}
-static inline _PyTok_Off
-_PyLexer_BufferOffset(const struct tok_state *tok, const char *position)
-{
- const char *base = _PyTok_SourceData(&tok->source);
- assert(position >= base && position <= base + tok->source.len);
- return tok->source.base_offset + (position - base);
-}
-
-static inline const char *
-_PyLexer_BufferPointer(const struct tok_state *tok, _PyTok_Off offset)
-{
- assert(offset >= tok->source.base_offset);
- assert(offset - tok->source.base_offset <= tok->source.len);
- return _PyTok_SourceData(&tok->source) + (offset -
tok->source.base_offset);
-}
-
-static inline const char *
-_PyLexer_BufferSpanView(const struct tok_state *tok, _PyTok_Span span,
- Py_ssize_t *length)
-{
- assert(length != NULL);
- assert(_PyTok_SpanIsValid(span));
- *length = span.end - span.start;
- (void)_PyLexer_BufferPointer(tok, span.end);
- return _PyLexer_BufferPointer(tok, span.start);
-}
-
static inline int
_PyLexer_ByteColumn(const struct tok_state *tok)
{
diff --git a/Parser/lexer/string.c b/Parser/lexer/string.c
index 99e45578b6ca16e..d0f568dfa857580 100644
--- a/Parser/lexer/string.c
+++ b/Parser/lexer/string.c
@@ -76,13 +76,11 @@ finish_ftstring_expr(struct tok_state *tok, ftstring_state
*state,
if (!(state->debug_expr || tstring_interpolation) || token->metadata) {
return 0;
}
- Py_ssize_t expr_len;
- const char *expr = _PyLexer_BufferSpanView(
- tok, state->expr_span, &expr_len);
tokenizer_comments *comments = state->comments;
PyObject *res;
if (comments != NULL && comments->count > 0) {
- Py_ssize_t stripped_size = expr_len;
+ Py_ssize_t stripped_size =
+ state->expr_span.end - state->expr_span.start;
Py_ssize_t comment_count = 0;
for (Py_ssize_t i = 0; i < comments->count; i++) {
_PyTok_Span comment = comments->spans[i];
@@ -101,26 +99,28 @@ finish_ftstring_expr(struct tok_state *tok, ftstring_state
*state,
PyErr_NoMemory();
return -1;
}
- _PyTok_Off copied_to = state->expr_span.start;
+ _PyTok_Span kept = {state->expr_span.start, state->expr_span.start};
Py_ssize_t stripped_len = 0;
for (Py_ssize_t i = 0; i < comment_count; i++) {
- _PyTok_Span comment = comments->spans[i];
- Py_ssize_t length = comment.start - copied_to;
- memcpy(stripped + stripped_len,
- expr + copied_to - state->expr_span.start,
- (size_t)length);
+ kept.end = comments->spans[i].start;
+ Py_ssize_t length;
+ const char *text = _PyTok_SourceSpanView(
+ &tok->source, kept, &length);
+ memcpy(stripped + stripped_len, text, (size_t)length);
stripped_len += length;
- copied_to = comment.end;
+ kept.start = comments->spans[i].end;
}
- Py_ssize_t length = state->expr_span.end - copied_to;
- memcpy(stripped + stripped_len,
- expr + copied_to - state->expr_span.start,
- (size_t)length);
- stripped_len += length;
- res = PyUnicode_DecodeUTF8(stripped, stripped_len, NULL);
+ kept.end = state->expr_span.end;
+ Py_ssize_t length;
+ const char *text = _PyTok_SourceSpanView(&tok->source, kept, &length);
+ memcpy(stripped + stripped_len, text, (size_t)length);
+ res = PyUnicode_DecodeUTF8(stripped, stripped_size, NULL);
PyMem_Free(stripped);
}
else {
+ Py_ssize_t expr_len;
+ const char *expr = _PyTok_SourceSpanView(
+ &tok->source, state->expr_span, &expr_len);
res = PyUnicode_DecodeUTF8(expr, expr_len, NULL);
}
@@ -339,7 +339,8 @@ _PyLexer_scan_string(struct tok_state *tok, struct token
*token, int c)
}
int end_lineno = tok->lineno;
_PyTok_Loc location = tok->start_loc;
- const char *line = _PyLexer_BufferPointer(tok, tok->start) -
location.byte_col;
+ const char *line = _PyTok_SourcePointer(
+ &tok->source, tok->start - location.byte_col);
Py_ssize_t cursor_offset = (Py_ssize_t)location.byte_col + 1;
const ftstring_state *state = _PyLexer_CurrentFTString(tok);
@@ -460,7 +461,8 @@ _PyLexer_get_ftstring(struct tok_state *tok, ftstring_state
*current, struct tok
int end_lineno = tok->lineno;
_PyTok_Loc location = current->start_loc;
- const char *line = _PyLexer_BufferPointer(tok, current->start) -
location.byte_col;
+ const char *line = _PyTok_SourcePointer(
+ &tok->source, current->start - location.byte_col);
Py_ssize_t cursor_offset = (Py_ssize_t)location.byte_col + 1;
if (quote_size == 3) {
diff --git a/Parser/tokenizer/api.c b/Parser/tokenizer/api.c
index 3f5efa8dc8f26e6..d955c6b335e1db6 100644
--- a/Parser/tokenizer/api.c
+++ b/Parser/tokenizer/api.c
@@ -4,6 +4,7 @@
#include "tokenizer.h"
#include "reader.h"
+#include "reader_internal.h"
#include "../lexer/state.h"
_PyTokenizer_Info
@@ -21,10 +22,10 @@ _PyTokenizer_GetInfo(const struct tok_state *tok)
.delimiter_loc = {-1, -1},
.in_formatted_string = tok->ftstring_depth != 0,
.is_interactive = _PyTok_ReaderIsInteractive(tok),
- .is_file = tok->fp != NULL && tok->fp != stdin,
+ .is_file = tok->reader->fp != NULL && tok->reader->fp != stdin,
.filename = tok->filename,
.module = tok->module,
- .encoding = tok->encoding,
+ .encoding = tok->reader->encoding,
};
if (tok->level > 0) {
int level = tok->level - 1;
@@ -45,14 +46,14 @@ _PyToken_TextView(const struct tok_state *tok, const struct
token *token,
*length = 0;
return "";
}
- return _PyLexer_BufferSpanView(tok, token->span, length);
+ return _PyTok_SourceSpanView(&tok->source, token->span, length);
}
const char *
_PyTokenizer_SpanView(const struct tok_state *tok, _PyTok_Span span,
Py_ssize_t *length)
{
- return _PyLexer_BufferSpanView(tok, span, length);
+ return _PyTok_SourceSpanView(&tok->source, span, length);
}
void
@@ -60,19 +61,13 @@ _PyToken_GetView(const struct tok_state *tok, const struct
token *token,
_PyToken_View *view)
{
assert(view != NULL);
- assert((token->span.start == -1 && token->span.end == -1) ||
- _PyTok_SpanIsValid(token->span));
- if (token->span.start >= 0) {
- (void)_PyLexer_BufferPointer(tok, token->span.end);
- }
- view->text = token->span.start < 0
- ? NULL : _PyLexer_BufferPointer(tok, token->span.start);
- view->length = token->span.end - token->span.start;
- view->end_line = _PyLexer_BufferPointer(tok, tok->line_start);
- view->line = ISSTRINGLIT(token->type)
- ? view->text - token->start_loc.byte_col : view->end_line;
- view->line_length = tok->inp - tok->line_start +
- (view->end_line - view->line);
+ view->text = _PyToken_TextView(tok, token, &view->length);
+ view->end_line_start = tok->line_start;
+ view->line_span = (_PyTok_Span){
+ ISSTRINGLIT(token->type)
+ ? token->span.start - token->start_loc.byte_col : tok->line_start,
+ tok->inp,
+ };
view->implicit_newline = tok->implicit_newline;
view->at_eof = tok->done == E_EOF;
}
@@ -111,22 +106,28 @@ _PyTokenizer_ImplyDedents(struct tok_state *tok)
int
_PyTokenizer_HasTrailingStatement(const struct tok_state *tok)
{
- const char *cur = _PyLexer_BufferPointer(tok, tok->cur);
- char c = *cur;
- for (;;) {
- while (c == ' ' || c == '\t' || c == '\n' || c == '\014') {
- c = *++cur;
- }
- if (!c) {
+ _PyTok_Off cur = tok->cur;
+ _PyTok_Off end = tok->source.base_offset + tok->source.len;
+ while (cur < end) {
+ int c = _PyTok_SourceByte(&tok->source, cur++);
+ if (c == '\0') {
return 0;
}
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\014') {
+ continue;
+ }
if (c != '#') {
return 1;
}
- while (c && c != '\n') {
- c = *++cur;
+ while (cur < end) {
+ c = _PyTok_SourceByte(&tok->source, cur);
+ if (c == '\0' || c == '\n') {
+ break;
+ }
+ cur++;
}
}
+ return 0;
}
int
diff --git a/Parser/tokenizer/decoder.c b/Parser/tokenizer/decoder.c
index 220fed65ad4e450..48f906845651aab 100644
--- a/Parser/tokenizer/decoder.c
+++ b/Parser/tokenizer/decoder.c
@@ -117,8 +117,8 @@ _PyTok_SetEncoding(struct tok_state *tok, const char
*encoding)
tok->done = E_NOMEM;
return -1;
}
- PyMem_Free(tok->encoding);
- tok->encoding = copy;
+ PyMem_Free(tok->reader->encoding);
+ tok->reader->encoding = copy;
return 0;
}
@@ -246,8 +246,8 @@ _PyTok_DetectEncoding(struct tok_state *tok, const
_PyTok_Chunk *first,
PyMem_Free(cookie);
return _PYTOK_ENCODING_ERROR;
}
- PyMem_Free(tok->encoding);
- tok->encoding = cookie;
+ PyMem_Free(tok->reader->encoding);
+ tok->reader->encoding = cookie;
return _PYTOK_ENCODING_DONE;
}
@@ -393,9 +393,10 @@ _PyTok_PrepareString(struct tok_state *tok, const char
*input, int utf8_only,
.len = raw_len,
.ownership = _PYTOK_CHUNK_BORROWED,
};
- if (tok->encoding != NULL && strcmp(tok->encoding, "utf-8") != 0) {
+ const char *encoding = tok->reader->encoding;
+ if (encoding != NULL && strcmp(encoding, "utf-8") != 0) {
if (_PyTok_DecodeOnce(
- tok, &decoded, tok->encoding, NULL) < 0) {
+ tok, &decoded, encoding, NULL) < 0) {
return -1;
}
}
@@ -407,7 +408,7 @@ _PyTok_PrepareString(struct tok_state *tok, const char
*input, int utf8_only,
return -1;
}
if (!utf8_only &&
- (tok->encoding == NULL || strcmp(tok->encoding, "utf-8") == 0) &&
+ (encoding == NULL || strcmp(encoding, "utf-8") == 0) &&
!_PyTokenizer_ensure_utf8(_PyTok_SourceData(&tok->source), tok,
1)) {
return -1;
}
@@ -418,22 +419,18 @@ int
_PyTok_StartDecoder(struct tok_state *tok, const char *errors)
{
_PyTok_Reader *reader = tok->reader;
- if (tok->encoding == NULL || reader->decoder != NULL) {
+ if (reader->encoding == NULL || reader->decoder != NULL) {
return 0;
}
if (reader->kind == _PYTOK_READER_FILE &&
- strcmp(tok->encoding, "utf-8") == 0) {
+ strcmp(reader->encoding, "utf-8") == 0) {
return 0;
}
- PyObject *codec = _PyCodec_LookupTextEncoding(tok->encoding, NULL);
+ PyObject *codec = _PyCodec_LookupTextEncoding(reader->encoding, NULL);
if (codec != NULL) {
- PyObject *factory = PyObject_GetAttrString(codec,
"incrementaldecoder");
+ reader->decoder = _PyCodecInfo_GetIncrementalDecoder(codec, errors);
Py_DECREF(codec);
- if (factory != NULL) {
- reader->decoder = PyObject_CallFunction(factory, "s", errors);
- Py_DECREF(factory);
- }
}
if (reader->decoder == NULL) {
tok->done = PyErr_ExceptionMatches(PyExc_MemoryError)
diff --git a/Parser/tokenizer/helpers.c b/Parser/tokenizer/helpers.c
index 0d3ea85109ec49e..c39e6756476b97c 100644
--- a/Parser/tokenizer/helpers.c
+++ b/Parser/tokenizer/helpers.c
@@ -91,9 +91,9 @@ _PyTokenizer_syntaxerror(struct tok_state *tok, const char
*format, ...)
// These errors are cleaned on startup. Todo: Fix it.
va_list vargs;
va_start(vargs, format);
- int ret = _syntaxerror_range(tok, _PyLexer_BufferPointer(tok,
tok->line_start),
- tok->cur - tok->line_start, tok->lineno,
- format, -1, -1, vargs);
+ int ret = _syntaxerror_range(
+ tok, _PyTok_SourcePointer(&tok->source, tok->line_start),
+ tok->cur - tok->line_start, tok->lineno, format, -1, -1, vargs);
va_end(vargs);
return ret;
}
@@ -105,9 +105,10 @@ _PyTokenizer_syntaxerror_known_range(struct tok_state *tok,
{
va_list vargs;
va_start(vargs, format);
- int ret = _syntaxerror_range(tok, _PyLexer_BufferPointer(tok,
tok->line_start),
- tok->cur - tok->line_start, tok->lineno,
- format, col_offset, end_col_offset, vargs);
+ int ret = _syntaxerror_range(
+ tok, _PyTok_SourcePointer(&tok->source, tok->line_start),
+ tok->cur - tok->line_start, tok->lineno,
+ format, col_offset, end_col_offset, vargs);
va_end(vargs);
return ret;
}
diff --git a/Parser/tokenizer/reader.c b/Parser/tokenizer/reader.c
index 36f910e3199756d..ce2bb1cf5c4bc30 100644
--- a/Parser/tokenizer/reader.c
+++ b/Parser/tokenizer/reader.c
@@ -34,6 +34,7 @@ _PyTok_ReaderFree(struct tok_state *tok)
i < (int)Py_ARRAY_LENGTH(reader->prefetched_lines); i++) {
_PyTok_ChunkClear(&reader->prefetched_lines[i]);
}
+ PyMem_Free(reader->encoding);
PyMem_Free(reader->file_buffer);
PyMem_Free(reader->decoded);
PyMem_Free(reader);
@@ -173,15 +174,13 @@ next_prepared(struct tok_state *tok, _PyTok_Chunk *chunk)
if (tok->lineno >= tok->source.nlines) {
return _PYTOK_READ_EOF;
}
- const char *start = _PyLexer_BufferPointer(tok, tok->inp);
- const char *newline = memchr(
- start, '\n', tok->source.bytes + tok->source.len - start);
- _PyTok_Off end = newline != NULL
- ? newline - tok->source.bytes + 1 : tok->source.len;
- chunk->data = (char *)start;
- chunk->len = tok->source.bytes + end - start;
+ _PyTok_Span tail = {tok->inp, tok->source.base_offset + tok->source.len};
+ _PyTok_Off newline = _PyTok_SourceFindByte(&tok->source, tail, '\n');
+ _PyTok_Span line = {tail.start, newline >= 0 ? newline + 1 : tail.end};
+ chunk->data = (char *)_PyTok_SourceSpanView(
+ &tok->source, line, &chunk->len);
chunk->ownership = _PYTOK_CHUNK_BORROWED;
- chunk->implicit_newline = end == tok->source.len &&
+ chunk->implicit_newline = line.end == tail.end &&
tok->reader->prepared_final_newline_is_implicit;
return _PYTOK_READ_LINE;
}
@@ -203,7 +202,7 @@ read_file_line(struct tok_state *tok, _PyTok_Chunk *chunk)
int available = (int)Py_MIN(reader->file_buffer_cap - len, INT_MAX);
size_t read = 0;
char *result = _Py_UniversalNewlineFgetsWithSize(
- reader->file_buffer + len, available, tok->fp, NULL, &read);
+ reader->file_buffer + len, available, reader->fp, NULL, &read);
if (result == NULL) {
if (len == 0) {
return _PYTOK_READ_EOF;
@@ -228,7 +227,7 @@ initialize_file(struct tok_state *tok)
{
_PyTok_Reader *reader = tok->reader;
reader->file_initialized = 1;
- if (tok->encoding != NULL) {
+ if (reader->encoding != NULL) {
return _PyTok_StartDecoder(tok, "strict");
}
@@ -411,7 +410,7 @@ next_readline(struct tok_state *tok, _PyTok_Chunk *chunk)
}
_PyTok_Chunk input = {0};
- if (tok->encoding != NULL) {
+ if (reader->encoding != NULL) {
if (!PyBytes_Check(raw)) {
PyErr_SetString(PyExc_TypeError,
"readline() returned a non-bytes object");
@@ -434,7 +433,7 @@ next_readline(struct tok_state *tok, _PyTok_Chunk *chunk)
input.ownership = _PYTOK_CHUNK_PYOBJECT;
int decoded;
if (reader->decoder == NULL &&
- strcmp(tok->encoding, "utf-8") == 0 &&
+ strcmp(reader->encoding, "utf-8") == 0 &&
chunk_is_line(&input)) {
decoded = _PyTok_DecodeOnce(
tok, &input, "utf-8", "replace");
@@ -510,7 +509,7 @@ next_interactive(struct tok_state *tok, _PyTok_Chunk *chunk)
return _PYTOK_READ_STOPPED;
}
char *input = PyOS_Readline(
- tok->fp != NULL ? tok->fp : stdin, stdout, reader->prompt);
+ reader->fp != NULL ? reader->fp : stdin, stdout, reader->prompt);
if (reader->nextprompt != NULL) {
reader->prompt = reader->nextprompt;
}
@@ -527,9 +526,9 @@ next_interactive(struct tok_state *tok, _PyTok_Chunk *chunk)
.len = len,
.ownership = _PYTOK_CHUNK_PYMEM,
};
- if (tok->encoding != NULL &&
+ if (reader->encoding != NULL &&
_PyTok_DecodeOnce(
- tok, &decoded, tok->encoding, NULL) < 0) {
+ tok, &decoded, reader->encoding, NULL) < 0) {
_PyTok_ChunkClear(&decoded);
return _PYTOK_READ_ERROR;
}
@@ -605,10 +604,11 @@ int
_PyTok_ReaderUnderflow(struct tok_state *tok)
{
assert(tok->cur >= tok->buf_offset && tok->cur <= tok->inp);
- _PyTok_ReaderKind kind = tok->reader->kind;
+ _PyTok_Reader *reader = tok->reader;
+ _PyTok_ReaderKind kind = reader->kind;
int prepared = kind == _PYTOK_READER_PREPARED;
int streaming = reader_is_streaming(kind);
- int reset_buffer = !prepared && tok->start < 0 &&
+ int reset_buffer = tok->start < 0 &&
_PyLexer_CurrentFTString(tok) == NULL;
_PyTok_Chunk chunk;
@@ -661,23 +661,24 @@ _PyTok_ReaderUnderflow(struct tok_state *tok)
tok->cur = source_start;
tok->buf_offset = source_start;
tok->line_start = tok->buf_offset;
- tok->start = -1;
}
tok->inp = source_start + chunk.len;
}
- if (prepared) {
- if (tok->start < 0 && _PyLexer_CurrentFTString(tok) == NULL) {
- tok->buf_offset = tok->source.base_offset +
- (chunk.data - tok->source.bytes);
+ else {
+ if (reset_buffer) {
+ tok->buf_offset = tok->inp;
}
- tok->inp = _PyLexer_BufferOffset(tok, chunk.data) + chunk.len;
+ tok->inp += chunk.len;
}
tok->implicit_newline = chunk.implicit_newline;
tok->lineno++;
if (kind == _PYTOK_READER_FILE &&
- (tok->encoding == NULL || strcmp(tok->encoding, "utf-8") == 0) &&
- !_PyTokenizer_ensure_utf8(_PyLexer_BufferPointer(tok, tok->cur),
tok, tok->lineno)) {
+ (reader->encoding == NULL ||
+ strcmp(reader->encoding, "utf-8") == 0) &&
+ !_PyTokenizer_ensure_utf8(
+ _PyTok_SourcePointer(&tok->source, tok->cur),
+ tok, tok->lineno)) {
_PyTok_ChunkClear(&chunk);
return 0;
}
@@ -779,7 +780,7 @@ _PyTokenizer_FromFile(FILE *fp, const char *encoding,
_PyTokenizer_Free(tok);
return NULL;
}
- tok->fp = fp;
+ tok->reader->fp = fp;
tok->reader->prompt = ps1;
tok->reader->nextprompt = ps2;
return tok;
@@ -840,8 +841,8 @@ _PyTokenizer_FindEncodingFilename(int fd, PyObject
*filename)
tok->filename = Py_NewRef(filename != NULL ? filename :
&_Py_STR(anon_string));
char *encoding = NULL;
if (initialize_file(tok) == 0) {
- encoding = tok->encoding;
- tok->encoding = NULL;
+ encoding = tok->reader->encoding;
+ tok->reader->encoding = NULL;
}
fclose(fp);
_PyTokenizer_Free(tok);
diff --git a/Parser/tokenizer/reader_internal.h
b/Parser/tokenizer/reader_internal.h
index f74063de8d653a4..a7a68269fffaa4a 100644
--- a/Parser/tokenizer/reader_internal.h
+++ b/Parser/tokenizer/reader_internal.h
@@ -41,6 +41,8 @@ typedef struct {
} _PyTok_Chunk;
typedef struct _PyTok_Reader {
+ FILE *fp; // Borrowed input stream; NULL for string and readline input.
+ char *encoding; // Owned source encoding.
PyObject *readline;
PyObject *decoder;
const char *prompt;
diff --git a/Parser/tokenizer/source.h b/Parser/tokenizer/source.h
index 66980a41fb7c481..0fd9fe1877bed2b 100644
--- a/Parser/tokenizer/source.h
+++ b/Parser/tokenizer/source.h
@@ -19,6 +19,46 @@ _PyTok_SourceData(const _PyTok_SourceText *source)
return source->bytes != NULL ? source->bytes : "";
}
+/* Convert positions within the retained source window. Pointers and views
+ are borrowed; append, discard, and clear invalidate them. */
+static inline const char *
+_PyTok_SourcePointer(const _PyTok_SourceText *source, _PyTok_Off offset)
+{
+ assert(offset >= source->base_offset);
+ assert(offset - source->base_offset <= source->len);
+ return _PyTok_SourceData(source) + (offset - source->base_offset);
+}
+
+static inline unsigned char
+_PyTok_SourceByte(const _PyTok_SourceText *source, _PyTok_Off offset)
+{
+ assert(offset >= source->base_offset);
+ assert(offset - source->base_offset < source->len);
+ return (unsigned char)source->bytes[offset - source->base_offset];
+}
+
+static inline const char *
+_PyTok_SourceSpanView(const _PyTok_SourceText *source, _PyTok_Span span,
+ Py_ssize_t *length)
+{
+ assert(length != NULL);
+ assert(_PyTok_SpanIsValid(span));
+ assert(span.end - source->base_offset <= source->len);
+ *length = span.end - span.start;
+ return _PyTok_SourcePointer(source, span.start);
+}
+
+/* Return the first matching offset within span, or -1 if absent. */
+static inline _PyTok_Off
+_PyTok_SourceFindByte(const _PyTok_SourceText *source, _PyTok_Span span,
+ int byte)
+{
+ Py_ssize_t length;
+ const char *data = _PyTok_SourceSpanView(source, span, &length);
+ const char *found = memchr(data, byte, length);
+ return found != NULL ? span.start + (found - data) : -1;
+}
+
PyAPI_FUNC(void) _PyTok_SourceInit(_PyTok_SourceText *);
/* Clear invalidates all spans and views for the source. */
PyAPI_FUNC(void) _PyTok_SourceClear(_PyTok_SourceText *);
diff --git a/Parser/tokenizer/tokenizer.h b/Parser/tokenizer/tokenizer.h
index 2ef87add3646c3c..bc4009700fdc967 100644
--- a/Parser/tokenizer/tokenizer.h
+++ b/Parser/tokenizer/tokenizer.h
@@ -21,9 +21,8 @@ struct token {
typedef struct {
const char *text;
Py_ssize_t length;
- const char *line;
- Py_ssize_t line_length;
- const char *end_line;
+ _PyTok_Span line_span;
+ _PyTok_Off end_line_start;
int implicit_newline;
int at_eof;
} _PyToken_View;
@@ -73,8 +72,9 @@ _PyTokenizer_Info _PyTokenizer_GetInfo(const struct tok_state
*);
/* An absent token span has a nonnull empty text view. */
const char *_PyToken_TextView(
const struct tok_state *, const struct token *, Py_ssize_t *);
-/* Use the token from the most recent Get. text is NULL for an absent span;
- line includes the token's complete physical line range. */
+/* Use the token from the most recent Get. An absent span has nonnull empty
text.
+ line_span covers the token's complete physical line range. end_line_start
+ is the logical offset of its final physical line. */
void _PyToken_GetView(
const struct tok_state *tok, const struct token *token,
_PyToken_View *view);
diff --git a/Python/Python-tokenize.c b/Python/Python-tokenize.c
index e91141bd1b9e610..731444e13a02750 100644
--- a/Python/Python-tokenize.c
+++ b/Python/Python-tokenize.c
@@ -158,12 +158,16 @@ _tokenizer_error(tokenizeriterobject *it)
static PyObject *
_get_current_line(tokenizeriterobject *it, int current_lineno,
- const char *line_start, Py_ssize_t size, int *line_changed)
+ const _PyToken_View *view, int *line_changed)
{
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(it);
if (current_lineno != it->last_lineno) {
- // Line has changed since last token, so we fetch the new line and
cache it
- // in the iter object.
+ Py_ssize_t size;
+ const char *line_start = _PyTokenizer_SpanView(
+ it->tok, view->line_span, &size);
+ if (size > 0 && view->implicit_newline) {
+ size--;
+ }
Py_XDECREF(it->last_line);
it->last_line = PyUnicode_DecodeUTF8(line_start, size, "replace");
it->byte_col_offset_diff = 0;
@@ -180,14 +184,13 @@ _get_col_offsets(tokenizeriterobject *it, const struct
token *token,
Py_ssize_t *col_offset, Py_ssize_t *end_col_offset)
{
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(it);
- const char *token_start = view->text;
- const char *token_end = token_start == NULL
- ? NULL : token_start + view->length;
+ _PyTok_Off token_start = token->span.start;
+ _PyTok_Off token_end = token->span.end;
Py_ssize_t lineno = token->start_loc.lineno;
Py_ssize_t end_lineno = token->end_loc.lineno;
Py_ssize_t byte_offset = -1;
- if (token_start != NULL && token_start >= view->line) {
- byte_offset = token_start - view->line;
+ if (token_start >= view->line_span.start) {
+ byte_offset = token_start - view->line_span.start;
if (line_changed) {
*col_offset = _PyPegen_byte_offset_to_character_offset_line(line,
0, byte_offset);
if (*col_offset < 0) {
@@ -200,8 +203,8 @@ _get_col_offsets(tokenizeriterobject *it, const struct
token *token,
}
}
- if (token_end != NULL && token_end >= view->end_line) {
- Py_ssize_t end_byte_offset = token_end - view->end_line;
+ if (token_end >= view->end_line_start) {
+ Py_ssize_t end_byte_offset = token_end - view->end_line_start;
if (lineno == end_lineno) {
// 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);
@@ -213,7 +216,8 @@ _get_col_offsets(tokenizeriterobject *it, const struct
token *token,
}
else {
*end_col_offset = _PyPegen_byte_offset_to_character_offset_line(
- line, view->end_line - view->line, token_end - view->line);
+ line, view->end_line_start - view->line_span.start,
+ token_end - view->line_span.start);
if (*end_col_offset < 0) {
return -1;
}
@@ -251,15 +255,7 @@ tokenizeriter_next(PyObject *op)
}
_PyToken_View view;
_PyToken_GetView(it->tok, &token, &view);
- const char *token_start = view.text;
- PyObject *str;
- if (token.span.start < 0) {
- assert(token.span.start == -1 && token.span.end == -1);
- str = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
- }
- else {
- str = PyUnicode_FromStringAndSize(token_start, view.length);
- }
+ PyObject *str = PyUnicode_FromStringAndSize(view.text, view.length);
if (str == NULL) {
goto exit;
}
@@ -271,13 +267,8 @@ tokenizeriter_next(PyObject *op)
if (it->extra_tokens && is_trailing_token) {
line = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
} else {
- Py_ssize_t size = view.line_length;
- if (size >= 1 && view.implicit_newline) {
- size -= 1;
- }
-
line = _get_current_line(
- it, token.end_loc.lineno, view.line, size, &line_changed);
+ it, token.end_loc.lineno, &view, &line_changed);
}
if (line == NULL) {
Py_DECREF(str);
@@ -307,8 +298,7 @@ tokenizeriter_next(PyObject *op)
else if (type == NEWLINE) {
if (!view.implicit_newline) {
Py_DECREF(str);
- assert(token_start != NULL);
- if (token_start[0] == '\r') {
+ if (view.text[0] == '\r') {
str = PyUnicode_FromString("\r\n");
} else {
str = PyUnicode_FromString("\n");
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]