Add newlines to the parsing of statements
[fur] / parsing.py
1 import collections
2
3 def consume_newlines(index, tokens):
4     while index < len(tokens) and tokens[index].type == 'newline':
5         index += 1
6
7     return True, index, None
8
9 def _or_parser(*parsers):
10     def result_parser(index, tokens):
11         failure = (False, index, None)
12
13         for parser in parsers:
14             success, index, value = parser(index, tokens)
15
16             if success:
17                 return (success, index, value)
18
19         return failure
20
21     return result_parser
22
23 def _zero_or_more_parser(formatter, parser):
24     def result_parser(index, tokens):
25         values = []
26
27         while index < len(tokens):
28             success, index, value = parser(index, tokens)
29
30             if success:
31                 values.append(value)
32             else:
33                 break
34
35         return (True, index, formatter(values))
36
37     return result_parser
38
39 FurIntegerLiteralExpression = collections.namedtuple(
40     'FurIntegerLiteralExpression',
41     [
42         'value',
43     ],
44 )
45
46 FurStringLiteralExpression = collections.namedtuple(
47     'FurStringLiteralExpression',
48     [
49         'value',
50     ],
51 )
52
53 FurSymbolExpression = collections.namedtuple(
54     'FurSymbolExpression',
55     [
56         'value',
57     ],
58 )
59
60 FurNegationExpression = collections.namedtuple(
61     'FurNegationExpression',
62     [
63         'value',
64     ],
65 )
66
67 FurParenthesizedExpression = collections.namedtuple(
68     'FurParenthesizedExpression',
69     [
70         'internal',
71     ],
72 )
73
74 FurInfixExpression = collections.namedtuple(
75     'FurInfixExpression',
76     [
77         'order',
78         'operator',
79         'left',
80         'right',
81     ],
82 )
83
84 def _integer_literal_expression_parser(index, tokens):
85     failure = (False, index, None)
86
87     if tokens[index].type != 'integer_literal':
88         return failure
89     value = int(tokens[index].match)
90     index += 1
91
92     return True, index, FurIntegerLiteralExpression(value=value)
93
94 def _string_literal_expression_parser(index, tokens):
95     if tokens[index].type == 'single_quoted_string_literal':
96         return (True, index + 1, FurStringLiteralExpression(value=tokens[index].match[1:-1]))
97
98     return (False, index, None)
99
100 def _symbol_expression_parser(index, tokens):
101     if tokens[index].type == 'symbol':
102         return (True, index + 1, FurSymbolExpression(value=tokens[index].match))
103
104     return (False, index, None)
105
106 def _parenthesized_expression_parser(index, tokens):
107     failure = (False, index, None)
108
109     if tokens[index].type == 'open_parenthese':
110         index += 1
111     else:
112         return failure
113
114     success, index, internal = _expression_parser(index, tokens)
115     if not success:
116         return failure
117
118     if tokens[index].type == 'close_parenthese':
119         index += 1
120     else:
121         raise Exception('Expected ")" on line {}, found "{}"'.format(
122             tokens[index].line,
123             tokens[index].match,
124         ))
125
126     return True, index, FurParenthesizedExpression(internal=internal)
127
128 def _negation_expression_parser(index, tokens):
129     failure = (False, index, None)
130
131     if tokens[index].match != '-':
132         return failure
133
134     success, index, value = _literal_level_expression_parser(index + 1, tokens)
135
136     if not success:
137         return failure
138
139     return (True, index, FurNegationExpression(value=value))
140
141 def _literal_level_expression_parser(index, tokens):
142     return _or_parser(
143         _negation_expression_parser,
144         _function_call_expression_parser,
145         _parenthesized_expression_parser,
146         _integer_literal_expression_parser,
147         _string_literal_expression_parser,
148         _symbol_expression_parser,
149     )(index, tokens)
150
151 def _left_recursive_infix_operator_parser(operator_token_matcher, operand_parser, order):
152     def result_parser(index, tokens):
153         failure = (False, index, None)
154
155         success, index, result = operand_parser(index, tokens)
156
157         if not success:
158             return failure
159
160         while success and index < len(tokens) and operator_token_matcher(tokens[index]):
161             success = False
162
163             if index + 1 < len(tokens):
164                 success, try_index, value = operand_parser(index + 1, tokens)
165
166             if success:
167                 result = FurInfixExpression(
168                     order=order,
169                     operator=tokens[index].match,
170                     left=result,
171                     right=value,
172                 )
173                 index = try_index
174
175         return True, index, result
176
177     return result_parser
178
179 def _multiplication_level_expression_parser(index, tokens):
180     return _left_recursive_infix_operator_parser(
181         lambda token: token.type == 'multiplication_level_operator',
182         _literal_level_expression_parser,
183         'multiplication_level',
184     )(index, tokens)
185
186 def _addition_level_expression_parser(index, tokens):
187     return _left_recursive_infix_operator_parser(
188         lambda token: token.type == 'addition_level_operator',
189         _multiplication_level_expression_parser,
190         'addition_level',
191     )(index, tokens)
192
193 def _equality_level_expression_parser(index, tokens):
194     return _left_recursive_infix_operator_parser(
195         lambda token: token.type == 'equality_level_operator',
196         _addition_level_expression_parser,
197         'equality_level',
198     )(index, tokens)
199
200 def _and_level_expression_parser(index, tokens):
201     return _left_recursive_infix_operator_parser(
202         lambda token: token.type == 'symbol' and token.match == 'and',
203         _equality_level_expression_parser,
204         'and_level',
205     )(index, tokens)
206
207 def _or_level_expression_parser(index, tokens):
208     return _left_recursive_infix_operator_parser(
209         lambda token: token.type == 'symbol' and token.match == 'or',
210         _and_level_expression_parser,
211         'or_level',
212     )(index, tokens)
213
214 def _comma_separated_list_parser(index, tokens):
215     failure = (False, index, None)
216
217     expressions = []
218
219     success, index, expression = _expression_parser(index, tokens)
220
221     if success:
222         expressions.append(expression)
223     else:
224         return failure
225
226     while success and index < len(tokens) and tokens[index].type == 'comma':
227         success = False
228
229         if index + 1 < len(tokens):
230             success, try_index, expression = _expression_parser(index + 1, tokens)
231
232         if success:
233             expressions.append(expression)
234             index = try_index
235
236     return True, index, tuple(expressions)
237
238
239 FurFunctionCallExpression = collections.namedtuple(
240     'FurFunctionCallExpression',
241     [
242         'function',
243         'arguments',
244     ],
245 )
246
247 FurAssignmentStatement = collections.namedtuple(
248     'FurAssignmentStatement',
249     [
250         'target',
251         'expression',
252     ],
253 )
254
255 FurProgram = collections.namedtuple(
256     'FurProgram',
257     [
258         'statement_list',
259     ],
260 )
261
262 def _function_call_expression_parser(index, tokens):
263     # TODO Use a FurSymbolExpression for the name
264     failure = (False, index, None)
265
266     success, index, function = _symbol_expression_parser(index, tokens)
267
268     if not success:
269         return failure
270
271     if tokens[index].type != 'open_parenthese':
272         return failure
273     index += 1
274
275     success, index, arguments = _comma_separated_list_parser(index, tokens)
276
277     if not success:
278         return failure
279
280     if tokens[index].type != 'close_parenthese':
281         raise Exception('Expected ")", found "{}" on line {}'.format(
282             tokens[index].match,
283             tokens[index].line,
284         ))
285     index += 1
286
287     return True, index, FurFunctionCallExpression(function=function, arguments=arguments)
288
289 _expression_parser = _or_level_expression_parser
290
291 def _assignment_statement_parser(index, tokens):
292     # TODO Use a FurSymbolExpression for the target? Maybe this is actually not a good idea
293     failure = (False, index, None)
294
295     if tokens[index].type != 'symbol':
296         return failure
297     target = tokens[index].match
298     index += 1
299
300     if tokens[index].type != 'assignment_operator':
301         return failure
302     assignment_operator_index = index
303
304     success, index, expression = _expression_parser(index + 1, tokens)
305
306     if not success:
307         raise Exception(
308             'Expected expression after assignment operator on line {}'.format(
309                 tokens[assignment_operator_index].line
310             )
311         )
312
313     return True, index, FurAssignmentStatement(target=target, expression=expression)
314
315 def _statement_parser(index, tokens):
316     _, index, _ = consume_newlines(index, tokens)
317
318     if index == len(tokens):
319         return (False, index, None)
320
321     return _or_parser(
322         _assignment_statement_parser,
323         _expression_parser,
324     )(index, tokens)
325
326 def _program_formatter(statement_list):
327     return FurProgram(statement_list=statement_list)
328
329 _program_parser = _zero_or_more_parser(_program_formatter, _statement_parser)
330
331 def _parse(parser, tokens):
332     success, index, result = parser(0, tokens)
333
334     if index < len(tokens):
335         raise Exception('Unable to parse token {}'.format(tokens[index]))
336
337     if success:
338         return result
339
340     raise Exception('Unable to parse')
341
342 def parse(tokens):
343     return _parse(_program_parser, tokens)
344
345 if __name__ == '__main__':
346     import unittest
347
348     import tokenization
349
350     class FurStringLiteralExpressionParserTests(unittest.TestCase):
351         def test_parses_single_quoted_string_literal(self):
352             self.assertEqual(
353                 _string_literal_expression_parser(0, tokenization.tokenize("'Hello, world'")),
354                 (
355                     True,
356                     1,
357                     FurStringLiteralExpression(value='Hello, world'),
358                 ),
359             )
360
361     class FurFunctionCallExpressionParserTests(unittest.TestCase):
362         def test_parses_function_with_string_literal_argument(self):
363             self.assertEqual(
364                 _function_call_expression_parser(0, tokenization.tokenize("print('Hello, world')")),
365                 (
366                     True,
367                     4,
368                     FurFunctionCallExpression(
369                         name='print',
370                         arguments=(FurStringLiteralExpression(value='Hello, world'),),
371                     ),
372                 ),
373             )
374
375     unittest.main()