@@ -572,11 +572,41 @@ _PyPegen_name_from_token(Parser *p, Token* t)
572572 p -> error_indicator = 1 ;
573573 return NULL ;
574574 }
575+ // Identifiers repeat constantly; a small span-keyed cache skips the
576+ // UTF-8 decode + intern for repeated occurrences. Keys point into
577+ // arena-owned token bytes and values are arena-owned interned strings,
578+ // so borrowed references are valid for the lifetime of the parse
579+ // (including the second error pass, which reuses parser and arena).
580+ Py_ssize_t len = PyBytes_GET_SIZE (t -> bytes );
581+ uint32_t hash = 2166136261u ;
582+ for (Py_ssize_t i = 0 ; i < len ; i ++ ) {
583+ hash = (hash ^ (unsigned char )s [i ]) * 16777619u ;
584+ }
585+ struct _identifier_cache_entry * free_slot = NULL ;
586+ size_t idx = hash & (_PYPEGEN_IDENT_CACHE_SIZE - 1 );
587+ for (int probe = 0 ; probe < _PYPEGEN_IDENT_CACHE_MAX_PROBES ; probe ++ ) {
588+ struct _identifier_cache_entry * e =
589+ & p -> ident_cache [(idx + probe ) & (_PYPEGEN_IDENT_CACHE_SIZE - 1 )];
590+ if (e -> key == NULL ) {
591+ free_slot = e ;
592+ break ;
593+ }
594+ if (e -> hash == hash && e -> len == len && memcmp (e -> key , s , len ) == 0 ) {
595+ return _PyAST_Name (e -> value , Load , t -> lineno , t -> col_offset ,
596+ t -> end_lineno , t -> end_col_offset , p -> arena );
597+ }
598+ }
575599 PyObject * id = _PyPegen_new_identifier (p , s );
576600 if (id == NULL ) {
577601 p -> error_indicator = 1 ;
578602 return NULL ;
579603 }
604+ if (free_slot != NULL ) {
605+ free_slot -> key = s ;
606+ free_slot -> len = len ;
607+ free_slot -> hash = hash ;
608+ free_slot -> value = id ;
609+ }
580610 return _PyAST_Name (id , Load , t -> lineno , t -> col_offset , t -> end_lineno ,
581611 t -> end_col_offset , p -> arena );
582612}
@@ -844,6 +874,15 @@ _PyPegen_Parser_New(struct tok_state *tok, int start_rule, int flags,
844874 p -> flags = flags ;
845875 p -> feature_version = feature_version ;
846876 p -> known_err_token = NULL ;
877+ p -> ident_cache = PyMem_Calloc (_PYPEGEN_IDENT_CACHE_SIZE ,
878+ sizeof (struct _identifier_cache_entry ));
879+ if (p -> ident_cache == NULL ) {
880+ growable_comment_array_deallocate (& p -> type_ignore_comments );
881+ PyMem_Free (p -> tokens [0 ]);
882+ PyMem_Free (p -> tokens );
883+ PyMem_Free (p );
884+ return (Parser * ) PyErr_NoMemory ();
885+ }
847886 p -> level = 0 ;
848887 p -> call_invalid_rules = 0 ;
849888 p -> last_stmt_location .lineno = 0 ;
@@ -859,6 +898,7 @@ _PyPegen_Parser_New(struct tok_state *tok, int start_rule, int flags,
859898void
860899_PyPegen_Parser_Free (Parser * p )
861900{
901+ PyMem_Free (p -> ident_cache );
862902 Py_XDECREF (p -> normalize );
863903 for (int i = 0 ; i < p -> size ; i ++ ) {
864904 PyMem_Free (p -> tokens [i ]);
0 commit comments