1"""This module implements a LALR(1) Parser
2"""
3# Author: Erez Shinan (2017)
4# Email : erezshin@gmail.com
5from typing import Dict, Any, Optional
6from ..lexer import Token, LexerThread
7from ..utils import Serialize
8from ..common import ParserConf, ParserCallbacks
9
10from .lalr_analysis import LALR_Analyzer, IntParseTable, ParseTableBase
11from .lalr_interactive_parser import InteractiveParser
12from lark.exceptions import UnexpectedCharacters, UnexpectedInput, UnexpectedToken
13from .lalr_parser_state import ParserState, ParseConf
14
15###{standalone
16
17class LALR_Parser(Serialize):
18 def __init__(self, parser_conf: ParserConf, debug: bool=False, strict: bool=False):
19 analysis = LALR_Analyzer(parser_conf, debug=debug, strict=strict)
20 analysis.compute_lalr()
21 callbacks = parser_conf.callbacks
22
23 self._parse_table = analysis.parse_table
24 self.parser_conf = parser_conf
25 self.parser = _Parser(analysis.parse_table, callbacks, debug)
26
27 @classmethod
28 def deserialize(cls, data, memo, callbacks, debug=False):
29 inst = cls.__new__(cls)
30 inst._parse_table = IntParseTable.deserialize(data, memo)
31 inst.parser = _Parser(inst._parse_table, callbacks, debug)
32 return inst
33
34 def serialize(self, memo: Any = None) -> Dict[str, Any]:
35 return self._parse_table.serialize(memo)
36
37 def parse_interactive(self, lexer: LexerThread, start: str):
38 return self.parser.parse(lexer, start, start_interactive=True)
39
40 def parse(self, lexer, start, on_error=None):
41 try:
42 return self.parser.parse(lexer, start)
43 except UnexpectedInput as e:
44 if on_error is None:
45 raise
46
47 while True:
48 if isinstance(e, UnexpectedCharacters):
49 s = e.interactive_parser.lexer_thread.state
50 p = s.line_ctr.char_pos
51
52 if not on_error(e):
53 raise e
54
55 if isinstance(e, UnexpectedCharacters):
56 # If user didn't change the character position, then we should
57 if p == s.line_ctr.char_pos:
58 s.line_ctr.feed(s.text.text[p:p+1])
59
60 try:
61 return e.interactive_parser.resume_parse()
62 except UnexpectedToken as e2:
63 if (isinstance(e, UnexpectedToken)
64 and e.token.type == e2.token.type == '$END'
65 and e.interactive_parser == e2.interactive_parser):
66 # Prevent infinite loop
67 raise e2
68 e = e2
69 except UnexpectedCharacters as e2:
70 e = e2
71
72
73class _Parser:
74 parse_table: ParseTableBase
75 callbacks: ParserCallbacks
76 debug: bool
77
78 def __init__(self, parse_table: ParseTableBase, callbacks: ParserCallbacks, debug: bool=False):
79 self.parse_table = parse_table
80 self.callbacks = callbacks
81 self.debug = debug
82
83 def parse(self, lexer: LexerThread, start: str, value_stack=None, state_stack=None, start_interactive=False):
84 parse_conf = ParseConf(self.parse_table, self.callbacks, start)
85 parser_state = ParserState(parse_conf, lexer, state_stack, value_stack)
86 if start_interactive:
87 return InteractiveParser(self, parser_state, parser_state.lexer)
88 return self.parse_from_state(parser_state)
89
90
91 def parse_from_state(self, state: ParserState, last_token: Optional[Token]=None):
92 """Run the main LALR parser loop
93
94 Parameters:
95 state - the initial state. Changed in-place.
96 last_token - Used only for line information in case of an empty lexer.
97 """
98 try:
99 token = last_token
100 for token in state.lexer.lex(state):
101 assert token is not None
102 state.feed_token(token)
103
104 end_token = Token.new_borrow_pos('$END', '', token) if token else Token('$END', '', 0, 1, 1)
105 return state.feed_token(end_token, True)
106 except UnexpectedInput as e:
107 try:
108 e.interactive_parser = InteractiveParser(self, state, state.lexer)
109 except NameError:
110 pass
111 raise e
112 except Exception as e:
113 if self.debug:
114 print("")
115 print("STATE STACK DUMP")
116 print("----------------")
117 for i, s in enumerate(state.state_stack):
118 print('%d)' % i , s)
119 print("")
120
121 raise
122###}