Some comments
[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 def _integer_literal_expression_parser(index, tokens):
109     failure = (False, index, None)
110
111     if tokens[index].type != 'integer_literal':
112         return failure
113     value = int(tokens[index].match)
114     index += 1
115
116     return True, index, FurIntegerLiteralExpression(value=value)
117
118 def _string_literal_expression_parser(index, tokens):
119     if tokens[index].type == 'single_quoted_string_literal':
120         return (True, index + 1, FurStringLiteralExpression(value=tokens[index].match[1:-1]))
121
122     return (False, index, None)
123
124 def _symbol_expression_parser(index, tokens):
125     if tokens[index].type == 'symbol':
126         return (True, index + 1, FurSymbolExpression(value=tokens[index].match))
127
128     return (False, index, None)
129
130 def _parenthesized_expression_parser(index, tokens):
131     failure = (False, index, None)
132
133     if tokens[index].type == 'open_parenthese':
134         index += 1
135     else:
136         return failure
137
138     success, index, internal = _expression_parser(index, tokens)
139     if not success:
140         return failure
141
142     if tokens[index].type == 'close_parenthese':
143         index += 1
144     else:
145         raise Exception('Expected ")" on line {}, found "{}"'.format(
146             tokens[index].line,
147             tokens[index].match,
148         ))
149
150     return True, index, FurParenthesizedExpression(internal=internal)
151
152 def _negation_expression_parser(index, tokens):
153     failure = (False, index, None)
154
155     if tokens[index].match != '-':
156         return failure
157
158     success, index, value = _literal_level_expression_parser(index + 1, tokens)
159
160     if not success:
161         return failure
162
163     return (True, index, FurNegationExpression(value=value))
164
165 def _literal_level_expression_parser(index, tokens):
166     return _or_parser(
167         _negation_expression_parser,
168         _function_call_expression_parser,
169         _parenthesized_expression_parser,
170         _integer_literal_expression_parser,
171         _string_literal_expression_parser,
172         _symbol_expression_parser,
173     )(index, tokens)
174
175 def _multiplication_level_expression_parser(index, tokens):
176     failure = (False, index, None)
177
178     success, index, result = _literal_level_expression_parser(index, tokens)
179
180     if not success:
181         return failure
182
183     while success and index < len(tokens) and tokens[index].type == 'multiplication_level_operator':
184         success = False
185
186         if index + 1 < len(tokens):
187             success, try_index, value = _literal_level_expression_parser(index + 1, tokens)
188
189         if success:
190             result = {
191                 '*': FurMultiplicationExpression,
192                 '//': FurIntegerDivisionExpression,
193                 '%': FurModularDivisionExpression,
194             }[tokens[index].match](left=result, right=value)
195             index = try_index
196
197     return True, index, result
198
199 def _addition_level_expression_parser(index, tokens):
200     failure = (False, index, None)
201
202     success, index, result = _multiplication_level_expression_parser(index, tokens)
203
204     if not success:
205         return failure
206
207     while success and index < len(tokens) and tokens[index].type == 'addition_level_operator':
208         success = False
209
210         if index + 1 < len(tokens):
211             success, try_index, value = _multiplication_level_expression_parser(index + 1, tokens)
212
213         if success:
214             result = {
215                 '+': FurAdditionExpression,
216                 '-': FurSubtractionExpression,
217             }[tokens[index].match](left=result, right=value)
218             index = try_index
219
220     return True, index, result
221
222 def _comma_separated_list_parser(index, tokens):
223     failure = (False, index, None)
224
225     expressions = []
226
227     success, index, expression = _addition_level_expression_parser(index, tokens)
228
229     if success:
230         expressions.append(expression)
231     else:
232         return failure
233
234     while success and index < len(tokens) and tokens[index].type == 'comma':
235         success = False
236
237         if index + 1 < len(tokens):
238             success, try_index, expression = _addition_level_expression_parser(index + 1, tokens)
239
240         if success:
241             expressions.append(expression)
242             index = try_index
243
244     return True, index, tuple(expressions)
245
246
247 FurFunctionCallExpression = collections.namedtuple(
248     'FurFunctionCallExpression',
249     [
250         'function',
251         'arguments',
252     ],
253 )
254
255 FurAssignmentStatement = collections.namedtuple(
256     'FurAssignmentStatement',
257     [
258         'target',
259         'expression',
260     ],
261 )
262
263 FurProgram = collections.namedtuple(
264     'FurProgram',
265     [
266         'statement_list',
267     ],
268 )
269
270 def _function_call_expression_parser(index, tokens):
271     # TODO Use a FurSymbolExpression for the name
272     failure = (False, index, None)
273
274     success, index, function = _symbol_expression_parser(index, tokens)
275
276     if not success:
277         return failure
278
279     if tokens[index].type != 'open_parenthese':
280         return failure
281     index += 1
282
283     success, index, arguments = _comma_separated_list_parser(index, tokens)
284
285     if not success:
286         return failure
287
288     if tokens[index].type != 'close_parenthese':
289         raise Exception('Expected ")", found "{}" on line {}'.format(
290             tokens[index].match,
291             tokens[index].line,
292         ))
293     index += 1
294
295     return True, index, FurFunctionCallExpression(function=function, arguments=arguments)
296
297 _expression_parser = _addition_level_expression_parser
298
299 def _assignment_statement_parser(index, tokens):
300     # TODO Use a FurSymbolExpression for the target? Maybe this is actually not a good idea
301     failure = (False, index, None)
302
303     if tokens[index].type != 'symbol':
304         return failure
305     target = tokens[index].match
306     index += 1
307
308     if tokens[index].type != 'assignment_operator':
309         return failure
310     assignment_operator_index = index
311
312     success, index, expression = _expression_parser(index + 1, tokens)
313
314     if not success:
315         raise Exception(
316             'Expected expression after assignment operator on line {}'.format(
317                 tokens[assignment_operator_index].line
318             )
319         )
320
321     return True, index, FurAssignmentStatement(target=target, expression=expression)
322
323 def _statement_parser(index, tokens):
324     # 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)
325     return _or_parser(
326         _assignment_statement_parser,
327         _expression_parser,
328     )(index, tokens)
329
330 def _program_formatter(statement_list):
331     return FurProgram(statement_list=statement_list)
332
333 _program_parser = _zero_or_more_parser(_program_formatter, _statement_parser)
334
335 def _parse(parser, tokens):
336     success, index, result = parser(0, tokens)
337
338     if index < len(tokens):
339         raise Exception('Unable to parse token {}'.format(tokens[index]))
340
341     if success:
342         return result
343
344     raise Exception('Unable to parse')
345
346 def parse(tokens):
347     return _parse(_program_parser, tokens)
348
349 if __name__ == '__main__':
350     import unittest
351
352     import tokenization
353
354     class FurStringLiteralExpressionParserTests(unittest.TestCase):
355         def test_parses_single_quoted_string_literal(self):
356             self.assertEqual(
357                 _string_literal_expression_parser(0, tokenization.tokenize("'Hello, world'")),
358                 (
359                     True,
360                     1,
361                     FurStringLiteralExpression(value='Hello, world'),
362                 ),
363             )
364
365     class FurFunctionCallExpressionParserTests(unittest.TestCase):
366         def test_parses_function_with_string_literal_argument(self):
367             self.assertEqual(
368                 _function_call_expression_parser(0, tokenization.tokenize("print('Hello, world')")),
369                 (
370                     True,
371                     4,
372                     FurFunctionCallExpression(
373                         name='print',
374                         arguments=(FurStringLiteralExpression(value='Hello, world'),),
375                     ),
376                 ),
377             )
378
379     unittest.main()