Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/lark/parsers/lalr_interactive_parser.py: 62%

76 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-14 06:19 +0000

1# This module provides a LALR interactive parser, which is used for debugging and error handling 

2 

3from typing import Iterator, List 

4from copy import copy 

5import warnings 

6 

7from lark.exceptions import UnexpectedToken 

8from lark.lexer import Token, LexerThread 

9 

10###{standalone 

11 

12class InteractiveParser: 

13 """InteractiveParser gives you advanced control over parsing and error handling when parsing with LALR. 

14 

15 For a simpler interface, see the ``on_error`` argument to ``Lark.parse()``. 

16 """ 

17 def __init__(self, parser, parser_state, lexer_thread: LexerThread): 

18 self.parser = parser 

19 self.parser_state = parser_state 

20 self.lexer_thread = lexer_thread 

21 self.result = None 

22 

23 @property 

24 def lexer_state(self) -> LexerThread: 

25 warnings.warn("lexer_state will be removed in subsequent releases. Use lexer_thread instead.", DeprecationWarning) 

26 return self.lexer_thread 

27 

28 def feed_token(self, token: Token): 

29 """Feed the parser with a token, and advance it to the next state, as if it received it from the lexer. 

30 

31 Note that ``token`` has to be an instance of ``Token``. 

32 """ 

33 return self.parser_state.feed_token(token, token.type == '$END') 

34 

35 def iter_parse(self) -> Iterator[Token]: 

36 """Step through the different stages of the parse, by reading tokens from the lexer 

37 and feeding them to the parser, one per iteration. 

38 

39 Returns an iterator of the tokens it encounters. 

40 

41 When the parse is over, the resulting tree can be found in ``InteractiveParser.result``. 

42 """ 

43 for token in self.lexer_thread.lex(self.parser_state): 

44 yield token 

45 self.result = self.feed_token(token) 

46 

47 def exhaust_lexer(self) -> List[Token]: 

48 """Try to feed the rest of the lexer state into the interactive parser. 

49 

50 Note that this modifies the instance in place and does not feed an '$END' Token 

51 """ 

52 return list(self.iter_parse()) 

53 

54 

55 def feed_eof(self, last_token=None): 

56 """Feed a '$END' Token. Borrows from 'last_token' if given.""" 

57 eof = Token.new_borrow_pos('$END', '', last_token) if last_token is not None else self.lexer_thread._Token('$END', '', 0, 1, 1) 

58 return self.feed_token(eof) 

59 

60 

61 def __copy__(self): 

62 """Create a new interactive parser with a separate state. 

63 

64 Calls to feed_token() won't affect the old instance, and vice-versa. 

65 """ 

66 return type(self)( 

67 self.parser, 

68 copy(self.parser_state), 

69 copy(self.lexer_thread), 

70 ) 

71 

72 def copy(self): 

73 return copy(self) 

74 

75 def __eq__(self, other): 

76 if not isinstance(other, InteractiveParser): 

77 return False 

78 

79 return self.parser_state == other.parser_state and self.lexer_thread == other.lexer_thread 

80 

81 def as_immutable(self): 

82 """Convert to an ``ImmutableInteractiveParser``.""" 

83 p = copy(self) 

84 return ImmutableInteractiveParser(p.parser, p.parser_state, p.lexer_thread) 

85 

86 def pretty(self): 

87 """Print the output of ``choices()`` in a way that's easier to read.""" 

88 out = ["Parser choices:"] 

89 for k, v in self.choices().items(): 

90 out.append('\t- %s -> %r' % (k, v)) 

91 out.append('stack size: %s' % len(self.parser_state.state_stack)) 

92 return '\n'.join(out) 

93 

94 def choices(self): 

95 """Returns a dictionary of token types, matched to their action in the parser. 

96 

97 Only returns token types that are accepted by the current state. 

98 

99 Updated by ``feed_token()``. 

100 """ 

101 return self.parser_state.parse_conf.parse_table.states[self.parser_state.position] 

102 

103 def accepts(self): 

104 """Returns the set of possible tokens that will advance the parser into a new valid state.""" 

105 accepts = set() 

106 conf_no_callbacks = copy(self.parser_state.parse_conf) 

107 # We don't want to call callbacks here since those might have arbitrary side effects 

108 # and are unnecessarily slow. 

109 conf_no_callbacks.callbacks = {} 

110 for t in self.choices(): 

111 if t.isupper(): # is terminal? 

112 new_cursor = copy(self) 

113 new_cursor.parser_state.parse_conf = conf_no_callbacks 

114 try: 

115 new_cursor.feed_token(self.lexer_thread._Token(t, '')) 

116 except UnexpectedToken: 

117 pass 

118 else: 

119 accepts.add(t) 

120 return accepts 

121 

122 def resume_parse(self): 

123 """Resume automated parsing from the current state. 

124 """ 

125 return self.parser.parse_from_state(self.parser_state, last_token=self.lexer_thread.state.last_token) 

126 

127 

128 

129class ImmutableInteractiveParser(InteractiveParser): 

130 """Same as ``InteractiveParser``, but operations create a new instance instead 

131 of changing it in-place. 

132 """ 

133 

134 result = None 

135 

136 def __hash__(self): 

137 return hash((self.parser_state, self.lexer_thread)) 

138 

139 def feed_token(self, token): 

140 c = copy(self) 

141 c.result = InteractiveParser.feed_token(c, token) 

142 return c 

143 

144 def exhaust_lexer(self): 

145 """Try to feed the rest of the lexer state into the parser. 

146 

147 Note that this returns a new ImmutableInteractiveParser and does not feed an '$END' Token""" 

148 cursor = self.as_mutable() 

149 cursor.exhaust_lexer() 

150 return cursor.as_immutable() 

151 

152 def as_mutable(self): 

153 """Convert to an ``InteractiveParser``.""" 

154 p = copy(self) 

155 return InteractiveParser(p.parser, p.parser_state, p.lexer_thread) 

156 

157###}