GeezerVison is showing github.com — gh-153569: return tokenizer tokens as source spans · python/cpython@f134e81
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: Parser/lexer/lexer.c +8-6Lines changed: 8 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 12 | 12 |
| 13 | 13 |
| 14 | 14 | #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)) | 17 | 15 |
| 18 | 16 | /* Spaces in this constant are treated as "zero or more spaces or tabs" when | 19 | 17 | tokenizing. */ | @@ -360,21 +358,25 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str | 360 | 358 | && !(tok->cur > ignore_end | 361 | 359 | && ((unsigned char)ignore_end[0] >= 128 || Py_ISALNUM(ignore_end[0])))); | 362 | 360 |
| | 361 | + int type = is_type_ignore ? TYPE_IGNORE : TYPE_COMMENT; | | 362 | + int start_col_offset = is_type_ignore | | 363 | + ? ignore_end_col_offset : current_starting_col_offset; | | 364 | + p_end = tok->cur; | 363 | 365 | if (is_type_ignore) { | 364 | 366 | p_start = ignore_end; | 365 | | - p_end = tok->cur; | 366 | 367 |
| 367 | 368 | /* If this type ignore is the only thing on the line, consume the newline also. */ | 368 | 369 | if (blankline) { | 369 | 370 | tok_nextc(tok); | 370 | 371 | tok->atbol = 1; | 371 | 372 | } | 372 | | - return MAKE_TYPE_COMMENT_TOKEN(TYPE_IGNORE, ignore_end_col_offset, tok->col_offset); | 373 | 373 | } else { | 374 | 374 | p_start = type_start; | 375 | | - p_end = tok->cur; | 376 | | - return MAKE_TYPE_COMMENT_TOKEN(TYPE_COMMENT, current_starting_col_offset, tok->col_offset); | 377 | 375 | } | | 376 | + _PyLexer_token_setup(tok, token, type, p_start, p_end); | | 377 | + token->start_loc = (_PyTok_Loc){tok->lineno, start_col_offset}; | | 378 | + token->end_loc = (_PyTok_Loc){tok->lineno, tok->col_offset}; | | 379 | + return type; | 378 | 380 | } | 379 | 381 | } | 380 | 382 | if (tok->tok_extra_tokens) { | |
Collapse file Copy file name to clipboardExpand all lines: Parser/lexer/lexer.h +21Lines changed: 21 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -7,4 +7,25 @@ int _PyLexer_update_ftstring_expr(struct tok_state *tok, char cur); | 7 | 7 |
| 8 | 8 | int _PyTokenizer_Get(struct tok_state *, struct token *); | 9 | 9 |
| | 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 (token->span.start < 0) { | | 18 | + assert(token->span.start == -1 && token->span.end == -1); | | 19 | + *length = 0; | | 20 | + return ""; | | 21 | + } | | 22 | + assert(_PyTok_SpanIsValid(token->span)); | | 23 | + assert(tok->buf != NULL); | | 24 | + assert(tok->inp >= tok->buf); | | 25 | + assert(token->span.start >= tok->buf_offset); | | 26 | + assert(token->span.end - tok->buf_offset <= tok->inp - tok->buf); | | 27 | + *length = token->span.end - token->span.start; | | 28 | + return tok->buf + (token->span.start - tok->buf_offset); | | 29 | +} | | 30 | + | 10 | 31 | #endif |
Collapse file Copy file name to clipboardExpand all lines: Parser/lexer/state.c +28-23Lines changed: 28 additions & 23 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -100,41 +100,46 @@ _PyToken_Free(struct token *token) { | 100 | 100 |
| 101 | 101 | void | 102 | 102 | _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 | 103 | 108 | token->metadata = NULL; | 104 | 109 | } | 105 | 110 |
| 106 | | -int | 107 | | -_PyLexer_type_comment_token_setup(struct tok_state *tok, struct token *token, int type, int col_offset, | 108 | | - 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) | 109 | 113 | { | 110 | | - token->level = tok->level; | 111 | | - token->lineno = token->end_lineno = tok->lineno; | 112 | | - token->col_offset = col_offset; | 113 | | - token->end_col_offset = end_col_offset; | 114 | | - token->start = start; | 115 | | - token->end = end; | 116 | | - 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); | 117 | 129 | } | 118 | 130 |
| 119 | 131 | int | 120 | 132 | _PyLexer_token_setup(struct tok_state *tok, struct token *token, int type, const char *start, const char *end) | 121 | 133 | { | 122 | | - assert((start == NULL && end == NULL) || (start != NULL && end != NULL)); | 123 | 134 | token->level = tok->level; | 124 | | - if (ISSTRINGLIT(type)) { | 125 | | - token->lineno = tok->first_lineno; | 126 | | - } | 127 | | - else { | 128 | | - token->lineno = tok->lineno; | 129 | | - } | 130 | | - token->end_lineno = tok->lineno; | 131 | | - token->col_offset = token->end_col_offset = -1; | 132 | | - token->start = start; | 133 | | - 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}; | 134 | 139 |
| 135 | 140 | if (start != NULL && end != NULL) { | 136 | | - token->col_offset = tok->starting_col_offset; | 137 | | - 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; | 138 | 143 | } | 139 | 144 | return type; | 140 | 145 | } |
Collapse file Copy file name to clipboardExpand all lines: Parser/lexer/state.h +4-5Lines changed: 4 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -23,8 +23,9 @@ enum interactive_underflow_t { | 23 | 23 |
| 24 | 24 | struct token { | 25 | 25 | 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; | 28 | 29 | PyObject *metadata; | 29 | 30 | }; | 30 | 31 |
| @@ -69,7 +70,7 @@ typedef struct _tokenizer_mode { | 69 | 70 | struct tok_state { | 70 | 71 | /* Input state; buf <= cur <= inp */ | 71 | 72 | /* 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. */ | 73 | 74 | char *cur; /* Next character in buffer */ | 74 | 75 | char *inp; /* End of data in buffer */ | 75 | 76 | _PyTok_Off buf_offset; /* Logical offset of buf[0]. */ | @@ -128,8 +129,6 @@ struct tok_state { | 128 | 129 | #endif | 129 | 130 | }; | 130 | 131 |
| 131 | | -int _PyLexer_type_comment_token_setup(struct tok_state *tok, struct token *token, int type, int col_offset, | 132 | | - int end_col_offset, const char *start, const char *end); | 133 | 132 | int _PyLexer_token_setup(struct tok_state *tok, struct token *token, int type, const char *start, const char *end); | 134 | 133 |
| 135 | 134 | struct tok_state *_PyTokenizer_tok_new(void); | |
Collapse file Copy file name to clipboardExpand all lines: Parser/pegen.c +23-18Lines changed: 23 additions & 18 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -171,18 +171,17 @@ growable_comment_array_deallocate(growable_comment_array *arr) { | 171 | 171 | } | 172 | 172 |
| 173 | 173 | 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) | 175 | 175 | { | 176 | | - Py_ssize_t name_len = new_token->end_col_offset - new_token->col_offset; | 177 | | - assert(name_len > 0); | | 176 | + assert(length > 0); | 178 | 177 |
| 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) { | 182 | 181 | return NAME; | 183 | 182 | } | 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) { | 186 | 185 | return k->type; | 187 | 186 | } | 188 | 187 | } | @@ -193,8 +192,11 @@ static int | 193 | 192 | initialize_token(Parser *p, Token *parser_token, struct token *new_token, int token_type) { | 194 | 193 | assert(parser_token != NULL); | 195 | 194 |
| 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); | 198 | 200 | if (parser_token->bytes == NULL) { | 199 | 201 | return -1; | 200 | 202 | } | @@ -214,12 +216,14 @@ initialize_token(Parser *p, Token *parser_token, struct token *new_token, int to | 214 | 216 | } | 215 | 217 |
| 216 | 218 | 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; | 223 | 227 |
| 224 | 228 | p->fill += 1; | 225 | 229 |
| @@ -261,13 +265,14 @@ _PyPegen_fill_token(Parser *p) | 261 | 265 |
| 262 | 266 | // Record and skip '# type: ignore' comments | 263 | 267 | 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); | 265 | 270 | char *tag = PyMem_Malloc((size_t)len + 1); | 266 | 271 | if (tag == NULL) { | 267 | 272 | PyErr_NoMemory(); | 268 | 273 | goto error; | 269 | 274 | } | 270 | | - strncpy(tag, new_token.start, (size_t)len); | | 275 | + memcpy(tag, text, (size_t)len); | 271 | 276 | tag[len] = '\0'; | 272 | 277 | // Ownership of tag passes to the growable array | 273 | 278 | if (!growable_comment_array_add(&p->type_ignore_comments, p->tok->lineno, tag)) { | |
Collapse file Copy file name to clipboardExpand all lines: Parser/tokenizer/source.h +2-1Lines changed: 2 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 5 | 5 |
| 6 | 6 | typedef Py_ssize_t _PyTok_Off; | 7 | 7 |
| 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. */ | 9 | 10 | typedef struct { | 10 | 11 | _PyTok_Off start; | 11 | 12 | _PyTok_Off end; | |
Collapse file Copy file name to clipboardExpand all lines: Python/Python-tokenize.c +27-18Lines changed: 27 additions & 18 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -203,14 +203,19 @@ _get_current_line(tokenizeriterobject *it, const char *line_start, Py_ssize_t si | 203 | 203 | } | 204 | 204 |
| 205 | 205 | 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 struct token *token, | | 207 | + const char *token_start, const char *line_start, | | 208 | + PyObject *line, int line_changed, | 208 | 209 | Py_ssize_t *col_offset, Py_ssize_t *end_col_offset) | 209 | 210 | { | 210 | 211 | _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(it); | | 212 | + const char *token_end = token_start == NULL | | 213 | + ? NULL : token_start + token->span.end - token->span.start; | | 214 | + Py_ssize_t lineno = token->start_loc.lineno; | | 215 | + Py_ssize_t end_lineno = token->end_loc.lineno; | 211 | 216 | Py_ssize_t byte_offset = -1; | 212 | | - if (token.start != NULL && token.start >= line_start) { | 213 | | - byte_offset = token.start - line_start; | | 217 | + if (token_start != NULL && token_start >= line_start) { | | 218 | + byte_offset = token_start - line_start; | 214 | 219 | if (line_changed) { | 215 | 220 | *col_offset = _PyPegen_byte_offset_to_character_offset_line(line, 0, byte_offset); | 216 | 221 | it->byte_col_offset_diff = byte_offset - *col_offset; | @@ -220,15 +225,13 @@ _get_col_offsets(tokenizeriterobject *it, struct token token, const char *line_s | 220 | 225 | } | 221 | 226 | } | 222 | 227 |
| 223 | | - if (token.end != NULL && token.end >= it->tok->line_start) { | 224 | | - Py_ssize_t end_byte_offset = token.end - it->tok->line_start; | | 228 | + if (token_end != NULL && token_end >= it->tok->line_start) { | | 229 | + Py_ssize_t end_byte_offset = token_end - it->tok->line_start; | 225 | 230 | 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. | | 231 | + // Avoid rescanning the prefix of a very long line. | 229 | 232 | Py_ssize_t token_col_offset = _PyPegen_byte_offset_to_character_offset_line(line, byte_offset, end_byte_offset); | 230 | 233 | *end_col_offset = *col_offset + token_col_offset; | 231 | | - it->byte_col_offset_diff += token.end - token.start - token_col_offset; | | 234 | + it->byte_col_offset_diff += token_end - token_start - token_col_offset; | 232 | 235 | } | 233 | 236 | else { | 234 | 237 | *end_col_offset = _PyPegen_byte_offset_to_character_offset_raw(it->tok->line_start, end_byte_offset); | @@ -263,12 +266,17 @@ tokenizeriter_next(PyObject *op) | 263 | 266 | it->done = 1; | 264 | 267 | goto exit; | 265 | 268 | } | 266 | | - PyObject *str = NULL; | 267 | | - if (token.start == NULL || token.end == NULL) { | | 269 | + const char *token_start = NULL; | | 270 | + PyObject *str; | | 271 | + if (token.span.start < 0) { | | 272 | + assert(token.span.start == -1 && token.span.end == -1); | 268 | 273 | str = Py_GetConstant(Py_CONSTANT_EMPTY_STR); | 269 | 274 | } | 270 | 275 | else { | 271 | | - str = PyUnicode_FromStringAndSize(token.start, token.end - token.start); | | 276 | + Py_ssize_t token_length; | | 277 | + token_start = _PyToken_TextView( | | 278 | + it->tok, &token, &token_length); | | 279 | + str = PyUnicode_FromStringAndSize(token_start, token_length); | 272 | 280 | } | 273 | 281 | if (str == NULL) { | 274 | 282 | goto exit; | @@ -297,12 +305,12 @@ tokenizeriter_next(PyObject *op) | 297 | 305 | goto exit; | 298 | 306 | } | 299 | 307 |
| 300 | | - Py_ssize_t lineno = ISSTRINGLIT(type) ? it->tok->first_lineno : it->tok->lineno; | 301 | | - Py_ssize_t end_lineno = it->tok->lineno; | | 308 | + Py_ssize_t lineno = token.start_loc.lineno; | | 309 | + Py_ssize_t end_lineno = token.end_loc.lineno; | 302 | 310 | Py_ssize_t col_offset = -1; | 303 | 311 | Py_ssize_t end_col_offset = -1; | 304 | | - _get_col_offsets(it, token, line_start, line, line_changed, | 305 | | - lineno, end_lineno, &col_offset, &end_col_offset); | | 312 | + _get_col_offsets(it, &token, token_start, line_start, line, line_changed, | | 313 | + &col_offset, &end_col_offset); | 306 | 314 |
| 307 | 315 | if (it->tok->tok_extra_tokens) { | 308 | 316 | if (is_trailing_token) { | @@ -317,7 +325,8 @@ tokenizeriter_next(PyObject *op) | 317 | 325 | else if (type == NEWLINE) { | 318 | 326 | Py_DECREF(str); | 319 | 327 | if (!it->tok->implicit_newline) { | 320 | | - if (it->tok->start[0] == '\r') { | | 328 | + assert(token_start != NULL); | | 329 | + if (token_start[0] == '\r') { | 321 | 330 | str = PyUnicode_FromString("\r\n"); | 322 | 331 | } else { | 323 | 332 | str = PyUnicode_FromString("\n"); | |
|