1# This module provides a LALR interactive parser, which is used for debugging and error handling
2
3from copy import copy
4
5from .. import Token
6from ..exceptions import UnexpectedToken
7
8
9class InteractiveParser(object):
10    """InteractiveParser gives you advanced control over parsing and error handling when parsing with LALR.
11
12    For a simpler interface, see the ``on_error`` argument to ``Lark.parse()``.
13    """
14    def __init__(self, parser, parser_state, lexer_state):
15        self.parser = parser
16        self.parser_state = parser_state
17        self.lexer_state = lexer_state
18
19    def feed_token(self, token):
20        """Feed the parser with a token, and advance it to the next state, as if it received it from the lexer.
21
22        Note that ``token`` has to be an instance of ``Token``.
23        """
24        return self.parser_state.feed_token(token, token.type == '$END')
25
26    def exhaust_lexer(self):
27        """Try to feed the rest of the lexer state into the interactive parser.
28
29        Note that this modifies the instance in place and does not feed an '$END' Token"""
30        for token in self.lexer_state.lex(self.parser_state):
31            self.parser_state.feed_token(token)
32
33    def feed_eof(self, last_token=None):
34        """Feed a '$END' Token. Borrows from 'last_token' if given."""
35        eof = Token.new_borrow_pos('$END', '', last_token) if last_token is not None else Token('$END', '', 0, 1, 1)
36        return self.feed_token(eof)
37
38
39    def __copy__(self):
40        """Create a new interactive parser with a separate state.
41
42        Calls to feed_token() won't affect the old instance, and vice-versa.
43        """
44        return type(self)(
45            self.parser,
46            copy(self.parser_state),
47            copy(self.lexer_state),
48        )
49
50    def copy(self):
51        return copy(self)
52
53    def __eq__(self, other):
54        if not isinstance(other, InteractiveParser):
55            return False
56
57        return self.parser_state == other.parser_state and self.lexer_state == other.lexer_state
58
59    def as_immutable(self):
60        """Convert to an ``ImmutableInteractiveParser``."""
61        p = copy(self)
62        return ImmutableInteractiveParser(p.parser, p.parser_state, p.lexer_state)
63
64    def pretty(self):
65        """Print the output of ``choices()`` in a way that's easier to read."""
66        out = ["Parser choices:"]
67        for k, v in self.choices().items():
68            out.append('\t- %s -> %r' % (k, v))
69        out.append('stack size: %s' % len(self.parser_state.state_stack))
70        return '\n'.join(out)
71
72    def choices(self):
73        """Returns a dictionary of token types, matched to their action in the parser.
74
75        Only returns token types that are accepted by the current state.
76
77        Updated by ``feed_token()``.
78        """
79        return self.parser_state.parse_conf.parse_table.states[self.parser_state.position]
80
81    def accepts(self):
82        """Returns the set of possible tokens that will advance the parser into a new valid state."""
83        accepts = set()
84        for t in self.choices():
85            if t.isupper(): # is terminal?
86                new_cursor = copy(self)
87                try:
88                    new_cursor.feed_token(Token(t, ''))
89                except UnexpectedToken:
90                    pass
91                else:
92                    accepts.add(t)
93        return accepts
94
95    def resume_parse(self):
96        """Resume automated parsing from the current state."""
97        return self.parser.parse_from_state(self.parser_state)
98
99
100
101class ImmutableInteractiveParser(InteractiveParser):
102    """Same as ``InteractiveParser``, but operations create a new instance instead
103    of changing it in-place.
104    """
105
106    result = None
107
108    def __hash__(self):
109        return hash((self.parser_state, self.lexer_state))
110
111    def feed_token(self, token):
112        c = copy(self)
113        c.result = InteractiveParser.feed_token(c, token)
114        return c
115
116    def exhaust_lexer(self):
117        """Try to feed the rest of the lexer state into the parser.
118
119        Note that this returns a new ImmutableInteractiveParser and does not feed an '$END' Token"""
120        cursor = self.as_mutable()
121        cursor.exhaust_lexer()
122        return cursor.as_immutable()
123
124    def as_mutable(self):
125        """Convert to an ``InteractiveParser``."""
126        p = copy(self)
127        return InteractiveParser(p.parser, p.parser_state, p.lexer_state)
128
129
130# Deprecated class names for the interactive parser
131ParserPuppet = InteractiveParser
132ImmutableParserPuppet = ImmutableInteractiveParser
133