Implement variable length arguments, but really only for print
[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_instruction_name_from_builtin(builtin):
40     try:
41         return {
42             '__add__': 'add',
43             '__integer_divide__': 'idiv',
44             '__modular_divide__': 'mod',
45             '__multiply__': 'mul',
46             '__negate__': 'neg',
47             '__subtract__': 'sub',
48
49             '__eq__': 'eq',
50             '__neq__': 'neq',
51             '__lt__': 'lt',
52             '__lte__': 'lte',
53             '__gt__': 'gt',
54             '__gte__': 'gte',
55         }[builtin]
56
57     except KeyError:
58         import ipdb; ipdb.set_trace()
59
60 def generate_function_call_expression(counters, expression):
61     if isinstance(expression.function_expression, conversion.CPSBuiltinExpression):
62         return (
63             (),
64             (
65                 CIRInstruction(
66                     instruction=generate_instruction_name_from_builtin(
67                         expression.function_expression.symbol,
68                     ),
69                     argument=expression.argument_count,
70                 ),
71             )
72         )
73
74     referenced_entry_list, instruction_list = generate_expression(
75         counters,
76         expression.function_expression,
77     )
78
79     instruction_list += (
80         CIRInstruction(
81             instruction='call',
82             argument=expression.argument_count,
83         ),
84     )
85
86     return referenced_entry_list, instruction_list
87
88 def generate_integer_literal_expression(counters, expression):
89     referenced_entry_list = ()
90     instruction_list = (CIRInstruction(
91         instruction='push_integer',
92         argument=generate_integer_literal(expression.integer),
93     ),)
94
95     return referenced_entry_list, instruction_list
96
97 def escape_name(name):
98     return name.replace('$','$$').replace('_','$')
99
100 def generate_lambda_expression(counters, expression):
101     if expression.name is None:
102         name = '__lambda__'
103     else:
104         name = escape_name(expression.name)
105
106     name_counter = counters.get(name, 0)
107     counters[expression.name] = name_counter + 1
108     label = '{}${}'.format(name, name_counter)
109
110     referenced_entry_list_list = []
111     instruction_list_list = []
112
113     for statement in expression.statement_list:
114         referenced_entry_list, instruction_list = generate_statement(counters, statement)
115         referenced_entry_list_list.append(referenced_entry_list)
116         instruction_list_list.append(instruction_list)
117
118     # Pop from the stack in reversed order, because arguments were pushed onto
119     # the stack in order
120     argument_bindings = tuple(
121         CIRInstruction(instruction='pop', argument='sym({})'.format(arg))
122         for arg in reversed(expression.argument_name_list)
123     )
124
125     lambda_body = flatten(instruction_list_list)
126     assert lambda_body[-1].instruction == 'drop'
127     lambda_body = argument_bindings + lambda_body[:-1] + (CIRInstruction(instruction='return', argument=None),)
128
129     referenced_entry_list_list.append(
130         (CIRLabel(label=label),) + lambda_body,
131     )
132
133     instruction_list = (
134         CIRInstruction(instruction='close', argument=label),
135     )
136
137     return flatten(referenced_entry_list_list), instruction_list
138
139 def generate_list_construct_expression(counters, expression):
140     referenced_entry_list = ()
141     instruction_list = (CIRInstruction(
142         instruction='list',
143         argument=2,
144     ),)
145     return referenced_entry_list, instruction_list
146
147 def generate_string_literal_expression(counters, expression):
148     referenced_entry_list = ()
149     instruction_list = (CIRInstruction(
150         instruction='push_string',
151         argument=generate_string_literal(expression.string),
152     ),)
153
154     return referenced_entry_list, instruction_list
155
156 def generate_structure_literal_expression(counters, expression):
157     referenced_entry_list = ()
158     instruction_list = (CIRInstruction(
159         instruction='structure',
160         argument=expression.field_count,
161     ),)
162
163     return referenced_entry_list, instruction_list
164
165 def generate_symbol_expression(counters, expression):
166     referenced_entry_list = ()
167     instruction_list = (CIRInstruction(
168         instruction='push',
169         argument=generate_symbol_literal(expression.symbol),
170     ),)
171
172     return referenced_entry_list, instruction_list
173
174 def generate_symbol_literal_expression(counters, expression):
175     referenced_entry_list = ()
176     instruction_list = (CIRInstruction(
177         instruction='push_symbol',
178         argument=generate_symbol_literal(expression.symbol),
179     ),)
180
181     return referenced_entry_list, instruction_list
182
183 def generate_variable_expression(counters, expression):
184     referenced_entry_list = ()
185     instruction_list = (CIRInstruction(
186         instruction='push',
187         argument=generate_symbol_literal(expression.variable),
188     ),)
189
190     return referenced_entry_list, instruction_list
191
192 def generate_expression(counters, expression):
193     return {
194         conversion.CPSFunctionCallExpression: generate_function_call_expression,
195         conversion.CPSIfElseExpression: generate_if_else_expression,
196         conversion.CPSIntegerLiteralExpression: generate_integer_literal_expression,
197         conversion.CPSLambdaExpression: generate_lambda_expression,
198         conversion.CPSListConstructExpression: generate_list_construct_expression,
199         conversion.CPSStringLiteralExpression: generate_string_literal_expression,
200         conversion.CPSStructureLiteralExpression: generate_structure_literal_expression,
201         conversion.CPSSymbolExpression: generate_symbol_expression,
202         conversion.CPSSymbolLiteralExpression: generate_symbol_literal_expression,
203         conversion.CPSVariableExpression: generate_variable_expression,
204     }[type(expression)](counters, expression)
205
206 def generate_expression_statement(counters, statement):
207     referenced_entry_list, instruction_list = generate_expression(
208         counters,
209         statement.expression,
210     )
211
212     instruction_list += (
213         CIRInstruction(
214             instruction='drop',
215             argument=None,
216         ),
217     )
218
219     return referenced_entry_list, instruction_list
220
221 def generate_if_else_expression(counters, statement):
222     if_counter = counters['if']
223     counters['if'] += 1
224
225     referenced_entry_list_list = []
226
227     condition_referenced_entry_list, condition_instruction_list = generate_expression(
228         counters,
229         statement.condition_expression,
230     )
231
232     if_instruction_list_list = []
233     for if_statement in statement.if_statement_list:
234         referenced_entry_list, instruction_list = generate_statement(counters, if_statement)
235         referenced_entry_list_list.append(referenced_entry_list)
236         if_instruction_list_list.append(instruction_list)
237
238     if_instruction_list = flatten(if_instruction_list_list)
239     assert if_instruction_list[-1].instruction == 'drop'
240     if_instruction_list = if_instruction_list[:-1]
241
242     else_instruction_list_list = []
243
244     for else_statement in statement.else_statement_list:
245         referenced_entry_list, instruction_list = generate_statement(counters, else_statement)
246         referenced_entry_list_list.append(referenced_entry_list)
247         else_instruction_list_list.append(instruction_list)
248
249     else_instruction_list = flatten(else_instruction_list_list)
250     assert else_instruction_list[-1].instruction == 'drop'
251     else_instruction_list = else_instruction_list[:-1]
252
253     if_label = '__if${}__'.format(if_counter)
254     else_label = '__else${}__'.format(if_counter)
255     endif_label = '__endif${}__'.format(if_counter)
256
257     instruction_list = condition_instruction_list + (
258         CIRInstruction(
259             instruction='jump_if_false',
260             argument=else_label,
261         ),
262         CIRInstruction(
263             instruction='jump',
264             argument=if_label,
265         ),
266         CIRLabel(label=if_label),
267     ) + if_instruction_list + (
268         CIRInstruction(
269             instruction='jump',
270             argument=endif_label,
271         ),
272         CIRLabel(label=else_label),
273     ) + else_instruction_list + (
274         CIRLabel(label=endif_label),
275     )
276
277     return (
278         condition_referenced_entry_list + flatten(referenced_entry_list_list),
279         instruction_list,
280     )
281
282 def generate_assignment_statement(counters, statement):
283     referenced_entry_list, instruction_list = generate_expression(
284         counters,
285         statement.expression,
286     )
287
288     instruction_list += (
289         CIRInstruction(
290             instruction='pop',
291             argument=generate_symbol_literal(statement.target),
292         ),
293     )
294
295     return referenced_entry_list, instruction_list
296
297 def generate_push_statement(counters, statement):
298     return generate_expression(counters, statement.expression)
299
300 def generate_variable_initialization_statement(counters, statement):
301     referenced_entry_list, instruction_list = generate_expression(
302         counters,
303         statement.expression,
304     )
305
306     instruction_list += (
307         CIRInstruction(
308             instruction='pop',
309             argument=generate_symbol_literal(statement.variable),
310         ),
311     )
312
313     return referenced_entry_list, instruction_list
314
315 def generate_statement(counters, statement):
316     return {
317         conversion.CPSAssignmentStatement: generate_assignment_statement,
318         conversion.CPSExpressionStatement: generate_expression_statement,
319         conversion.CPSPushStatement: generate_push_statement,
320         conversion.CPSVariableInitializationStatement: generate_variable_initialization_statement,
321     }[type(statement)](counters, statement)
322
323 def generate(converted):
324     referenced_entry_list_list = []
325     instruction_list_list = []
326     counters = {
327         'if': 0,
328     }
329
330     for statement in converted.statement_list:
331         referenced_entry_list, instruction_list = generate_statement(counters, statement)
332         referenced_entry_list_list.append(referenced_entry_list)
333         instruction_list_list.append(instruction_list)
334
335     return CIRProgram(
336         entry_list=flatten(referenced_entry_list_list) + (
337             CIRLabel(label='__main__'),
338         ) + flatten(instruction_list_list) + (
339             CIRInstruction(instruction='end', argument=None),
340         )
341     )
342
343 NO_ARGUMENT_INSTRUCTIONS = set([
344     'drop',
345     'return',
346 ])
347
348 def format_argument(arg):
349     if arg is None:
350         return 'nil'
351     return arg
352
353 def output(program):
354     lines = []
355
356     for entry in program.entry_list:
357         if isinstance(entry, CIRInstruction):
358             if entry.instruction in NO_ARGUMENT_INSTRUCTIONS and entry.argument is None:
359                 lines.append('    {}'.format(entry.instruction))
360             else:
361                 lines.append('    {} {}'.format(entry.instruction, format_argument(entry.argument)))
362
363         if isinstance(entry, CIRLabel):
364             lines.append('\n{}:'.format(entry.label))
365
366     return '\n'.join(lines).lstrip()