Add support for lists to IR, clean up
[fur] / crossplatform_ir_generation.py
1 import collections
2
3 import conversion
4
5 def flatten(xses):
6     return tuple(x for xs in xses for x in xs)
7
8 CIRProgram = collections.namedtuple(
9     'CIRProgram',
10     (
11         'entry_list',
12     ),
13 )
14
15 CIRLabel = collections.namedtuple(
16     'CIRLabel',
17     (
18         'label',
19     ),
20 )
21
22 CIRInstruction = collections.namedtuple(
23     'CIRInstruction',
24     (
25         'instruction',
26         'argument',
27     ),
28 )
29
30 def generate_integer_literal(integer):
31     return integer
32
33 def generate_string_literal(string):
34     return '"{}"'.format(string)
35
36 def generate_symbol_literal(symbol):
37     return 'sym({})'.format(symbol)
38
39 def generate_function_call_expression(counters, expression):
40     referenced_entry_list, instruction_list = generate_expression(
41         counters,
42         expression.function_expression,
43     )
44
45     instruction_list += (
46         CIRInstruction(
47             instruction='call',
48             argument=expression.argument_count,
49         ),
50     )
51
52     return referenced_entry_list, instruction_list
53
54 def generate_integer_literal_expression(counters, expression):
55     referenced_entry_list = ()
56     instruction_list = (CIRInstruction(
57         instruction='push_value',
58         argument=generate_integer_literal(expression.integer),
59     ),)
60
61     return referenced_entry_list, instruction_list
62
63 def generate_lambda_expression(counters, expression):
64     if expression.name is None or 'lambda' in expression.name.lower():
65         import ipdb; ipdb.set_trace()
66
67     name_counter = counters.get(expression.name, 0)
68     counters[expression.name] = name_counter + 1
69     label = '{}${}'.format(expression.name, name_counter)
70
71     referenced_entry_list_list = []
72     instruction_list_list = []
73
74     for statement in expression.statement_list:
75         referenced_entry_list, instruction_list = generate_statement(counters, statement)
76         referenced_entry_list_list.append(referenced_entry_list)
77         instruction_list_list.append(instruction_list)
78
79     # Pop from the stack in reversed order, because arguments were pushed onto
80     # the stack in order
81     argument_bindings = tuple(
82         CIRInstruction(instruction='pop', argument='sym({})'.format(arg))
83         for arg in reversed(expression.argument_name_list)
84     )
85
86     lambda_body = flatten(instruction_list_list)
87     assert lambda_body[-1].instruction == 'drop'
88     lambda_body = argument_bindings + lambda_body[:-1] + (CIRInstruction(instruction='return', argument=None),)
89
90     referenced_entry_list_list.append(
91         (CIRLabel(label=label),) + lambda_body,
92     )
93
94     instruction_list = (
95         CIRInstruction(instruction='close', argument=label),
96     )
97
98     return flatten(referenced_entry_list_list), instruction_list
99
100 def generate_list_construct_expression(counters, expression):
101     referenced_entry_list = ()
102     instruction_list = (CIRInstruction(
103         instruction='list',
104         argument=2,
105     ),)
106     return referenced_entry_list, instruction_list
107
108 def generate_string_literal_expression(counters, expression):
109     referenced_entry_list = ()
110     instruction_list = (CIRInstruction(
111         instruction='push_value',
112         argument=generate_string_literal(expression.string),
113     ),)
114
115     return referenced_entry_list, instruction_list
116
117 def generate_symbol_expression(counters, expression):
118     referenced_entry_list = ()
119     instruction_list = (CIRInstruction(
120         instruction='push',
121         argument=generate_symbol_literal(expression.symbol),
122     ),)
123
124     return referenced_entry_list, instruction_list
125
126 def generate_variable_expression(counters, expression):
127     referenced_entry_list = ()
128     instruction_list = (CIRInstruction(
129         instruction='push',
130         argument=generate_symbol_literal(expression.variable),
131     ),)
132
133     return referenced_entry_list, instruction_list
134
135 def generate_expression(counters, expression):
136     return {
137         conversion.CPSFunctionCallExpression: generate_function_call_expression,
138         conversion.CPSIfElseExpression: generate_if_else_expression,
139         conversion.CPSIntegerLiteralExpression: generate_integer_literal_expression,
140         conversion.CPSLambdaExpression: generate_lambda_expression,
141         conversion.CPSListConstructExpression: generate_list_construct_expression,
142         conversion.CPSStringLiteralExpression: generate_string_literal_expression,
143         conversion.CPSSymbolExpression: generate_symbol_expression,
144         conversion.CPSVariableExpression: generate_variable_expression,
145     }[type(expression)](counters, expression)
146
147 def generate_expression_statement(counters, statement):
148     referenced_entry_list, instruction_list = generate_expression(
149         counters,
150         statement.expression,
151     )
152
153     instruction_list += (
154         CIRInstruction(
155             instruction='drop',
156             argument=None,
157         ),
158     )
159
160     return referenced_entry_list, instruction_list
161
162 def generate_if_else_expression(counters, statement):
163     if_counter = counters['if']
164     counters['if'] += 1
165
166     referenced_entry_list_list = []
167
168     condition_referenced_entry_list, condition_instruction_list = generate_expression(
169         counters,
170         statement.condition_expression,
171     )
172
173     if_instruction_list_list = []
174     for if_statement in statement.if_statement_list:
175         referenced_entry_list, instruction_list = generate_statement(counters, if_statement)
176         referenced_entry_list_list.append(referenced_entry_list)
177         if_instruction_list_list.append(instruction_list)
178
179     if_instruction_list = flatten(if_instruction_list_list)
180     assert if_instruction_list[-1].instruction == 'drop'
181     if_instruction_list = if_instruction_list[:-1]
182
183     else_instruction_list_list = []
184
185     for else_statement in statement.else_statement_list:
186         referenced_entry_list, instruction_list = generate_statement(counters, else_statement)
187         referenced_entry_list_list.append(referenced_entry_list)
188         else_instruction_list_list.append(instruction_list)
189
190     else_instruction_list = flatten(else_instruction_list_list)
191     assert else_instruction_list[-1].instruction == 'drop'
192     else_instruction_list = else_instruction_list[:-1]
193
194     if_label = '__if${}__'.format(if_counter)
195     else_label = '__else${}__'.format(if_counter)
196     endif_label = '__endif${}__'.format(if_counter)
197
198     instruction_list = condition_instruction_list + (
199         CIRInstruction(
200             instruction='jump_if_false',
201             argument=else_label,
202         ),
203         CIRInstruction(
204             instruction='jump',
205             argument=if_label,
206         ),
207         CIRLabel(label=if_label),
208     ) + if_instruction_list + (
209         CIRInstruction(
210             instruction='jump',
211             argument=endif_label,
212         ),
213         CIRLabel(label=else_label),
214     ) + else_instruction_list + (
215         CIRLabel(label=endif_label),
216     )
217
218     return (
219         condition_referenced_entry_list + flatten(referenced_entry_list_list),
220         instruction_list,
221     )
222
223 def generate_assignment_statement(counters, statement):
224     referenced_entry_list, instruction_list = generate_expression(
225         counters,
226         statement.expression,
227     )
228
229     instruction_list += (
230         CIRInstruction(
231             instruction='pop',
232             argument=generate_symbol_literal(statement.target),
233         ),
234     )
235
236     return referenced_entry_list, instruction_list
237
238 def generate_push_statement(counters, statement):
239     return generate_expression(counters, statement.expression)
240
241 def generate_variable_initialization_statement(counters, statement):
242     referenced_entry_list, instruction_list = generate_expression(
243         counters,
244         statement.expression,
245     )
246
247     instruction_list += (
248         CIRInstruction(
249             instruction='pop',
250             argument=generate_symbol_literal(statement.variable),
251         ),
252     )
253
254     return referenced_entry_list, instruction_list
255
256 def generate_statement(counters, statement):
257     return {
258         conversion.CPSAssignmentStatement: generate_assignment_statement,
259         conversion.CPSExpressionStatement: generate_expression_statement,
260         conversion.CPSPushStatement: generate_push_statement,
261         conversion.CPSVariableInitializationStatement: generate_variable_initialization_statement,
262     }[type(statement)](counters, statement)
263
264 def generate(converted):
265     referenced_entry_list_list = []
266     instruction_list_list = []
267     counters = {
268         'if': 0,
269     }
270
271     for statement in converted.statement_list:
272         referenced_entry_list, instruction_list = generate_statement(counters, statement)
273         referenced_entry_list_list.append(referenced_entry_list)
274         instruction_list_list.append(instruction_list)
275
276     return CIRProgram(
277         entry_list=flatten(referenced_entry_list_list) + (
278             CIRLabel(label='__main__'),
279         ) + flatten(instruction_list_list),
280     )
281
282 NO_ARGUMENT_INSTRUCTIONS = set([
283     'drop',
284     'return',
285 ])
286
287 def format_argument(arg):
288     if arg is None:
289         return 'nil'
290     return arg
291
292 def output(program):
293     lines = []
294
295     for entry in program.entry_list:
296         if isinstance(entry, CIRInstruction):
297             if entry.instruction in NO_ARGUMENT_INSTRUCTIONS and entry.argument is None:
298                 lines.append('    {}'.format(entry.instruction))
299             else:
300                 lines.append('    {} {}'.format(entry.instruction, format_argument(entry.argument)))
301
302         if isinstance(entry, CIRLabel):
303             lines.append('\n{}:'.format(entry.label))
304
305     return '\n'.join(lines).lstrip()