@@ -46,6 +46,17 @@ def lam_sub(grammar: Grammar, node: RawNode) -> NL:
4646 return Node (type = node [0 ], children = node [3 ], context = node [2 ])
4747
4848
49+ # A placeholder node, used when parser is backtracking.
50+ DUMMY_NODE = (- 1 , None , None , None )
51+
52+
53+ def stack_copy (
54+ stack : List [Tuple [DFAS , int , RawNode ]]
55+ ) -> List [Tuple [DFAS , int , RawNode ]]:
56+ """Nodeless stack copy."""
57+ return [(copy .deepcopy (dfa ), label , DUMMY_NODE ) for dfa , label , _ in stack ]
58+
59+
4960class Recorder :
5061 def __init__ (self , parser : "Parser" , ilabels : List [int ], context : Context ) -> None :
5162 self .parser = parser
@@ -54,21 +65,40 @@ def __init__(self, parser: "Parser", ilabels: List[int], context: Context) -> No
5465
5566 self ._dead_ilabels : Set [int ] = set ()
5667 self ._start_point = self .parser .stack
57- self ._points = {ilabel : copy . deepcopy (self ._start_point ) for ilabel in ilabels }
68+ self ._points = {ilabel : stack_copy (self ._start_point ) for ilabel in ilabels }
5869
5970 @property
6071 def ilabels (self ) -> Set [int ]:
6172 return self ._dead_ilabels .symmetric_difference (self ._ilabels )
6273
6374 @contextmanager
6475 def switch_to (self , ilabel : int ) -> Iterator [None ]:
65- self .parser .stack = self ._points [ilabel ]
76+ with self .backtrack ():
77+ self .parser .stack = self ._points [ilabel ]
78+ try :
79+ yield
80+ except ParseError :
81+ self ._dead_ilabels .add (ilabel )
82+ finally :
83+ self .parser .stack = self ._start_point
84+
85+ @contextmanager
86+ def backtrack (self ) -> Iterator [None ]:
87+ """
88+ Use the node-level invariant ones for basic parsing operations (push/pop/shift).
89+ These still will operate on the stack; but they won't create any new nodes, or
90+ modify the contents of any other existing nodes.
91+
92+ This saves us a ton of time when we are backtracking, since we
93+ want to restore to the initial state as quick as possible, which
94+ can only be done by having as little mutatations as possible.
95+ """
96+ is_backtracking = self .parser .is_backtracking
6697 try :
98+ self .parser .is_backtracking = True
6799 yield
68- except ParseError :
69- self ._dead_ilabels .add (ilabel )
70100 finally :
71- self .parser .stack = self . _start_point
101+ self .parser .is_backtracking = is_backtracking
72102
73103 def add_token (self , tok_type : int , tok_val : Text , raw : bool = False ) -> None :
74104 func : Callable [..., Any ]
@@ -179,6 +209,7 @@ def __init__(self, grammar: Grammar, convert: Optional[Convert] = None) -> None:
179209 self .grammar = grammar
180210 # See note in docstring above. TL;DR this is ignored.
181211 self .convert = convert or lam_sub
212+ self .is_backtracking = False
182213
183214 def setup (self , proxy : "TokenProxy" , start : Optional [int ] = None ) -> None :
184215 """Prepare for parsing.
@@ -319,28 +350,40 @@ def classify(self, type: int, value: Text, context: Context) -> List[int]:
319350
320351 def shift (self , type : int , value : Text , newstate : int , context : Context ) -> None :
321352 """Shift a token. (Internal)"""
322- dfa , state , node = self .stack [- 1 ]
323- rawnode : RawNode = (type , value , context , None )
324- newnode = convert (self .grammar , rawnode )
325- assert node [- 1 ] is not None
326- node [- 1 ].append (newnode )
327- self .stack [- 1 ] = (dfa , newstate , node )
353+ if self .is_backtracking :
354+ dfa , state , _ = self .stack [- 1 ]
355+ self .stack [- 1 ] = (dfa , newstate , DUMMY_NODE )
356+ else :
357+ dfa , state , node = self .stack [- 1 ]
358+ rawnode : RawNode = (type , value , context , None )
359+ newnode = convert (self .grammar , rawnode )
360+ assert node [- 1 ] is not None
361+ node [- 1 ].append (newnode )
362+ self .stack [- 1 ] = (dfa , newstate , node )
328363
329364 def push (self , type : int , newdfa : DFAS , newstate : int , context : Context ) -> None :
330365 """Push a nonterminal. (Internal)"""
331- dfa , state , node = self .stack [- 1 ]
332- newnode : RawNode = (type , None , context , [])
333- self .stack [- 1 ] = (dfa , newstate , node )
334- self .stack .append ((newdfa , 0 , newnode ))
366+ if self .is_backtracking :
367+ dfa , state , _ = self .stack [- 1 ]
368+ self .stack [- 1 ] = (dfa , newstate , DUMMY_NODE )
369+ self .stack .append ((newdfa , 0 , DUMMY_NODE ))
370+ else :
371+ dfa , state , node = self .stack [- 1 ]
372+ newnode : RawNode = (type , None , context , [])
373+ self .stack [- 1 ] = (dfa , newstate , node )
374+ self .stack .append ((newdfa , 0 , newnode ))
335375
336376 def pop (self ) -> None :
337377 """Pop a nonterminal. (Internal)"""
338- popdfa , popstate , popnode = self .stack .pop ()
339- newnode = convert (self .grammar , popnode )
340- if self .stack :
341- dfa , state , node = self .stack [- 1 ]
342- assert node [- 1 ] is not None
343- node [- 1 ].append (newnode )
378+ if self .is_backtracking :
379+ self .stack .pop ()
344380 else :
345- self .rootnode = newnode
346- self .rootnode .used_names = self .used_names
381+ popdfa , popstate , popnode = self .stack .pop ()
382+ newnode = convert (self .grammar , popnode )
383+ if self .stack :
384+ dfa , state , node = self .stack [- 1 ]
385+ assert node [- 1 ] is not None
386+ node [- 1 ].append (newnode )
387+ else :
388+ self .rootnode = newnode
389+ self .rootnode .used_names = self .used_names
0 commit comments