Skip to content

Commit c10fd82

Browse files
committed
gh-153569: return tokenizer tokens as source spans
1 parent 600a445 commit c10fd82

7 files changed

Lines changed: 115 additions & 69 deletions

File tree

Parser/lexer/lexer.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,21 @@
1212

1313

1414
#define MAKE_TOKEN(token_type) _PyLexer_token_setup(tok, token, token_type, p_start, p_end)
15-
#define MAKE_TYPE_COMMENT_TOKEN(token_type, col_offset, end_col_offset) (\
16-
_PyLexer_type_comment_token_setup(tok, token, token_type, col_offset, end_col_offset, p_start, p_end))
15+
16+
static int
17+
type_comment_token_setup(struct tok_state *tok, struct token *token, int type,
18+
int col_offset, int end_col_offset,
19+
const char *start, const char *end)
20+
{
21+
_PyLexer_token_setup(tok, token, type, start, end);
22+
token->start_loc = (_PyTok_Loc){tok->lineno, col_offset};
23+
token->end_loc = (_PyTok_Loc){tok->lineno, end_col_offset};
24+
return type;
25+
}
26+
27+
#define MAKE_TYPE_COMMENT_TOKEN(token_type, col_offset, end_col_offset) \
28+
type_comment_token_setup(tok, token, token_type, col_offset, \
29+
end_col_offset, p_start, p_end)
1730

1831
/* Spaces in this constant are treated as "zero or more spaces or tabs" when
1932
tokenizing. */

Parser/lexer/lexer.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,23 @@ int _PyLexer_update_ftstring_expr(struct tok_state *tok, char cur);
77

88
int _PyTokenizer_Get(struct tok_state *, struct token *);
99

10+
/* The view points into the current input window. The next
11+
_PyTokenizer_Get() call may discard it. */
12+
static inline const char *
13+
_PyToken_TextView(const struct tok_state *tok, const struct token *token,
14+
Py_ssize_t *length)
15+
{
16+
assert(length != NULL);
17+
if (!_PyTok_SpanIsValid(token->span)) {
18+
*length = 0;
19+
return "";
20+
}
21+
assert(tok->buf != NULL);
22+
assert(tok->inp >= tok->buf);
23+
assert(token->span.start >= tok->buf_offset);
24+
assert(token->span.end - tok->buf_offset <= tok->inp - tok->buf);
25+
*length = token->span.end - token->span.start;
26+
return tok->buf + (token->span.start - tok->buf_offset);
27+
}
28+
1029
#endif

Parser/lexer/state.c

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ _PyTokenizer_tok_new(void)
5151
tok->comment_newline = 0;
5252
tok->implicit_newline = 0;
5353
_PyTok_SourceInit(&tok->source);
54-
_PyTok_CursorInit(&tok->reader_cursor, &tok->source);
5554
tok->reader = NULL;
5655
tok->tok_mode_stack[0] = (tokenizer_mode){.kind =TOK_REGULAR_MODE, .quote='\0', .quote_size = 0, .in_debug=0};
5756
tok->tok_mode_stack_index = 0;
@@ -101,41 +100,46 @@ _PyToken_Free(struct token *token) {
101100

102101
void
103102
_PyToken_Init(struct token *token) {
103+
#ifdef Py_DEBUG
104+
token->span = (_PyTok_Span){-1, -1};
105+
token->start_loc = (_PyTok_Loc){-1, -1};
106+
token->end_loc = (_PyTok_Loc){-1, -1};
107+
#endif
104108
token->metadata = NULL;
105109
}
106110

107-
int
108-
_PyLexer_type_comment_token_setup(struct tok_state *tok, struct token *token, int type, int col_offset,
109-
int end_col_offset, const char *start, const char *end)
111+
static inline _PyTok_Span
112+
buffer_span(const struct tok_state *tok, const char *start, const char *end)
110113
{
111-
token->level = tok->level;
112-
token->lineno = token->end_lineno = tok->lineno;
113-
token->col_offset = col_offset;
114-
token->end_col_offset = end_col_offset;
115-
token->start = start;
116-
token->end = end;
117-
return type;
114+
if (start == NULL) {
115+
assert(end == NULL);
116+
return (_PyTok_Span){-1, -1};
117+
}
118+
assert(end != NULL);
119+
const char *base = tok->buf;
120+
assert(base != NULL);
121+
assert(tok->inp >= base);
122+
Py_ssize_t start_offset = start - base;
123+
Py_ssize_t end_offset = end - base;
124+
assert(start_offset >= 0 && start_offset <= end_offset);
125+
assert(end_offset <= tok->inp - base);
126+
assert(tok->buf_offset <= PY_SSIZE_T_MAX - end_offset);
127+
return _PyTok_SpanFromBounds(
128+
tok->buf_offset + start_offset, tok->buf_offset + end_offset);
118129
}
119130

120131
int
121132
_PyLexer_token_setup(struct tok_state *tok, struct token *token, int type, const char *start, const char *end)
122133
{
123-
assert((start == NULL && end == NULL) || (start != NULL && end != NULL));
124134
token->level = tok->level;
125-
if (ISSTRINGLIT(type)) {
126-
token->lineno = tok->first_lineno;
127-
}
128-
else {
129-
token->lineno = tok->lineno;
130-
}
131-
token->end_lineno = tok->lineno;
132-
token->col_offset = token->end_col_offset = -1;
133-
token->start = start;
134-
token->end = end;
135+
token->span = buffer_span(tok, start, end);
136+
int lineno = ISSTRINGLIT(type) ? tok->first_lineno : tok->lineno;
137+
token->start_loc = (_PyTok_Loc){lineno, -1};
138+
token->end_loc = (_PyTok_Loc){tok->lineno, -1};
135139

136140
if (start != NULL && end != NULL) {
137-
token->col_offset = tok->starting_col_offset;
138-
token->end_col_offset = tok->col_offset;
141+
token->start_loc.byte_col = tok->starting_col_offset;
142+
token->end_loc.byte_col = tok->col_offset;
139143
}
140144
return type;
141145
}

Parser/lexer/state.h

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#define _PY_LEXER_H_
33

44
#include "object.h"
5-
#include "../tokenizer/cursor.h"
5+
#include "../tokenizer/source.h"
66

77
#define MAXINDENT 100 /* Max indentation level */
88
#define MAXLEVEL 200 /* Max parentheses level */
@@ -23,8 +23,9 @@ enum interactive_underflow_t {
2323

2424
struct token {
2525
int level;
26-
int lineno, col_offset, end_lineno, end_col_offset;
27-
const char *start, *end;
26+
_PyTok_Span span;
27+
_PyTok_Loc start_loc;
28+
_PyTok_Loc end_loc;
2829
PyObject *metadata;
2930
};
3031

@@ -69,7 +70,7 @@ typedef struct _tokenizer_mode {
6970
struct tok_state {
7071
/* Input state; buf <= cur <= inp */
7172
/* NB an entire line is held in the buffer */
72-
char *buf; /* Input buffer, or NULL; malloc'ed if fp != NULL or readline != NULL */
73+
char *buf; /* Owned for file/readline input; source-backed otherwise. */
7374
char *cur; /* Next character in buffer */
7475
char *inp; /* End of data in buffer */
7576
_PyTok_Off buf_offset; /* Logical offset of buf[0]. */
@@ -110,7 +111,6 @@ struct tok_state {
110111
char* str; /* Source string being tokenized (if tokenizing from a string)*/
111112

112113
_PyTok_SourceText source;
113-
_PyTok_Cursor reader_cursor;
114114
struct _PyTok_Reader *reader;
115115

116116
int type_comments; /* Whether to look for type comments */
@@ -129,8 +129,6 @@ struct tok_state {
129129
#endif
130130
};
131131

132-
int _PyLexer_type_comment_token_setup(struct tok_state *tok, struct token *token, int type, int col_offset,
133-
int end_col_offset, const char *start, const char *end);
134132
int _PyLexer_token_setup(struct tok_state *tok, struct token *token, int type, const char *start, const char *end);
135133

136134
struct tok_state *_PyTokenizer_tok_new(void);

Parser/pegen.c

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -171,18 +171,17 @@ growable_comment_array_deallocate(growable_comment_array *arr) {
171171
}
172172

173173
static int
174-
_get_keyword_or_name_type(Parser *p, struct token *new_token)
174+
_get_keyword_or_name_type(Parser *p, const char *text, Py_ssize_t length)
175175
{
176-
Py_ssize_t name_len = new_token->end_col_offset - new_token->col_offset;
177-
assert(name_len > 0);
176+
assert(length > 0);
178177

179-
if (name_len >= p->n_keyword_lists ||
180-
p->keywords[name_len] == NULL ||
181-
p->keywords[name_len]->type == -1) {
178+
if (length >= p->n_keyword_lists ||
179+
p->keywords[length] == NULL ||
180+
p->keywords[length]->type == -1) {
182181
return NAME;
183182
}
184-
for (KeywordToken *k = p->keywords[name_len]; k != NULL && k->type != -1; k++) {
185-
if (strncmp(k->str, new_token->start, (size_t)name_len) == 0) {
183+
for (KeywordToken *k = p->keywords[length]; k != NULL && k->type != -1; k++) {
184+
if (memcmp(k->str, text, (size_t)length) == 0) {
186185
return k->type;
187186
}
188187
}
@@ -193,8 +192,11 @@ static int
193192
initialize_token(Parser *p, Token *parser_token, struct token *new_token, int token_type) {
194193
assert(parser_token != NULL);
195194

196-
parser_token->type = (token_type == NAME) ? _get_keyword_or_name_type(p, new_token) : token_type;
197-
parser_token->bytes = PyBytes_FromStringAndSize(new_token->start, new_token->end - new_token->start);
195+
Py_ssize_t length;
196+
const char *text = _PyToken_TextView(p->tok, new_token, &length);
197+
parser_token->type = token_type == NAME
198+
? _get_keyword_or_name_type(p, text, length) : token_type;
199+
parser_token->bytes = PyBytes_FromStringAndSize(text, length);
198200
if (parser_token->bytes == NULL) {
199201
return -1;
200202
}
@@ -214,12 +216,14 @@ initialize_token(Parser *p, Token *parser_token, struct token *new_token, int to
214216
}
215217

216218
parser_token->level = new_token->level;
217-
parser_token->lineno = new_token->lineno;
218-
parser_token->col_offset = p->tok->lineno == p->starting_lineno ? p->starting_col_offset + new_token->col_offset
219-
: new_token->col_offset;
220-
parser_token->end_lineno = new_token->end_lineno;
221-
parser_token->end_col_offset = p->tok->lineno == p->starting_lineno ? p->starting_col_offset + new_token->end_col_offset
222-
: new_token->end_col_offset;
219+
parser_token->lineno = new_token->start_loc.lineno;
220+
parser_token->col_offset = p->tok->lineno == p->starting_lineno
221+
? p->starting_col_offset + new_token->start_loc.byte_col
222+
: new_token->start_loc.byte_col;
223+
parser_token->end_lineno = new_token->end_loc.lineno;
224+
parser_token->end_col_offset = p->tok->lineno == p->starting_lineno
225+
? p->starting_col_offset + new_token->end_loc.byte_col
226+
: new_token->end_loc.byte_col;
223227

224228
p->fill += 1;
225229

@@ -261,13 +265,14 @@ _PyPegen_fill_token(Parser *p)
261265

262266
// Record and skip '# type: ignore' comments
263267
while (type == TYPE_IGNORE) {
264-
Py_ssize_t len = new_token.end_col_offset - new_token.col_offset;
268+
Py_ssize_t len;
269+
const char *text = _PyToken_TextView(p->tok, &new_token, &len);
265270
char *tag = PyMem_Malloc((size_t)len + 1);
266271
if (tag == NULL) {
267272
PyErr_NoMemory();
268273
goto error;
269274
}
270-
strncpy(tag, new_token.start, (size_t)len);
275+
memcpy(tag, text, (size_t)len);
271276
tag[len] = '\0';
272277
// Ownership of tag passes to the growable array
273278
if (!growable_comment_array_add(&p->type_ignore_comments, p->tok->lineno, tag)) {

Parser/tokenizer/source.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
typedef Py_ssize_t _PyTok_Off;
77

8-
/* Half-open byte offsets into a _PyTok_SourceText. */
8+
/* Spans use half-open logical byte offsets into decoded input. Their backing
9+
storage may retain only the current input window. */
910
typedef struct {
1011
_PyTok_Off start;
1112
_PyTok_Off end;

Python/Python-tokenize.c

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -203,14 +203,15 @@ _get_current_line(tokenizeriterobject *it, const char *line_start, Py_ssize_t si
203203
}
204204

205205
static void
206-
_get_col_offsets(tokenizeriterobject *it, struct token token, const char *line_start,
207-
PyObject *line, int line_changed, Py_ssize_t lineno, Py_ssize_t end_lineno,
206+
_get_col_offsets(tokenizeriterobject *it, const char *token_start,
207+
const char *token_end, const char *line_start, PyObject *line,
208+
int line_changed, Py_ssize_t lineno, Py_ssize_t end_lineno,
208209
Py_ssize_t *col_offset, Py_ssize_t *end_col_offset)
209210
{
210211
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(it);
211212
Py_ssize_t byte_offset = -1;
212-
if (token.start != NULL && token.start >= line_start) {
213-
byte_offset = token.start - line_start;
213+
if (token_start != NULL && token_start >= line_start) {
214+
byte_offset = token_start - line_start;
214215
if (line_changed) {
215216
*col_offset = _PyPegen_byte_offset_to_character_offset_line(line, 0, byte_offset);
216217
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
220221
}
221222
}
222223

223-
if (token.end != NULL && token.end >= it->tok->line_start) {
224-
Py_ssize_t end_byte_offset = token.end - it->tok->line_start;
224+
if (token_end != NULL && token_end >= it->tok->line_start) {
225+
Py_ssize_t end_byte_offset = token_end - it->tok->line_start;
225226
if (lineno == end_lineno) {
226-
// If the whole token is at the same line, we can just use the token.start
227-
// buffer for figuring out the new column offset, since using line is not
228-
// performant for very long lines.
227+
// Avoid rescanning the prefix of a very long line.
229228
Py_ssize_t token_col_offset = _PyPegen_byte_offset_to_character_offset_line(line, byte_offset, end_byte_offset);
230229
*end_col_offset = *col_offset + token_col_offset;
231-
it->byte_col_offset_diff += token.end - token.start - token_col_offset;
230+
it->byte_col_offset_diff += token_end - token_start - token_col_offset;
232231
}
233232
else {
234233
*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)
263262
it->done = 1;
264263
goto exit;
265264
}
266-
PyObject *str = NULL;
267-
if (token.start == NULL || token.end == NULL) {
265+
const char *token_start = NULL;
266+
const char *token_end = NULL;
267+
PyObject *str;
268+
if (!_PyTok_SpanIsValid(token.span)) {
268269
str = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
269270
}
270271
else {
271-
str = PyUnicode_FromStringAndSize(token.start, token.end - token.start);
272+
Py_ssize_t token_length;
273+
token_start = _PyToken_TextView(
274+
it->tok, &token, &token_length);
275+
token_end = token_start + token_length;
276+
str = PyUnicode_FromStringAndSize(token_start, token_length);
272277
}
273278
if (str == NULL) {
274279
goto exit;
@@ -297,11 +302,11 @@ tokenizeriter_next(PyObject *op)
297302
goto exit;
298303
}
299304

300-
Py_ssize_t lineno = ISSTRINGLIT(type) ? it->tok->first_lineno : it->tok->lineno;
301-
Py_ssize_t end_lineno = it->tok->lineno;
305+
Py_ssize_t lineno = token.start_loc.lineno;
306+
Py_ssize_t end_lineno = token.end_loc.lineno;
302307
Py_ssize_t col_offset = -1;
303308
Py_ssize_t end_col_offset = -1;
304-
_get_col_offsets(it, token, line_start, line, line_changed,
309+
_get_col_offsets(it, token_start, token_end, line_start, line, line_changed,
305310
lineno, end_lineno, &col_offset, &end_col_offset);
306311

307312
if (it->tok->tok_extra_tokens) {
@@ -317,7 +322,8 @@ tokenizeriter_next(PyObject *op)
317322
else if (type == NEWLINE) {
318323
Py_DECREF(str);
319324
if (!it->tok->implicit_newline) {
320-
if (it->tok->start[0] == '\r') {
325+
assert(token_start != NULL);
326+
if (token_start[0] == '\r') {
321327
str = PyUnicode_FromString("\r\n");
322328
} else {
323329
str = PyUnicode_FromString("\n");

0 commit comments

Comments
 (0)