Handle arguments to functions
[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_string_literal_expression(counters, expression):
101     referenced_entry_list = ()
102     instruction_list = (CIRInstruction(
103         instruction='push_value',
104         argument=generate_string_literal(expression.string),
105     ),)
106
107     return referenced_entry_list, instruction_list
108
109 def generate_symbol_expression(counters, expression):
110     referenced_entry_list = ()
111     instruction_list = (CIRInstruction(
112         instruction='push',
113         argument=generate_symbol_literal(expression.symbol),
114     ),)
115
116     return referenced_entry_list, instruction_list
117
118 def generate_variable_expression(counters, expression):
119     referenced_entry_list = ()
120     instruction_list = (CIRInstruction(
121         instruction='push',
122         argument=generate_symbol_literal(expression.variable),
123     ),)
124
125     return referenced_entry_list, instruction_list
126
127 def generate_expression(counters, expression):
128     return {
129         conversion.CPSFunctionCallExpression: generate_function_call_expression,
130         conversion.CPSIfElseExpression: generate_if_else_expression,
131         conversion.CPSIntegerLiteralExpression: generate_integer_literal_expression,
132         conversion.CPSLambdaExpression: generate_lambda_expression,
133         conversion.CPSStringLiteralExpression: generate_string_literal_expression,
134         conversion.CPSSymbolExpression: generate_symbol_expression,
135         conversion.CPSVariableExpression: generate_variable_expression,
136     }[type(expression)](counters, expression)
137
138 def generate_expression_statement(counters, statement):
139     referenced_entry_list, instruction_list = generate_expression(
140         counters,
141         statement.expression,
142     )
143
144     instruction_list += (
145         CIRInstruction(
146             instruction='drop',
147             argument=None,
148         ),
149     )
150
151     return referenced_entry_list, instruction_list
152
153 def generate_if_else_expression(counters, statement):
154     if_counter = counters['if']
155     counters['if'] += 1
156
157     referenced_entry_list_list = []
158
159     condition_referenced_entry_list, condition_instruction_list = generate_expression(
160         counters,
161         statement.condition_expression,
162     )
163
164     if_instruction_list_list = []
165     for if_statement in statement.if_statement_list:
166         referenced_entry_list, instruction_list = generate_statement(counters, if_statement)
167         referenced_entry_list_list.append(referenced_entry_list)
168         if_instruction_list_list.append(instruction_list)
169
170     if_instruction_list = flatten(if_instruction_list_list)
171     assert if_instruction_list[-1].instruction == 'drop'
172     if_instruction_list = if_instruction_list[:-1]
173
174     else_instruction_list_list = []
175
176     for else_statement in statement.else_statement_list:
177         referenced_entry_list, instruction_list = generate_statement(counters, else_statement)
178         referenced_entry_list_list.append(referenced_entry_list)
179         else_instruction_list_list.append(instruction_list)
180
181     else_instruction_list = flatten(else_instruction_list_list)
182     assert else_instruction_list[-1].instruction == 'drop'
183     else_instruction_list = else_instruction_list[:-1]
184
185     if_label = '__if${}__'.format(if_counter)
186     else_label = '__else${}__'.format(if_counter)
187     endif_label = '__endif${}__'.format(if_counter)
188
189     instruction_list = condition_instruction_list + (
190         CIRInstruction(
191             instruction='jump_if_false',
192             argument=else_label,
193         ),
194         CIRInstruction(
195             instruction='jump',
196             argument=if_label,
197         ),
198         CIRLabel(label=if_label),
199     ) + if_instruction_list + (
200         CIRInstruction(
201             instruction='jump',
202             argument=endif_label,
203         ),
204         CIRLabel(label=else_label),
205     ) + else_instruction_list + (
206         CIRLabel(label=endif_label),
207     )
208
209     return (
210         condition_referenced_entry_list + flatten(referenced_entry_list_list),
211         instruction_list,
212     )
213
214 def generate_assignment_statement(counters, statement):
215     referenced_entry_list, instruction_list = generate_expression(
216         counters,
217         statement.expression,
218     )
219
220     instruction_list += (
221         CIRInstruction(
222             instruction='pop',
223             argument=generate_symbol_literal(statement.target),
224         ),
225     )
226
227     return referenced_entry_list, instruction_list
228
229 def generate_push_statement(counters, statement):
230     return generate_expression(counters, statement.expression)
231
232 def generate_variable_initialization_statement(counters, statement):
233     referenced_entry_list, instruction_list = generate_expression(
234         counters,
235         statement.expression,
236     )
237
238     instruction_list += (
239         CIRInstruction(
240             instruction='pop',
241             argument=generate_symbol_literal(statement.variable),
242         ),
243     )
244
245     return referenced_entry_list, instruction_list
246
247 def generate_statement(counters, statement):
248     return {
249         conversion.CPSAssignmentStatement: generate_assignment_statement,
250         conversion.CPSExpressionStatement: generate_expression_statement,
251         conversion.CPSPushStatement: generate_push_statement,
252         conversion.CPSVariableInitializationStatement: generate_variable_initialization_statement,
253     }[type(statement)](counters, statement)
254
255 def generate(converted):
256     referenced_entry_list_list = []
257     instruction_list_list = []
258     counters = {
259         'if': 0,
260     }
261
262     for statement in converted.statement_list:
263         referenced_entry_list, instruction_list = generate_statement(counters, statement)
264         referenced_entry_list_list.append(referenced_entry_list)
265         instruction_list_list.append(instruction_list)
266
267     return CIRProgram(
268         entry_list=flatten(referenced_entry_list_list) + (
269             CIRLabel(label='__main__'),
270         ) + flatten(instruction_list_list),
271     )
272
273 NO_ARGUMENT_INSTRUCTIONS = set([
274     'drop',
275     'return',
276 ])
277
278 def format_argument(arg):
279     if arg is None:
280         return 'nil'
281     return arg
282
283 def output(program):
284     lines = []
285
286     for entry in program.entry_list:
287         if isinstance(entry, CIRInstruction):
288             if entry.instruction in NO_ARGUMENT_INSTRUCTIONS and entry.argument is None:
289                 lines.append('    {}'.format(entry.instruction))
290             else:
291                 lines.append('    {} {}'.format(entry.instruction, format_argument(entry.argument)))
292
293         if isinstance(entry, CIRLabel):
294             lines.append('\n{}:'.format(entry.label))
295
296     return '\n'.join(lines).lstrip()