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