Skip to content

Commit f892b11

Browse files
authored
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.
1 parent 50082b8 commit f892b11

14 files changed

Lines changed: 201 additions & 225 deletions

File tree

‎Include/internal/pycore_codecs.h‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,9 @@ extern PyObject* _PyCodec_DecodeText(
5959
const char *encoding,
6060
const char *errors);
6161

62-
/* These two aren't actually text encoding specific, but _io.TextIOWrapper
63-
* is the only current API consumer.
64-
*/
65-
extern PyObject* _PyCodecInfo_GetIncrementalDecoder(
62+
/* Helpers to create incremental codecs from a codec info object. */
63+
// Export for test_peg_generator.
64+
PyAPI_FUNC(PyObject*) _PyCodecInfo_GetIncrementalDecoder(
6665
PyObject *codec_info,
6766
const char *errors);
6867

‎Parser/lexer/lexer.c‎

Lines changed: 36 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,6 @@
1313
tokenizing. */
1414
static const char* type_comment_prefix = "# type: ";
1515

16-
static inline int
17-
contains_null_bytes(const char* str, size_t size)
18-
{
19-
return memchr(str, 0, size) != NULL;
20-
}
21-
2216
int
2317
_PyLexer_refill(struct tok_state *tok)
2418
{
@@ -29,8 +23,9 @@ _PyLexer_refill(struct tok_state *tok)
2923
#if defined(Py_DEBUG)
3024
if (tok->debug) {
3125
fprintf(stderr, "line[%d] = ", tok->lineno);
32-
_PyTokenizer_print_escape(stderr, _PyLexer_BufferPointer(tok, tok->cur),
33-
tok->inp - tok->cur);
26+
_PyTokenizer_print_escape(
27+
stderr, _PyTok_SourcePointer(&tok->source, tok->cur),
28+
tok->inp - tok->cur);
3429
fprintf(stderr, " tok->done = %d\n", tok->done);
3530
}
3631
#endif
@@ -39,8 +34,8 @@ _PyLexer_refill(struct tok_state *tok)
3934
return 0;
4035
}
4136
tok->line_start = tok->cur;
42-
if (contains_null_bytes(_PyLexer_BufferPointer(tok, tok->line_start),
43-
tok->inp - tok->line_start)) {
37+
_PyTok_Span line = {tok->line_start, tok->inp};
38+
if (_PyTok_SourceFindByte(&tok->source, line, '\0') >= 0) {
4439
_PyTokenizer_syntaxerror(tok, "source code cannot contain null bytes");
4540
tok->cur = tok->inp;
4641
return 0;
@@ -56,7 +51,7 @@ _PyLexer_backup(struct tok_state *tok, int c)
5651
if (--tok->cur < tok->buf_offset) {
5752
Py_FatalError("tokenizer beginning of buffer");
5853
}
59-
if ((int)(unsigned char)*_PyLexer_BufferPointer(tok, tok->cur) != Py_CHARMASK(c)) {
54+
if (_PyTok_SourceByte(&tok->source, tok->cur) != Py_CHARMASK(c)) {
6055
Py_FatalError("tok_backup: wrong character");
6156
}
6257
}
@@ -74,7 +69,8 @@ verify_identifier(struct tok_state *tok)
7469
PyObject *s;
7570
if (tok_failed(tok))
7671
return 0;
77-
s = PyUnicode_DecodeUTF8(_PyLexer_BufferPointer(tok, tok->start), tok->cur - tok->start, NULL);
72+
s = PyUnicode_DecodeUTF8(_PyTok_SourcePointer(&tok->source, tok->start),
73+
tok->cur - tok->start, NULL);
7874
if (s == NULL) {
7975
if (PyErr_ExceptionMatches(PyExc_UnicodeDecodeError)) {
8076
tok->done = E_DECODE;
@@ -105,13 +101,13 @@ verify_identifier(struct tok_state *tok)
105101
Py_DECREF(s);
106102
if (Py_UNICODE_ISPRINTABLE(ch)) {
107103
_PyTokenizer_syntaxerror_at(
108-
tok, _PyLexer_BufferPointer(tok, tok->line_start),
104+
tok, _PyTok_SourcePointer(&tok->source, tok->line_start),
109105
error_cursor - tok->line_start, tok->lineno, -1, -1,
110106
"invalid character '%c' (U+%04X)", ch, ch);
111107
}
112108
else {
113109
_PyTokenizer_syntaxerror_at(
114-
tok, _PyLexer_BufferPointer(tok, tok->line_start),
110+
tok, _PyTok_SourcePointer(&tok->source, tok->line_start),
115111
error_cursor - tok->line_start, tok->lineno, -1, -1,
116112
"invalid non-printable character U+%04X", ch);
117113
}
@@ -172,10 +168,6 @@ _PyLexer_get_normal(struct tok_state *tok, ftstring_state *current, struct token
172168
/* Skip comment, unless it's a type comment */
173169
if (c == '#') {
174170

175-
const char* p = NULL;
176-
const char *prefix, *type_start;
177-
int current_starting_col_offset;
178-
179171
while (c != EOF && c != '\n' && c != '\r') {
180172
c = tok_nextc(tok);
181173
}
@@ -192,23 +184,20 @@ _PyLexer_get_normal(struct tok_state *tok, ftstring_state *current, struct token
192184
}
193185
}
194186

195-
if (tok->tok_extra_tokens) {
196-
p = _PyLexer_BufferPointer(tok, tok->start);
197-
}
198-
187+
_PyTok_Off comment = tok->start;
199188
if (tok->type_comments) {
200-
p = _PyLexer_BufferPointer(tok, tok->start);
201-
current_starting_col_offset = tok->start_loc.byte_col;
202-
prefix = type_comment_prefix;
203-
while (*prefix && p < _PyLexer_BufferPointer(tok, tok->cur)) {
189+
const char *prefix = type_comment_prefix;
190+
while (*prefix && comment < tok->cur) {
204191
if (*prefix == ' ') {
205-
while (*p == ' ' || *p == '\t') {
206-
p++;
207-
current_starting_col_offset++;
192+
while (comment < tok->cur) {
193+
int ch = _PyTok_SourceByte(&tok->source, comment);
194+
if (ch != ' ' && ch != '\t') {
195+
break;
196+
}
197+
comment++;
208198
}
209-
} else if (*prefix == *p) {
210-
p++;
211-
current_starting_col_offset++;
199+
} else if (*prefix == _PyTok_SourceByte(&tok->source, comment)) {
200+
comment++;
212201
} else {
213202
break;
214203
}
@@ -218,35 +207,26 @@ _PyLexer_get_normal(struct tok_state *tok, ftstring_state *current, struct token
218207

219208
/* This is a type comment if we matched all of type_comment_prefix. */
220209
if (!*prefix) {
221-
int is_type_ignore = 1;
222-
// +6 in order to skip the word 'ignore'
223-
const char *ignore_end = p + 6;
224-
const int ignore_end_col_offset = current_starting_col_offset + 6;
225210
tok_backup(tok, c); /* don't eat the newline or EOF */
226-
227-
type_start = p;
228-
229211
/* A TYPE_IGNORE is "type: ignore" followed by the end of the token
230212
* or anything ASCII and non-alphanumeric. */
231-
is_type_ignore = (
232-
_PyLexer_BufferPointer(tok, tok->cur) >= ignore_end && memcmp(p, "ignore", 6) == 0
233-
&& !(_PyLexer_BufferPointer(tok, tok->cur) > ignore_end
234-
&& ((unsigned char)ignore_end[0] >= 128 || Py_ISALNUM(ignore_end[0]))));
213+
int is_type_ignore = tok->cur - comment >= 6 &&
214+
memcmp(_PyTok_SourcePointer(&tok->source, comment),
215+
"ignore", 6) == 0;
216+
if (is_type_ignore && comment + 6 < tok->cur) {
217+
int ch = _PyTok_SourceByte(&tok->source, comment + 6);
218+
is_type_ignore = ch < 128 && !Py_ISALNUM(ch);
219+
}
235220

236221
int type = is_type_ignore ? TYPE_IGNORE : TYPE_COMMENT;
237-
int start_col_offset = is_type_ignore
238-
? ignore_end_col_offset : current_starting_col_offset;
222+
p_start = comment + (is_type_ignore ? 6 : 0);
239223
p_end = tok->cur;
240-
if (is_type_ignore) {
241-
p_start = _PyLexer_BufferOffset(tok, ignore_end);
242-
243-
/* If this type ignore is the only thing on the line, consume the newline also. */
244-
if (blankline) {
245-
tok_nextc(tok);
246-
tok->layout.at_bol = 1;
247-
}
248-
} else {
249-
p_start = _PyLexer_BufferOffset(tok, type_start);
224+
int start_col_offset = tok->start_loc.byte_col +
225+
(int)(p_start - tok->start);
226+
/* Consume the newline after a standalone type ignore. */
227+
if (is_type_ignore && blankline) {
228+
tok_nextc(tok);
229+
tok->layout.at_bol = 1;
250230
}
251231
_PyLexer_token_setup(tok, token, type, p_start, p_end);
252232
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
257237
}
258238
if (tok->tok_extra_tokens) {
259239
tok_backup(tok, c); /* don't eat the newline or EOF */
260-
p_start = _PyLexer_BufferOffset(tok, p);
240+
p_start = comment;
261241
p_end = tok->cur;
262242
tok->layout.comment_newline = blankline;
263243
return MAKE_TOKEN(COMMENT);

‎Parser/lexer/lexer_internal.h‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,11 @@ tok_nextc(struct tok_state *tok)
4040
return EOF;
4141
}
4242
}
43-
assert(tok->cur >= tok->source.base_offset);
44-
assert(tok->cur - tok->source.base_offset < tok->source.len);
4543
if (tok->cur - tok->line_start >= INT_MAX) {
4644
tok->done = E_COLUMNOVERFLOW;
4745
return EOF;
4846
}
49-
return Py_CHARMASK(
50-
tok->source.bytes[tok->cur++ - tok->source.base_offset]);
47+
return _PyTok_SourceByte(&tok->source, tok->cur++);
5148
}
5249

5350
/* Return -1 on error, otherwise whether the line is blank. */

‎Parser/lexer/state.c‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@ _PyLexer_PopFTString(struct tok_state *tok)
5858
void
5959
_PyTokenizer_Free(struct tok_state *tok)
6060
{
61-
if (tok->encoding != NULL) {
62-
PyMem_Free(tok->encoding);
63-
}
6461
Py_XDECREF(tok->filename);
6562
Py_XDECREF(tok->module);
6663
_PyTok_ReaderFree(tok);

‎Parser/lexer/state.h‎

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ struct tok_state {
8080
_PyTok_SourceText source;
8181
int done; /* E_OK normally, E_EOF at EOF, otherwise error code */
8282
/* NB If done != E_OK, cur must be == inp!!! */
83-
FILE *fp; /* Rest of input; NULL if tokenizing a string */
8483
lexer_layout_state layout;
8584
int lineno; /* Current line number */
8685
_PyTok_Loc start_loc;
@@ -92,9 +91,6 @@ struct tok_state {
9291
int parencolstack[MAXLEVEL];
9392
PyObject *filename;
9493
PyObject *module;
95-
/* Stuff for PEP 0263 */
96-
char *encoding; /* Source encoding. */
97-
9894
struct _PyTok_Reader *reader;
9995

10096
int type_comments; /* Whether to look for type comments */
@@ -135,33 +131,6 @@ _PyLexer_FTStringBracketDepth(const struct tok_state *tok,
135131
return tok->level - state->paren_level;
136132
}
137133

138-
static inline _PyTok_Off
139-
_PyLexer_BufferOffset(const struct tok_state *tok, const char *position)
140-
{
141-
const char *base = _PyTok_SourceData(&tok->source);
142-
assert(position >= base && position <= base + tok->source.len);
143-
return tok->source.base_offset + (position - base);
144-
}
145-
146-
static inline const char *
147-
_PyLexer_BufferPointer(const struct tok_state *tok, _PyTok_Off offset)
148-
{
149-
assert(offset >= tok->source.base_offset);
150-
assert(offset - tok->source.base_offset <= tok->source.len);
151-
return _PyTok_SourceData(&tok->source) + (offset - tok->source.base_offset);
152-
}
153-
154-
static inline const char *
155-
_PyLexer_BufferSpanView(const struct tok_state *tok, _PyTok_Span span,
156-
Py_ssize_t *length)
157-
{
158-
assert(length != NULL);
159-
assert(_PyTok_SpanIsValid(span));
160-
*length = span.end - span.start;
161-
(void)_PyLexer_BufferPointer(tok, span.end);
162-
return _PyLexer_BufferPointer(tok, span.start);
163-
}
164-
165134
static inline int
166135
_PyLexer_ByteColumn(const struct tok_state *tok)
167136
{

‎Parser/lexer/string.c‎

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,11 @@ finish_ftstring_expr(struct tok_state *tok, ftstring_state *state,
7676
if (!(state->debug_expr || tstring_interpolation) || token->metadata) {
7777
return 0;
7878
}
79-
Py_ssize_t expr_len;
80-
const char *expr = _PyLexer_BufferSpanView(
81-
tok, state->expr_span, &expr_len);
8279
tokenizer_comments *comments = state->comments;
8380
PyObject *res;
8481
if (comments != NULL && comments->count > 0) {
85-
Py_ssize_t stripped_size = expr_len;
82+
Py_ssize_t stripped_size =
83+
state->expr_span.end - state->expr_span.start;
8684
Py_ssize_t comment_count = 0;
8785
for (Py_ssize_t i = 0; i < comments->count; i++) {
8886
_PyTok_Span comment = comments->spans[i];
@@ -101,26 +99,28 @@ finish_ftstring_expr(struct tok_state *tok, ftstring_state *state,
10199
PyErr_NoMemory();
102100
return -1;
103101
}
104-
_PyTok_Off copied_to = state->expr_span.start;
102+
_PyTok_Span kept = {state->expr_span.start, state->expr_span.start};
105103
Py_ssize_t stripped_len = 0;
106104
for (Py_ssize_t i = 0; i < comment_count; i++) {
107-
_PyTok_Span comment = comments->spans[i];
108-
Py_ssize_t length = comment.start - copied_to;
109-
memcpy(stripped + stripped_len,
110-
expr + copied_to - state->expr_span.start,
111-
(size_t)length);
105+
kept.end = comments->spans[i].start;
106+
Py_ssize_t length;
107+
const char *text = _PyTok_SourceSpanView(
108+
&tok->source, kept, &length);
109+
memcpy(stripped + stripped_len, text, (size_t)length);
112110
stripped_len += length;
113-
copied_to = comment.end;
111+
kept.start = comments->spans[i].end;
114112
}
115-
Py_ssize_t length = state->expr_span.end - copied_to;
116-
memcpy(stripped + stripped_len,
117-
expr + copied_to - state->expr_span.start,
118-
(size_t)length);
119-
stripped_len += length;
120-
res = PyUnicode_DecodeUTF8(stripped, stripped_len, NULL);
113+
kept.end = state->expr_span.end;
114+
Py_ssize_t length;
115+
const char *text = _PyTok_SourceSpanView(&tok->source, kept, &length);
116+
memcpy(stripped + stripped_len, text, (size_t)length);
117+
res = PyUnicode_DecodeUTF8(stripped, stripped_size, NULL);
121118
PyMem_Free(stripped);
122119
}
123120
else {
121+
Py_ssize_t expr_len;
122+
const char *expr = _PyTok_SourceSpanView(
123+
&tok->source, state->expr_span, &expr_len);
124124
res = PyUnicode_DecodeUTF8(expr, expr_len, NULL);
125125
}
126126

@@ -339,7 +339,8 @@ _PyLexer_scan_string(struct tok_state *tok, struct token *token, int c)
339339
}
340340
int end_lineno = tok->lineno;
341341
_PyTok_Loc location = tok->start_loc;
342-
const char *line = _PyLexer_BufferPointer(tok, tok->start) - location.byte_col;
342+
const char *line = _PyTok_SourcePointer(
343+
&tok->source, tok->start - location.byte_col);
343344
Py_ssize_t cursor_offset = (Py_ssize_t)location.byte_col + 1;
344345

345346
const ftstring_state *state = _PyLexer_CurrentFTString(tok);
@@ -460,7 +461,8 @@ _PyLexer_get_ftstring(struct tok_state *tok, ftstring_state *current, struct tok
460461

461462
int end_lineno = tok->lineno;
462463
_PyTok_Loc location = current->start_loc;
463-
const char *line = _PyLexer_BufferPointer(tok, current->start) - location.byte_col;
464+
const char *line = _PyTok_SourcePointer(
465+
&tok->source, current->start - location.byte_col);
464466
Py_ssize_t cursor_offset = (Py_ssize_t)location.byte_col + 1;
465467

466468
if (quote_size == 3) {

0 commit comments

Comments
 (0)