Removed duplication in left recursive infix operator parsers
[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 FurSymbolExpression = collections.namedtuple(
48     'FurSymbolExpression',
49     [
50         'value',
51     ],
52 )
53
54 FurNegationExpression = collections.namedtuple(
55     'FurNegationExpression',
56     [
57         'value',
58     ],
59 )
60
61 FurParenthesizedExpression = collections.namedtuple(
62     'FurParenthesizedExpression',
63     [
64         'internal',
65     ],
66 )
67
68 FurAdditionExpression = collections.namedtuple(
69     'FurAdditionExpression',
70     [
71         'left',
72         'right',
73     ],
74 )
75
76 FurSubtractionExpression = collections.namedtuple(
77     'FurSubtractionExpression',
78     [
79         'left',
80         'right',
81     ],
82 )
83
84 FurMultiplicationExpression = collections.namedtuple(
85     'FurMultiplicationExpression',
86     [
87         'left',
88         'right',
89     ],
90 )
91
92 FurIntegerDivisionExpression = collections.namedtuple(
93     'FurIntegerDivisionExpression',
94     [
95         'left',
96         'right',
97     ],
98 )
99
100 FurModularDivisionExpression = collections.namedtuple(
101     'FurModularDivisionExpression',
102     [
103         'left',
104         'right',
105     ],
106 )
107
108 FurEqualityExpression = collections.namedtuple(
109     'FurEqualityExpression',
110     [
111         'left',
112         'right',
113     ],
114 )
115
116 FurInequalityExpression = collections.namedtuple(
117     'FurInequalityExpression',
118     [
119         'left',
120         'right',
121     ],
122 )
123
124 FurLessThanOrEqualExpression = collections.namedtuple(
125     'FurLessThanOrEqualExpression',
126     [
127         'left',
128         'right',
129     ],
130 )
131
132 FurGreaterThanOrEqualExpression = collections.namedtuple(
133     'FurGreaterThanOrEqualExpression',
134     [
135         'left',
136         'right',
137     ],
138 )
139
140 FurLessThanExpression = collections.namedtuple(
141     'FurLessThanExpression',
142     [
143         'left',
144         'right',
145     ],
146 )
147
148 FurGreaterThanExpression = collections.namedtuple(
149     'FurGreaterThanExpression',
150     [
151         'left',
152         'right',
153     ],
154 )
155
156 def _integer_literal_expression_parser(index, tokens):
157     failure = (False, index, None)
158
159     if tokens[index].type != 'integer_literal':
160         return failure
161     value = int(tokens[index].match)
162     index += 1
163
164     return True, index, FurIntegerLiteralExpression(value=value)
165
166 def _string_literal_expression_parser(index, tokens):
167     if tokens[index].type == 'single_quoted_string_literal':
168         return (True, index + 1, FurStringLiteralExpression(value=tokens[index].match[1:-1]))
169
170     return (False, index, None)
171
172 def _symbol_expression_parser(index, tokens):
173     if tokens[index].type == 'symbol':
174         return (True, index + 1, FurSymbolExpression(value=tokens[index].match))
175
176     return (False, index, None)
177
178 def _parenthesized_expression_parser(index, tokens):
179     failure = (False, index, None)
180
181     if tokens[index].type == 'open_parenthese':
182         index += 1
183     else:
184         return failure
185
186     success, index, internal = _expression_parser(index, tokens)
187     if not success:
188         return failure
189
190     if tokens[index].type == 'close_parenthese':
191         index += 1
192     else:
193         raise Exception('Expected ")" on line {}, found "{}"'.format(
194             tokens[index].line,
195             tokens[index].match,
196         ))
197
198     return True, index, FurParenthesizedExpression(internal=internal)
199
200 def _negation_expression_parser(index, tokens):
201     failure = (False, index, None)
202
203     if tokens[index].match != '-':
204         return failure
205
206     success, index, value = _literal_level_expression_parser(index + 1, tokens)
207
208     if not success:
209         return failure
210
211     return (True, index, FurNegationExpression(value=value))
212
213 def _literal_level_expression_parser(index, tokens):
214     return _or_parser(
215         _negation_expression_parser,
216         _function_call_expression_parser,
217         _parenthesized_expression_parser,
218         _integer_literal_expression_parser,
219         _string_literal_expression_parser,
220         _symbol_expression_parser,
221     )(index, tokens)
222
223 def _left_recursive_infix_operator_parser(token_type, operand_parser, operator_to_expression_type_mapping):
224     def result_parser(index, tokens):
225         failure = (False, index, None)
226
227         success, index, result = operand_parser(index, tokens)
228
229         if not success:
230             return failure
231
232         while success and index < len(tokens) and tokens[index].type == token_type:
233             success = False
234
235             if index + 1 < len(tokens):
236                 success, try_index, value = operand_parser(index + 1, tokens)
237
238             if success:
239                 result = operator_to_expression_type_mapping[tokens[index].match](left=result, right=value)
240                 index = try_index
241
242         return True, index, result
243
244     return result_parser
245
246 def _multiplication_level_expression_parser(index, tokens):
247     return _left_recursive_infix_operator_parser(
248         'multiplication_level_operator',
249         _literal_level_expression_parser,
250         {
251             '*': FurMultiplicationExpression,
252             '//': FurIntegerDivisionExpression,
253             '%': FurModularDivisionExpression,
254         },
255     )(index, tokens)
256
257 def _addition_level_expression_parser(index, tokens):
258     return _left_recursive_infix_operator_parser(
259         'addition_level_operator',
260         _multiplication_level_expression_parser,
261         {
262             '+': FurAdditionExpression,
263             '-': FurSubtractionExpression,
264         },
265     )(index, tokens)
266
267 def _equality_level_expression_parser(index, tokens):
268     return _left_recursive_infix_operator_parser(
269         'equality_level_operator',
270         _addition_level_expression_parser,
271         {
272             '==': FurEqualityExpression,
273             '!=': FurInequalityExpression,
274             '>=': FurGreaterThanOrEqualExpression,
275             '<=': FurLessThanOrEqualExpression,
276             '>': FurGreaterThanExpression,
277             '<': FurLessThanExpression,
278         },
279     )(index, tokens)
280
281 def _comma_separated_list_parser(index, tokens):
282     failure = (False, index, None)
283
284     expressions = []
285
286     success, index, expression = _expression_parser(index, tokens)
287
288     if success:
289         expressions.append(expression)
290     else:
291         return failure
292
293     while success and index < len(tokens) and tokens[index].type == 'comma':
294         success = False
295
296         if index + 1 < len(tokens):
297             success, try_index, expression = _expression_parser(index + 1, tokens)
298
299         if success:
300             expressions.append(expression)
301             index = try_index
302
303     return True, index, tuple(expressions)
304
305
306 FurFunctionCallExpression = collections.namedtuple(
307     'FurFunctionCallExpression',
308     [
309         'function',
310         'arguments',
311     ],
312 )
313
314 FurAssignmentStatement = collections.namedtuple(
315     'FurAssignmentStatement',
316     [
317         'target',
318         'expression',
319     ],
320 )
321
322 FurProgram = collections.namedtuple(
323     'FurProgram',
324     [
325         'statement_list',
326     ],
327 )
328
329 def _function_call_expression_parser(index, tokens):
330     # TODO Use a FurSymbolExpression for the name
331     failure = (False, index, None)
332
333     success, index, function = _symbol_expression_parser(index, tokens)
334
335     if not success:
336         return failure
337
338     if tokens[index].type != 'open_parenthese':
339         return failure
340     index += 1
341
342     success, index, arguments = _comma_separated_list_parser(index, tokens)
343
344     if not success:
345         return failure
346
347     if tokens[index].type != 'close_parenthese':
348         raise Exception('Expected ")", found "{}" on line {}'.format(
349             tokens[index].match,
350             tokens[index].line,
351         ))
352     index += 1
353
354     return True, index, FurFunctionCallExpression(function=function, arguments=arguments)
355
356 _expression_parser = _equality_level_expression_parser
357
358 def _assignment_statement_parser(index, tokens):
359     # TODO Use a FurSymbolExpression for the target? Maybe this is actually not a good idea
360     failure = (False, index, None)
361
362     if tokens[index].type != 'symbol':
363         return failure
364     target = tokens[index].match
365     index += 1
366
367     if tokens[index].type != 'assignment_operator':
368         return failure
369     assignment_operator_index = index
370
371     success, index, expression = _expression_parser(index + 1, tokens)
372
373     if not success:
374         raise Exception(
375             'Expected expression after assignment operator on line {}'.format(
376                 tokens[assignment_operator_index].line
377             )
378         )
379
380     return True, index, FurAssignmentStatement(target=target, expression=expression)
381
382 def _statement_parser(index, tokens):
383     # TODO It would be good to include newlines in the parsing of this because it removes the ambiguity between "function(argument)" (one statement) and "function\n(argument)" (two statements)
384     return _or_parser(
385         _assignment_statement_parser,
386         _expression_parser,
387     )(index, tokens)
388
389 def _program_formatter(statement_list):
390     return FurProgram(statement_list=statement_list)
391
392 _program_parser = _zero_or_more_parser(_program_formatter, _statement_parser)
393
394 def _parse(parser, tokens):
395     success, index, result = parser(0, tokens)
396
397     if index < len(tokens):
398         raise Exception('Unable to parse token {}'.format(tokens[index]))
399
400     if success:
401         return result
402
403     raise Exception('Unable to parse')
404
405 def parse(tokens):
406     return _parse(_program_parser, tokens)
407
408 if __name__ == '__main__':
409     import unittest
410
411     import tokenization
412
413     class FurStringLiteralExpressionParserTests(unittest.TestCase):
414         def test_parses_single_quoted_string_literal(self):
415             self.assertEqual(
416                 _string_literal_expression_parser(0, tokenization.tokenize("'Hello, world'")),
417                 (
418                     True,
419                     1,
420                     FurStringLiteralExpression(value='Hello, world'),
421                 ),
422             )
423
424     class FurFunctionCallExpressionParserTests(unittest.TestCase):
425         def test_parses_function_with_string_literal_argument(self):
426             self.assertEqual(
427                 _function_call_expression_parser(0, tokenization.tokenize("print('Hello, world')")),
428                 (
429                     True,
430                     4,
431                     FurFunctionCallExpression(
432                         name='print',
433                         arguments=(FurStringLiteralExpression(value='Hello, world'),),
434                     ),
435                 ),
436             )
437
438     unittest.main()