|
- # This module provide a LALR puppet, which is used to debugging and error handling
-
- from copy import deepcopy
-
- from .lalr_analysis import Shift, Reduce
- from .. import Token
- from ..exceptions import ParseError
-
-
- class ParserPuppet(object):
- """ParserPuppet gives you advanced control over error handling when parsing with LALR.
-
- For a simpler, more streamlined interface, see the ``on_error`` argument to ``Lark.parse()``.
- """
- def __init__(self, parser, state_stack, value_stack, start, stream, set_state):
- self.parser = parser
- self._state_stack = state_stack
- self._value_stack = value_stack
- self._start = start
- self._stream = stream
- self._set_state = set_state
-
- self.result = None
-
- def feed_token(self, token):
- """Feed the parser with a token, and advance it to the next state, as if it received it from the lexer.
-
- Note that ``token`` has to be an instance of ``Token``.
- """
- end_state = self.parser.parse_table.end_states[self._start]
- state_stack = self._state_stack
- value_stack = self._value_stack
-
- state = state_stack[-1]
- action, arg = self.parser.parse_table.states[state][token.type]
- if arg == end_state:
- raise ParseError(arg)
-
- while action is Reduce:
- rule = arg
- size = len(rule.expansion)
- if size:
- s = value_stack[-size:]
- del state_stack[-size:]
- del value_stack[-size:]
- else:
- s = []
-
- value = self.parser.callbacks[rule](s)
-
- _action, new_state = self.parser.parse_table.states[state_stack[-1]][rule.origin.name]
- assert _action is Shift
- state_stack.append(new_state)
- value_stack.append(value)
-
- if state_stack[-1] == end_state:
- self.result = value_stack[-1]
- return self.result
-
- state = state_stack[-1]
- try:
- action, arg = self.parser.parse_table.states[state][token.type]
- except KeyError as e:
- raise ParseError(e)
- assert arg != end_state
-
- assert action is Shift
- state_stack.append(arg)
- value_stack.append(token)
-
- def copy(self):
- """Create a new puppet with a separate state.
-
- Calls to feed_token() won't affect the old puppet, and vice-versa.
- """
- return type(self)(
- self.parser,
- list(self._state_stack),
- deepcopy(self._value_stack),
- self._start,
- self._stream,
- self._set_state,
- )
-
- def __eq__(self, other):
- if not isinstance(other, ParserPuppet):
- return False
-
- return (
- self._state_stack == other._state_stack and
- self._value_stack == other._value_stack and
- self._stream == other._stream and
- self._start == other._start
- )
-
- def __hash__(self):
- return hash((tuple(self._state_stack), self._start))
-
- def pretty(self):
- """Print the output of ``choices()`` in a way that's easier to read."""
- out = ["Puppet choices:"]
- for k, v in self.choices().items():
- out.append('\t- %s -> %s' % (k, v))
- out.append('stack size: %s' % len(self._state_stack))
- return '\n'.join(out)
-
- def choices(self):
- """Returns a dictionary of token types, matched to their action in the parser.
-
- Only returns token types that are accepted by the current state.
-
- Updated by ``feed_token()``.
- """
- return self.parser.parse_table.states[self._state_stack[-1]]
-
- def accepts(self):
- accepts = set()
- for t in self.choices():
- if t.isupper(): # is terminal?
- new_puppet = self.copy()
- try:
- new_puppet.feed_token(Token(t, ''))
- except ParseError:
- pass
- else:
- accepts.add(t)
- return accepts
-
- def resume_parse(self):
- """Resume parsing from the current puppet state."""
- return self.parser.parse(
- self._stream, self._start, self._set_state,
- self._value_stack, self._state_stack
- )
|