@@ -681,13 +681,22 @@ class Grammar(Serialize):
681681 term_defs : List [Tuple [str , Tuple [Tree , int ]]]
682682 rule_defs : List [Tuple [str , Tuple [str , ...], Tree , RuleOptions ]]
683683 ignore : List [str ]
684+ term_references : Dict [str , List [str ]]
684685
685- def __init__ (self , rule_defs : List [Tuple [str , Tuple [str , ...], Tree , RuleOptions ]], term_defs : List [Tuple [str , Tuple [Tree , int ]]], ignore : List [str ]) -> None :
686+ def __init__ (self , rule_defs : List [Tuple [str , Tuple [str , ...], Tree , RuleOptions ]], term_defs : List [Tuple [str , Tuple [Tree , int ]]], ignore : List [str ], term_references : Optional [ Dict [ str , List [ str ]]] = None ) -> None :
686687 self .term_defs = term_defs
687688 self .rule_defs = rule_defs
688689 self .ignore = ignore
690+ self .term_references = term_references or {}
689691
690- __serialize_fields__ = 'term_defs' , 'rule_defs' , 'ignore'
692+ __serialize_fields__ = 'term_defs' , 'rule_defs' , 'ignore' , 'term_references'
693+
694+ @classmethod
695+ def deserialize (cls , data , memo ):
696+ if 'term_references' not in data :
697+ data = dict (data )
698+ data ['term_references' ] = {}
699+ return super ().deserialize (data , memo )
691700
692701 def compile (self , start , terminals_to_keep ) -> Tuple [List [TerminalDef ], List [Rule ], List [str ]]:
693702 # We change the trees in-place (to support huge grammars)
@@ -810,9 +819,12 @@ def compile(self, start, terminals_to_keep) -> Tuple[List[TerminalDef], List[Rul
810819 used_terms = {t .name for r in compiled_rules
811820 for t in r .expansion
812821 if isinstance (t , Terminal )}
822+ kept_terms = used_terms | set (self .ignore ) | set (terminals_to_keep )
823+ reachable_terms = set (bfs (kept_terms , lambda name : self .term_references .get (name , ())))
813824 terminals , unused = classify_bool (terminals , lambda t : t .name in used_terms or t .name in self .ignore or t .name in terminals_to_keep )
814- if unused :
815- logger .debug ("Unused terminals: %s" , [t .name for t in unused ])
825+ unused_names = [t .name for t in unused if t .name not in reachable_terms ]
826+ if unused_names :
827+ logger .debug ("Unused terminals: %s" , unused_names )
816828
817829 return terminals , compiled_rules , self .ignore
818830
@@ -1084,6 +1096,7 @@ def __init__(self, is_term, tree, params=(), options=None):
10841096 self .tree = tree
10851097 self .params = tuple (params )
10861098 self .options = options
1099+ self .term_references = _find_used_symbols (tree ) if is_term and tree is not None else set ()
10871100
10881101class GrammarBuilder :
10891102
@@ -1155,6 +1168,8 @@ def _extend(self, name, is_term, exp, params=(), options=None):
11551168
11561169 # TODO: think about what to do with 'options'
11571170 base = d .tree
1171+ if is_term :
1172+ d .term_references .update (_find_used_symbols (exp ))
11581173
11591174 assert isinstance (base , Tree ) and base .data == 'expansions'
11601175 base .children .insert (0 , exp )
@@ -1380,15 +1395,17 @@ def build(self) -> Grammar:
13801395 self .validate ()
13811396 rule_defs = []
13821397 term_defs = []
1398+ term_references = {}
13831399 for name , d in self ._definitions .items ():
13841400 (params , exp , options ) = d .params , d .tree , d .options
13851401 if d .is_term :
13861402 assert len (params ) == 0
13871403 term_defs .append ((name , (exp , options )))
1404+ term_references [name ] = sorted (d .term_references )
13881405 else :
13891406 rule_defs .append ((name , params , exp , options ))
13901407 # resolve_term_references(term_defs)
1391- return Grammar (rule_defs , term_defs , self ._ignore_names )
1408+ return Grammar (rule_defs , term_defs , self ._ignore_names , term_references )
13921409
13931410
13941411def verify_used_files (file_hashes ):
0 commit comments