|
1 | 1 | # Parser based on RFC 5228, especially the grammar as defined in section 8. All |
2 | 2 | # references are to sections in RFC 5228 unless stated otherwise. |
3 | 3 |
|
| 4 | + |
4 | 5 | from sifter.grammar.test import Test |
5 | 6 | from sifter.grammar.command import Command |
6 | 7 | from typing import ( |
7 | 8 | TYPE_CHECKING, |
| 9 | + cast, |
8 | 10 | Any |
9 | 11 | ) |
10 | 12 |
|
11 | 13 | import ply.yacc # type: ignore |
12 | 14 |
|
13 | 15 | import sifter.grammar |
14 | 16 | from sifter.grammar.tag import Tag |
15 | | -from sifter.grammar.lexer import tokens |
16 | 17 | from sifter.grammar.command_list import CommandList |
17 | 18 | from sifter.grammar.string import String |
18 | 19 | import sifter.handler |
| 20 | +from sifter.grammar.lexer import SieveLexer |
19 | 21 |
|
20 | 22 | if TYPE_CHECKING: |
21 | 23 | from py.yacc import LRParser, YaccProduction # type: ignore |
22 | 24 |
|
23 | | -__all__ = ('parser',) |
24 | 25 |
|
| 26 | +class SieveParser(): |
25 | 27 |
|
26 | | -def parser(**kwargs: Any) -> 'LRParser': |
27 | | - return ply.yacc.yacc(**kwargs) |
| 28 | + tokens = SieveLexer.tokens |
28 | 29 |
|
| 30 | + def __init__(self) -> None: |
| 31 | + self.lexer = SieveLexer() |
| 32 | + self.parser = self.make_parser(self) |
29 | 33 |
|
30 | | -def p_commands_list(p: 'YaccProduction') -> None: |
31 | | - """commands : commands command""" |
32 | | - p[0] = p[1] |
| 34 | + @staticmethod |
| 35 | + def make_parser(mod) -> 'LRParser': |
| 36 | + return ply.yacc.yacc( |
| 37 | + module=mod |
| 38 | + ) |
33 | 39 |
|
34 | | - # section 3.2: REQUIRE command must come before any other commands |
35 | | - if p[2].RULE_IDENTIFIER == 'REQUIRE': |
36 | | - if any(command.RULE_IDENTIFIER != 'REQUIRE' |
37 | | - for command in p[0].commands): |
38 | | - print("REQUIRE command on line %d must come before any " |
39 | | - "other non-REQUIRE commands" % p.lineno(2)) |
40 | | - raise SyntaxError |
| 40 | + def parse(self, rules, tracking=0): |
| 41 | + self.parser.errok() |
41 | 42 |
|
42 | | - # section 3.1: ELSIF and ELSE must follow IF or another ELSIF |
43 | | - elif p[2].RULE_IDENTIFIER in ('ELSIF', 'ELSE'): |
44 | | - if p[0].commands[-1].RULE_IDENTIFIER not in ('IF', 'ELSIF'): |
45 | | - print("ELSIF/ELSE command on line %d must follow an IF/ELSIF " |
46 | | - "command" % p.lineno(2)) |
47 | | - raise SyntaxError |
| 43 | + rules = self.parser.parse(rules, self.lexer, tracking=tracking) |
| 44 | + if not self.parser.errorok: |
| 45 | + raise ply.yacc.YaccError('Syntax error') |
48 | 46 |
|
49 | | - p[0].commands.append(p[2]) |
| 47 | + return cast(CommandList, rules) |
50 | 48 |
|
| 49 | + def p_commands_list(self, p: 'YaccProduction') -> None: |
| 50 | + """commands : commands command""" |
| 51 | + p[0] = p[1] |
51 | 52 |
|
52 | | -def p_commands_empty(p: 'YaccProduction') -> None: |
53 | | - """commands : """ |
54 | | - p[0] = CommandList() |
| 53 | + # section 3.2: REQUIRE command must come before any other commands |
| 54 | + if p[2].RULE_IDENTIFIER == 'REQUIRE': |
| 55 | + if any(command.RULE_IDENTIFIER != 'REQUIRE' for command in p[0].commands): |
| 56 | + print("REQUIRE command on line %d must come before any other non-REQUIRE commands" % p.lineno(2)) |
| 57 | + raise SyntaxError |
55 | 58 |
|
| 59 | + # section 3.1: ELSIF and ELSE must follow IF or another ELSIF |
| 60 | + elif p[2].RULE_IDENTIFIER in ('ELSIF', 'ELSE'): |
| 61 | + if p[0].commands[-1].RULE_IDENTIFIER not in ('IF', 'ELSIF'): |
| 62 | + print("ELSIF/ELSE command on line %d must follow an IF/ELSIF command" % p.lineno(2)) |
| 63 | + raise SyntaxError |
56 | 64 |
|
57 | | -def p_command(p: 'YaccProduction') -> None: |
58 | | - """command : IDENTIFIER arguments ';' |
59 | | - | IDENTIFIER arguments block""" |
60 | | - # print("COMMAND:", p[1], p[2], p[3]) |
61 | | - tests = p[2].get('tests') |
62 | | - block = None |
63 | | - if p[3] != ';': |
64 | | - block = p[3] |
65 | | - handler = sifter.handler.get('command', p[1]) |
66 | | - if handler is None: |
67 | | - print("No handler registered for command '%s' on line %d" % (p[1], p.lineno(1))) |
68 | | - raise SyntaxError |
69 | | - if not isinstance(handler, type) or not issubclass(handler, Command): |
70 | | - raise ValueError("handler must be subclass of Command") |
71 | | - p[0] = handler(arguments=p[2]['args'], tests=tests, block=block) |
72 | | - |
| 65 | + p[0].commands.append(p[2]) |
73 | 66 |
|
74 | | -def p_command_error(p: 'YaccProduction') -> None: |
75 | | - """command : IDENTIFIER error ';' |
76 | | - | IDENTIFIER error block""" |
77 | | - print("Syntax error in command definition after %s on line %d" % (p[1], p.lineno(1))) |
78 | | - raise SyntaxError |
| 67 | + def p_commands_empty(self, p: 'YaccProduction') -> None: |
| 68 | + """commands : """ |
| 69 | + p[0] = CommandList() |
79 | 70 |
|
80 | | - |
81 | | -def p_block(p: 'YaccProduction') -> None: |
82 | | - """block : '{' commands '}' """ |
83 | | - # section 3.2: REQUIRE command must come before any other commands, |
84 | | - # which means it can't be in the block of another command |
85 | | - if any(command.RULE_IDENTIFIER == 'REQUIRE' |
86 | | - for command in p[2].commands): |
87 | | - print("REQUIRE command not allowed inside of a block (line %d)" % (p.lineno(2))) |
| 71 | + def p_command(self, p: 'YaccProduction') -> None: |
| 72 | + """command : IDENTIFIER arguments ';' |
| 73 | + | IDENTIFIER arguments block""" |
| 74 | + # print("COMMAND:", p[1], p[2], p[3]) |
| 75 | + tests = p[2].get('tests') |
| 76 | + block = None |
| 77 | + if p[3] != ';': |
| 78 | + block = p[3] |
| 79 | + handler = sifter.handler.get('command', p[1]) |
| 80 | + if handler is None: |
| 81 | + print("No handler registered for command '%s' on line %d" % (p[1], p.lineno(1))) |
| 82 | + raise SyntaxError |
| 83 | + if not isinstance(handler, type) or not issubclass(handler, Command): |
| 84 | + raise ValueError("handler must be subclass of Command") |
| 85 | + p[0] = handler(arguments=p[2]['args'], tests=tests, block=block) |
| 86 | + |
| 87 | + def p_command_error(self, p: 'YaccProduction') -> None: |
| 88 | + """command : IDENTIFIER error ';' |
| 89 | + | IDENTIFIER error block""" |
| 90 | + print("Syntax error in command definition after %s on line %d" % (p[1], p.lineno(1))) |
88 | 91 | raise SyntaxError |
89 | | - p[0] = p[2] |
90 | | - |
91 | | - |
92 | | -def p_block_error(p: 'YaccProduction') -> None: |
93 | | - """block : '{' error '}'""" |
94 | | - print("Syntax error in command block that starts on line %d" % (p.lineno(1),)) |
95 | | - raise SyntaxError |
96 | | - |
97 | | - |
98 | | -def p_arguments(p: 'YaccProduction') -> None: |
99 | | - """arguments : argumentlist |
100 | | - | argumentlist test |
101 | | - | argumentlist '(' testlist ')'""" |
102 | | - p[0] = {'args': p[1], } |
103 | | - if len(p) > 2: |
104 | | - if p[2] == '(': |
105 | | - p[0]['tests'] = p[3] |
106 | | - else: |
107 | | - p[0]['tests'] = [p[2]] |
108 | | - |
109 | | - |
110 | | -def p_testlist_error(p: 'YaccProduction') -> None: |
111 | | - """arguments : argumentlist '(' error ')'""" |
112 | | - print("Syntax error in test list that starts on line %d" % p.lineno(2)) |
113 | | - raise SyntaxError |
114 | | - |
115 | | - |
116 | | -def p_argumentlist_list(p: 'YaccProduction') -> None: |
117 | | - """argumentlist : argumentlist argument""" |
118 | | - p[0] = p[1] |
119 | | - p[0].append(p[2]) |
120 | | - |
121 | | - |
122 | | -def p_argumentlist_empty(p: 'YaccProduction') -> None: |
123 | | - """argumentlist : """ |
124 | | - p[0] = [] |
125 | 92 |
|
| 93 | + def p_block(self, p: 'YaccProduction') -> None: |
| 94 | + """block : '{' commands '}' """ |
| 95 | + # section 3.2: REQUIRE command must come before any other commands, |
| 96 | + # which means it can't be in the block of another command |
| 97 | + if any(command.RULE_IDENTIFIER == 'REQUIRE' for command in p[2].commands): |
| 98 | + print("REQUIRE command not allowed inside of a block (line %d)" % (p.lineno(2))) |
| 99 | + raise SyntaxError |
| 100 | + p[0] = p[2] |
126 | 101 |
|
127 | | -def p_test(p: 'YaccProduction') -> None: |
128 | | - """test : IDENTIFIER arguments""" |
129 | | - # print("TEST:", p[1], p[2]) |
130 | | - tests = p[2].get('tests') |
131 | | - handler = sifter.handler.get('test', p[1]) |
132 | | - if handler is None: |
133 | | - print("No handler registered for test '%s' on line %d" % (p[1], p.lineno(1))) |
| 102 | + def p_block_error(self, p: 'YaccProduction') -> None: |
| 103 | + """block : '{' error '}'""" |
| 104 | + print("Syntax error in command block that starts on line %d" % (p.lineno(1),)) |
134 | 105 | raise SyntaxError |
135 | | - if not isinstance(handler, type) or not issubclass(handler, Test): |
136 | | - raise ValueError("handler must be subclass of Test") |
137 | | - p[0] = handler(arguments=p[2]['args'], tests=tests) |
138 | 106 |
|
| 107 | + def p_arguments(self, p: 'YaccProduction') -> None: |
| 108 | + """arguments : argumentlist |
| 109 | + | argumentlist test |
| 110 | + | argumentlist '(' testlist ')'""" |
| 111 | + p[0] = {'args': p[1], } |
| 112 | + if len(p) > 2: |
| 113 | + if p[2] == '(': |
| 114 | + p[0]['tests'] = p[3] |
| 115 | + else: |
| 116 | + p[0]['tests'] = [p[2]] |
| 117 | + |
| 118 | + def p_testlist_error(self, p: 'YaccProduction') -> None: |
| 119 | + """arguments : argumentlist '(' error ')'""" |
| 120 | + print("Syntax error in test list that starts on line %d" % p.lineno(2)) |
| 121 | + raise SyntaxError |
139 | 122 |
|
140 | | -def p_testlist_list(p: 'YaccProduction') -> None: |
141 | | - """testlist : test ',' testlist""" |
142 | | - p[0] = p[3] |
143 | | - p[0].insert(0, p[1]) |
144 | | - |
145 | | - |
146 | | -def p_testlist_single(p: 'YaccProduction') -> None: |
147 | | - """testlist : test""" |
148 | | - p[0] = [p[1]] |
149 | | - |
150 | | - |
151 | | -def p_argument_stringlist(p: 'YaccProduction') -> None: |
152 | | - """argument : '[' stringlist ']'""" |
153 | | - p[0] = p[2] |
154 | | - |
155 | | - |
156 | | -def p_argument_string(p: 'YaccProduction') -> None: |
157 | | - """argument : string""" |
158 | | - # for simplicity, we treat all single strings as a string list |
159 | | - p[0] = [p[1]] |
160 | | - |
161 | | - |
162 | | -def p_argument_number(p: 'YaccProduction') -> None: |
163 | | - """argument : NUMBER""" |
164 | | - p[0] = p[1] |
165 | | - |
166 | | - |
167 | | -def p_argument_tag(p: 'YaccProduction') -> None: |
168 | | - """argument : TAG""" |
169 | | - p[0] = Tag(p[1]) |
170 | | - |
171 | | - |
172 | | -def p_stringlist_error(p: 'YaccProduction') -> None: |
173 | | - """argument : '[' error ']'""" |
174 | | - print("Syntax error in string list that starts on line %d" % p.lineno(1)) |
175 | | - raise SyntaxError |
176 | | - |
177 | | - |
178 | | -def p_stringlist_list(p: 'YaccProduction') -> None: |
179 | | - """stringlist : string ',' stringlist""" |
180 | | - p[0] = p[3] |
181 | | - p[0].insert(0, p[1]) |
182 | | - |
| 123 | + def p_argumentlist_list(self, p: 'YaccProduction') -> None: |
| 124 | + """argumentlist : argumentlist argument""" |
| 125 | + p[0] = p[1] |
| 126 | + p[0].append(p[2]) |
| 127 | + |
| 128 | + def p_argumentlist_empty(self, p: 'YaccProduction') -> None: |
| 129 | + """argumentlist : """ |
| 130 | + p[0] = [] |
| 131 | + |
| 132 | + def p_test(self, p: 'YaccProduction') -> None: |
| 133 | + """test : IDENTIFIER arguments""" |
| 134 | + # print("TEST:", p[1], p[2]) |
| 135 | + tests = p[2].get('tests') |
| 136 | + handler = sifter.handler.get('test', p[1]) |
| 137 | + if handler is None: |
| 138 | + print("No handler registered for test '%s' on line %d" % (p[1], p.lineno(1))) |
| 139 | + raise SyntaxError |
| 140 | + if not isinstance(handler, type) or not issubclass(handler, Test): |
| 141 | + raise ValueError("handler must be subclass of Test") |
| 142 | + p[0] = handler(arguments=p[2]['args'], tests=tests) |
| 143 | + |
| 144 | + def p_testlist_list(self, p: 'YaccProduction') -> None: |
| 145 | + """testlist : test ',' testlist""" |
| 146 | + p[0] = p[3] |
| 147 | + p[0].insert(0, p[1]) |
| 148 | + |
| 149 | + def p_testlist_single(self, p: 'YaccProduction') -> None: |
| 150 | + """testlist : test""" |
| 151 | + p[0] = [p[1]] |
| 152 | + |
| 153 | + def p_argument_stringlist(self, p: 'YaccProduction') -> None: |
| 154 | + """argument : '[' stringlist ']'""" |
| 155 | + p[0] = p[2] |
| 156 | + |
| 157 | + def p_argument_string(self, p: 'YaccProduction') -> None: |
| 158 | + """argument : string""" |
| 159 | + # for simplicity, we treat all single strings as a string list |
| 160 | + p[0] = [p[1]] |
| 161 | + |
| 162 | + def p_argument_number(self, p: 'YaccProduction') -> None: |
| 163 | + """argument : NUMBER""" |
| 164 | + p[0] = p[1] |
| 165 | + |
| 166 | + def p_argument_tag(self, p: 'YaccProduction') -> None: |
| 167 | + """argument : TAG""" |
| 168 | + p[0] = Tag(p[1]) |
| 169 | + |
| 170 | + def p_stringlist_error(self, p: 'YaccProduction') -> None: |
| 171 | + """argument : '[' error ']'""" |
| 172 | + print("Syntax error in string list that starts on line %d" % p.lineno(1)) |
| 173 | + raise SyntaxError |
183 | 174 |
|
184 | | -def p_stringlist_single(p: 'YaccProduction') -> None: |
185 | | - """stringlist : string""" |
186 | | - p[0] = [p[1]] |
| 175 | + def p_stringlist_list(self, p: 'YaccProduction') -> None: |
| 176 | + """stringlist : string ',' stringlist""" |
| 177 | + p[0] = p[3] |
| 178 | + p[0].insert(0, p[1]) |
187 | 179 |
|
| 180 | + def p_stringlist_single(self, p: 'YaccProduction') -> None: |
| 181 | + """stringlist : string""" |
| 182 | + p[0] = [p[1]] |
188 | 183 |
|
189 | | -def p_string(p: 'YaccProduction') -> None: |
190 | | - """string : QUOTED_STRING""" |
191 | | - p[0] = String(p[1]) |
| 184 | + def p_string(self, p: 'YaccProduction') -> None: |
| 185 | + """string : QUOTED_STRING""" |
| 186 | + p[0] = String(p[1]) |
0 commit comments