Skip to content

Commit ac85f97

Browse files
committed
gh-153568: Simplify parser identifier and buffer handling
1 parent 5252627 commit ac85f97

4 files changed

Lines changed: 56 additions & 61 deletions

File tree

Parser/pegen.c

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -628,35 +628,11 @@ _PyPegen_new_identifier(Parser *p, const char *n)
628628
return NULL;
629629
}
630630

631-
static expr_ty
632-
name_from_identifier(Parser *p, Token *t, PyObject *id)
633-
{
634-
expr_ty result = _PyAST_Name(id, Load, t->lineno, t->col_offset,
635-
t->end_lineno, t->end_col_offset, p->arena);
636-
if (result != NULL && _PyPegen_insert_memo(p, p->mark - 1, NAME, result) < 0) {
637-
p->error_indicator = 1;
638-
return NULL;
639-
}
640-
return result;
641-
}
642-
643-
static expr_ty
644-
_PyPegen_name_from_token(Parser *p, Token* t)
631+
// Return an arena-owned identifier; callers borrow the reference.
632+
static PyObject *
633+
get_cached_identifier(Parser *p, PyObject *bytes)
645634
{
646-
if (t == NULL) {
647-
return NULL;
648-
}
649-
// Reuse the AST node when backtracking revisits this token. Memo lookup
650-
// starts before the token and restores the position after it on a hit.
651-
// Token kinds can be memo keys: generated grammar rule IDs start at 1000.
652-
int mark = p->mark - 1;
653-
p->mark = mark;
654-
expr_ty cached = NULL;
655-
if (_PyPegen_is_memoized(p, NAME, &cached)) {
656-
return cached;
657-
}
658-
p->mark = mark + 1;
659-
const char *s = PyBytes_AsString(t->bytes);
635+
const char *s = PyBytes_AsString(bytes);
660636
if (!s) {
661637
p->error_indicator = 1;
662638
return NULL;
@@ -666,8 +642,8 @@ _PyPegen_name_from_token(Parser *p, Token* t)
666642
// arena-owned token bytes and values are arena-owned interned strings,
667643
// so borrowed references are valid for the lifetime of the parse
668644
// (including the second error pass, which reuses parser and arena).
669-
Py_ssize_t len = PyBytes_GET_SIZE(t->bytes);
670-
Py_hash_t hash = PyObject_Hash(t->bytes);
645+
Py_ssize_t len = PyBytes_GET_SIZE(bytes);
646+
Py_hash_t hash = PyObject_Hash(bytes);
671647
if (hash == -1) {
672648
p->error_indicator = 1;
673649
return NULL;
@@ -678,7 +654,7 @@ _PyPegen_name_from_token(Parser *p, Token* t)
678654
IDENTIFIER_CACHE_SIZE, sizeof(*p->identifier_cache));
679655
if (p->identifier_cache == NULL) {
680656
p->error_indicator = 1;
681-
return (expr_ty)PyErr_NoMemory();
657+
return PyErr_NoMemory();
682658
}
683659
}
684660
IdentifierCacheEntry *free_slot = NULL;
@@ -693,7 +669,7 @@ _PyPegen_name_from_token(Parser *p, Token* t)
693669
if (entry->hash == hash && entry->len == len &&
694670
memcmp(entry->key, s, len) == 0)
695671
{
696-
return name_from_identifier(p, t, entry->value);
672+
return entry->value;
697673
}
698674
}
699675
PyObject *id = _PyPegen_new_identifier(p, s);
@@ -707,7 +683,36 @@ _PyPegen_name_from_token(Parser *p, Token* t)
707683
free_slot->hash = hash;
708684
free_slot->value = id;
709685
}
710-
return name_from_identifier(p, t, id);
686+
return id;
687+
}
688+
689+
static expr_ty
690+
_PyPegen_name_from_token(Parser *p, Token* t)
691+
{
692+
if (t == NULL) {
693+
return NULL;
694+
}
695+
// Reuse the AST node when backtracking revisits this token. Memo lookup
696+
// starts before the token and restores the position after it on a hit.
697+
// Token kinds can be memo keys: generated grammar rule IDs start at 1000.
698+
int mark = p->mark - 1;
699+
p->mark = mark;
700+
expr_ty cached = NULL;
701+
if (_PyPegen_is_memoized(p, NAME, &cached)) {
702+
return cached;
703+
}
704+
p->mark = mark + 1;
705+
PyObject *id = get_cached_identifier(p, t->bytes);
706+
if (id == NULL) {
707+
return NULL;
708+
}
709+
expr_ty result = _PyAST_Name(id, Load, t->lineno, t->col_offset,
710+
t->end_lineno, t->end_col_offset, p->arena);
711+
if (result != NULL && _PyPegen_insert_memo(p, mark, NAME, result) < 0) {
712+
p->error_indicator = 1;
713+
return NULL;
714+
}
715+
return result;
711716
}
712717

713718
expr_ty

Parser/tokenizer/decoder.c

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ normalize_newlines_into(char *result, const char *data, Py_ssize_t len,
8080
Py_ssize_t *out_len, int *implicit_newline)
8181
{
8282
Py_ssize_t write = 0;
83-
if (memchr(data, '\r', len) == NULL) {
84-
// No carriage returns: nothing to translate, copy verbatim.
83+
if (preserve_crlf || memchr(data, '\r', len) == NULL) {
84+
// No translation needed: copy verbatim.
8585
memcpy(result, data, len);
8686
write = len;
8787
}
8888
else {
8989
for (Py_ssize_t read = 0; read < len; read++) {
9090
char c = data[read];
91-
if (!preserve_crlf && c == '\r') {
91+
if (c == '\r') {
9292
if (read + 1 < len && data[read + 1] == '\n') {
9393
read++;
9494
}
@@ -341,24 +341,9 @@ store_prepared_source(struct tok_state *tok, const char *data, Py_ssize_t len,
341341
}
342342
// Reserve space for an optional final '\n' and the NUL terminator.
343343
Py_ssize_t needed = line_len + 2;
344-
if (needed > capacity) {
345-
// Grow geometrically to avoid reallocating for every longer line.
346-
Py_ssize_t next_capacity = capacity > 0 ? capacity : 256;
347-
while (next_capacity < needed) {
348-
if (next_capacity > PY_SSIZE_T_MAX / 2) {
349-
next_capacity = needed;
350-
break;
351-
}
352-
next_capacity *= 2;
353-
}
354-
char *resized = PyMem_Realloc(normalized, next_capacity);
355-
if (resized == NULL) {
356-
PyErr_NoMemory();
357-
tok->done = E_NOMEM;
358-
goto error;
359-
}
360-
normalized = resized;
361-
capacity = next_capacity;
344+
if (_PyTok_ReserveBuffer(&normalized, &capacity, needed, 256) < 0) {
345+
tok->done = E_NOMEM;
346+
goto error;
362347
}
363348
normalize_newlines_into(normalized, line, line_len,
364349
preserve_crlf, add_newline,

Parser/tokenizer/reader.c

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@ _PyTok_ReaderFree(struct tok_state *tok)
3737
tok->reader = NULL;
3838
}
3939

40-
static int
41-
reserve_buffer(char **buffer, Py_ssize_t *capacity, Py_ssize_t needed)
40+
int
41+
_PyTok_ReserveBuffer(char **buffer, Py_ssize_t *capacity, Py_ssize_t needed,
42+
Py_ssize_t initial_capacity)
4243
{
44+
assert(initial_capacity > 0);
4345
if (needed <= *capacity) {
4446
return 0;
4547
}
46-
Py_ssize_t cap = *capacity > 0 ? *capacity : BUFSIZ;
48+
// Grow geometrically to avoid reallocating for every longer line.
49+
Py_ssize_t cap = *capacity > 0 ? *capacity : initial_capacity;
4750
while (cap < needed) {
4851
if (cap > PY_SSIZE_T_MAX / 2) {
4952
cap = needed;
@@ -72,8 +75,8 @@ append_decoded(_PyTok_Reader *reader, const char *data, Py_ssize_t len)
7275
reader->decoded_len = remaining;
7376
}
7477
if (len < 0 || reader->decoded_len > PY_SSIZE_T_MAX - len - 1 ||
75-
reserve_buffer(&reader->decoded, &reader->decoded_cap,
76-
reader->decoded_len + len + 1) < 0) {
78+
_PyTok_ReserveBuffer(&reader->decoded, &reader->decoded_cap,
79+
reader->decoded_len + len + 1, BUFSIZ) < 0) {
7780
PyErr_NoMemory();
7881
return -1;
7982
}
@@ -160,8 +163,8 @@ read_file_line(struct tok_state *tok, _PyTok_Chunk *chunk)
160163
Py_ssize_t len = 0;
161164
for (;;) {
162165
if (len > PY_SSIZE_T_MAX - BUFSIZ ||
163-
reserve_buffer(&reader->file_buffer, &reader->file_buffer_cap,
164-
len + BUFSIZ) < 0) {
166+
_PyTok_ReserveBuffer(&reader->file_buffer, &reader->file_buffer_cap,
167+
len + BUFSIZ, BUFSIZ) < 0) {
165168
return _PYTOK_READ_ERROR;
166169
}
167170
int available = (int)Py_MIN(reader->file_buffer_cap - len, INT_MAX);

Parser/tokenizer/reader_internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ typedef struct _PyTok_Reader {
6363

6464
struct tok_state;
6565

66+
// On failure, the buffer and capacity are unchanged.
67+
int _PyTok_ReserveBuffer(char **, Py_ssize_t *, Py_ssize_t, Py_ssize_t);
6668
char *_PyTok_CopyBytes(const char *, Py_ssize_t);
6769
int _PyTok_DecodeOnce(
6870
struct tok_state *, _PyTok_Chunk *, const char *, const char *);

0 commit comments

Comments
 (0)