A pretty featureful commit:
[fur] / parsing.py
1 import collections
2
3 def _or_parser(*parsers):
4     def result_parser(index, tokens):
5         failure = (False, index, None)
6
7         for parser in parsers:
8             success, index, value = parser(index, tokens)
9
10             if success:
11                 return (success, index, value)
12
13         return failure
14
15     return result_parser
16
17 def _zero_or_more_parser(formatter, parser):
18     def result_parser(index, tokens):
19         values = []
20
21         while index < len(tokens):
22             success, index, value = parser(index, tokens)
23
24             if success:
25                 values.append(value)
26             else:
27                 break
28
29         return (True, index, formatter(values))
30
31     return result_parser
32
33 FurIntegerLiteralExpression = collections.namedtuple(
34     'FurIntegerLiteralExpression',
35     [
36         'value',
37     ],
38 )
39
40 FurStringLiteralExpression = collections.namedtuple(
41     'FurStringLiteralExpression',
42     [
43         'value',
44     ],
45 )
46
47 FurAdditionExpression = collections.namedtuple(
48     'FurAdditionExpression',
49     [
50         'left',
51         'right',
52     ],
53 )
54
55 FurSubtractionExpression = collections.namedtuple(
56     'FurSubtractionExpression',
57     [
58         'left',
59         'right',
60     ],
61 )
62
63 FurMultiplicationExpression = collections.namedtuple(
64     'FurMultiplicationExpression',
65     [
66         'left',
67         'right',
68     ],
69 )
70
71 FurIntegerDivisionExpression = collections.namedtuple(
72     'FurIntegerDivisionExpression',
73     [
74         'left',
75         'right',
76     ],
77 )
78
79 FurModularDivisionExpression = collections.namedtuple(
80     'FurModularDivisionExpression',
81     [
82         'left',
83         'right',
84     ],
85 )
86
87 def _integer_literal_expression_parser(index, tokens):
88     failure = (False, index, None)
89
90     if tokens[index].type != 'integer_literal':
91         return failure
92     value = int(tokens[index].match)
93     index += 1
94
95     return True, index, FurIntegerLiteralExpression(value=value)
96
97 def _string_literal_expression_parser(index, tokens):
98     failure = (False, index, None)
99
100     if tokens[index].type != 'single_quoted_string_literal':
101         return failure
102     value = tokens[index].match[1:-1]
103     index += 1
104
105     return True, index, FurStringLiteralExpression(value=value)
106
107 def _literal_level_expression_parser(index, tokens):
108     return _or_parser(
109         _function_call_expression_parser,
110         _integer_literal_expression_parser,
111         _string_literal_expression_parser,
112     )(index, tokens)
113
114 def _multiplication_level_expression_parser(index, tokens):
115     failure = (False, index, None)
116
117     success, index, result = _literal_level_expression_parser(index, tokens)
118
119     if not success:
120         return failure
121
122     while success and index < len(tokens) and tokens[index].type == 'multiplication_level_operator':
123         success = False
124
125         if index + 1 < len(tokens):
126             success, try_index, value = _literal_level_expression_parser(index + 1, tokens)
127
128         if success:
129             result = {
130                 '*': FurMultiplicationExpression,
131                 '//': FurIntegerDivisionExpression,
132                 '%': FurModularDivisionExpression,
133             }[tokens[index].match](left=result, right=value)
134             index = try_index
135
136     return True, index, result
137
138 def _addition_level_expression_parser(index, tokens):
139     failure = (False, index, None)
140
141     success, index, result = _multiplication_level_expression_parser(index, tokens)
142
143     if not success:
144         return failure
145
146     while success and index < len(tokens) and tokens[index].type == 'addition_level_operator':
147         success = False
148
149         if index + 1 < len(tokens):
150             success, try_index, value = _multiplication_level_expression_parser(index + 1, tokens)
151
152         if success:
153             result = {
154                 '+': FurAdditionExpression,
155                 '-': FurSubtractionExpression,
156             }[tokens[index].match](left=result, right=value)
157             index = try_index
158
159     return True, index, result
160
161 def _comma_separated_list_parser(index, tokens):
162     failure = (False, index, None)
163
164     expressions = []
165
166     success, index, expression = _addition_level_expression_parser(index, tokens)
167
168     if success:
169         expressions.append(expression)
170     else:
171         return failure
172
173     while success and index < len(tokens) and tokens[index].type == 'comma':
174         success = False
175
176         if index + 1 < len(tokens):
177             success, try_index, expression = _addition_level_expression_parser(index + 1, tokens)
178
179         if success:
180             expressions.append(expression)
181             index = try_index
182
183     return True, index, tuple(expressions)
184
185
186 FurFunctionCallExpression = collections.namedtuple(
187     'FurFunctionCallExpression',
188     [
189         'name',
190         'arguments',
191     ],
192 )
193
194 FurProgram = collections.namedtuple(
195     'FurProgram',
196     [
197         'statement_list',
198     ],
199 )
200
201 def _function_call_expression_parser(index, tokens):
202     failure = (False, index, None)
203
204     if tokens[index].type != 'symbol':
205         return failure
206     name = tokens[index].match
207     index += 1
208
209     if tokens[index].type != 'open_parenthese':
210         return failure
211     index += 1
212
213     success, index, arguments = _comma_separated_list_parser(index, tokens)
214
215     if not success:
216         return failure
217
218     if tokens[index].type != 'close_parenthese':
219         raise Exception('Expected ")", found "{}" on line {}'.format(
220             tokens[index].match,
221             tokens[index].line,
222         ))
223     index += 1
224
225     return True, index, FurFunctionCallExpression(name=name, arguments=arguments)
226
227 def _program_formatter(statement_list):
228     return FurProgram(statement_list=statement_list)
229
230 _program_parser = _zero_or_more_parser(_program_formatter, _function_call_expression_parser)
231
232 def _parse(parser, tokens):
233     success, index, result = parser(0, tokens)
234
235     if index < len(tokens):
236         raise Exception('Unable to parse token {}'.format(tokens[index]))
237
238     if success:
239         return result
240
241     raise Exception('Unable to parse')
242
243 def parse(tokens):
244     return _parse(_program_parser, tokens)
245
246 if __name__ == '__main__':
247     import unittest
248
249     import tokenization
250
251     class FurStringLiteralExpressionParserTests(unittest.TestCase):
252         def test_parses_single_quoted_string_literal(self):
253             self.assertEqual(
254                 _string_literal_expression_parser(0, tokenization.tokenize("'Hello, world'")),
255                 (
256                     True,
257                     1,
258                     FurStringLiteralExpression(value='Hello, world'),
259                 ),
260             )
261
262     class FurFunctionCallExpressionParserTests(unittest.TestCase):
263         def test_parses_function_with_string_literal_argument(self):
264             self.assertEqual(
265                 _function_call_expression_parser(0, tokenization.tokenize("print('Hello, world')")),
266                 (
267                     True,
268                     4,
269                     FurFunctionCallExpression(
270                         name='print',
271                         arguments=(FurStringLiteralExpression(value='Hello, world'),),
272                     ),
273                 ),
274             )
275
276     unittest.main()