Skip to content

Commit 92a6c40

Browse files
converted lexer and grammar to objects
1 parent 27d8319 commit 92a6c40

3 files changed

Lines changed: 258 additions & 265 deletions

File tree

sifter/grammar/grammar.py

Lines changed: 143 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,191 +1,186 @@
11
# Parser based on RFC 5228, especially the grammar as defined in section 8. All
22
# references are to sections in RFC 5228 unless stated otherwise.
33

4+
45
from sifter.grammar.test import Test
56
from sifter.grammar.command import Command
67
from typing import (
78
TYPE_CHECKING,
9+
cast,
810
Any
911
)
1012

1113
import ply.yacc # type: ignore
1214

1315
import sifter.grammar
1416
from sifter.grammar.tag import Tag
15-
from sifter.grammar.lexer import tokens
1617
from sifter.grammar.command_list import CommandList
1718
from sifter.grammar.string import String
1819
import sifter.handler
20+
from sifter.grammar.lexer import SieveLexer
1921

2022
if TYPE_CHECKING:
2123
from py.yacc import LRParser, YaccProduction # type: ignore
2224

23-
__all__ = ('parser',)
2425

26+
class SieveParser():
2527

26-
def parser(**kwargs: Any) -> 'LRParser':
27-
return ply.yacc.yacc(**kwargs)
28+
tokens = SieveLexer.tokens
2829

30+
def __init__(self) -> None:
31+
self.lexer = SieveLexer()
32+
self.parser = self.make_parser(self)
2933

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+
)
3339

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()
4142

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')
4846

49-
p[0].commands.append(p[2])
47+
return cast(CommandList, rules)
5048

49+
def p_commands_list(self, p: 'YaccProduction') -> None:
50+
"""commands : commands command"""
51+
p[0] = p[1]
5152

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
5558

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
5664

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])
7366

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()
7970

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)))
8891
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] = []
12592

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]
126101

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),))
134105
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)
138106

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
139122

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
183174

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])
187179

180+
def p_stringlist_single(self, p: 'YaccProduction') -> None:
181+
"""stringlist : string"""
182+
p[0] = [p[1]]
188183

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

Comments
 (0)