this repo has no description
1# /// script
2# requires-python = ">=3.12"
3# dependencies = [
4# "unittest-parallel",
5# ]
6# ///
7import tempfile
8import unittest
9from run import run
10
11WORD_SIZE = 8
12FIXNUM_SHIFT = 2
13FIXNUM_MASK = 0b11
14FIXNUM_TAG = 0b00
15CHAR_TAG = 0b00001111
16CHAR_SHIFT = 8
17BOOL_TAG = 0b0011111
18BOOL_SHIFT = 7
19BOOL_MASK = 0b1111111
20BOOL_BIT = 1 << BOOL_SHIFT
21EMPTY_LIST = 0b00101111
22CONS_TAG = 0b001
23CLOSURE_TAG = 0b110
24HEAP_ALIGNMENT = 2 * WORD_SIZE
25
26def immediate_rep(val):
27 match val:
28 case bool(_):
29 return (val << BOOL_SHIFT) | BOOL_TAG
30 case int(_):
31 return val << FIXNUM_SHIFT
32 case Char():
33 return (val.byte << CHAR_SHIFT) | CHAR_TAG
34 case _:
35 raise NotImplementedError(val)
36
37class Char:
38 def __init__(self, c):
39 b = c.encode("utf-8")
40 assert len(b) == 1
41 self.byte = b[0]
42
43 def __eq__(self, other):
44 return isinstance(other, Char) and self.byte == other.byte
45
46 def __repr__(self):
47 return f"Char({self.byte})"
48
49NEXT_LABEL = -1
50
51CLOSURE_BASE = "rdi"
52HEAP_BASE = "rsi"
53
54def indirect(reg, offset):
55 if offset >= 0:
56 return f"[{reg}+{offset}]"
57 else:
58 return f"[{reg}{offset}]"
59
60def stack_at(si):
61 assert si < 0
62 return indirect("rsp", si)
63
64BUILTINS = frozenset({
65 "add1", "integer->char",
66 "char->integer", "null?", "zero?",
67 "not", "integer?", "boolean?", "+",
68 "cons", "car", "cdr",
69})
70
71def compile_expr(expr, code, si, env):
72 emit = code.append
73 def comment(msg):
74 emit(f"# {msg}")
75 def heap_at(offset):
76 return indirect(HEAP_BASE, offset)
77 def unique_label():
78 global NEXT_LABEL
79 NEXT_LABEL += 1
80 return f"L{NEXT_LABEL}"
81 def align(size):
82 if size % HEAP_ALIGNMENT == 0:
83 return size
84 return size + WORD_SIZE
85 match expr:
86 case int(_) | Char():
87 emit(f"mov rax, {immediate_rep(expr)}")
88 case str(_):
89 emit(f"mov rax, {env[expr]}")
90 case []:
91 emit(f"mov rax, {EMPTY_LIST}")
92 case ["add1", e]:
93 compile_expr(e, code, si, env)
94 emit(f"add rax, {immediate_rep(1)}")
95 case ["integer->char", e]:
96 compile_expr(e, code, si, env)
97 emit(f"shl rax, {CHAR_SHIFT-FIXNUM_SHIFT}")
98 emit(f"or rax, {CHAR_TAG}")
99 case ["char->integer", e]:
100 compile_expr(e, code, si, env)
101 emit(f"shr rax, {CHAR_SHIFT-FIXNUM_SHIFT}")
102 case ["null?", e]:
103 compile_expr(e, code, si, env)
104 emit(f"cmp rax, {EMPTY_LIST}")
105 emit(f"mov rax, 0")
106 emit(f"sete al")
107 emit(f"shl rax, {BOOL_SHIFT}")
108 emit(f"or rax, {BOOL_TAG}")
109 case ["zero?", e]:
110 compile_expr(e, code, si, env)
111 emit(f"test rax, rax")
112 emit(f"mov rax, 0")
113 emit(f"sete al")
114 emit(f"shl rax, {BOOL_SHIFT}")
115 emit(f"or rax, {BOOL_TAG}")
116 case ["not", e]:
117 compile_expr(e, code, si, env)
118 emit(f"xor rax, {BOOL_BIT}")
119 case ["integer?", e]:
120 compile_expr(e, code, si, env)
121 emit(f"and al, {FIXNUM_MASK}")
122 emit(f"test al, al")
123 emit(f"mov rax, 0")
124 emit(f"sete al")
125 emit(f"shl rax, {BOOL_SHIFT}")
126 emit(f"or rax, {BOOL_TAG}")
127 case ["boolean?", e]:
128 compile_expr(e, code, si, env)
129 emit(f"and al, {BOOL_MASK}")
130 emit(f"cmp al, {BOOL_TAG}")
131 emit(f"mov rax, 0")
132 emit(f"sete al")
133 emit(f"shl rax, {BOOL_SHIFT}")
134 emit(f"or rax, {BOOL_TAG}")
135 case ["+", e0, e1]:
136 compile_expr(e0, code, si, env)
137 emit(f"mov {stack_at(si)}, rax")
138 compile_expr(e1, code, si-WORD_SIZE, env)
139 emit(f"add rax, {stack_at(si)}")
140 case ["let", bindings, body]:
141 new_env = env.copy()
142 new_si = si
143 for (name, val) in bindings:
144 comment(f"Code for {name}")
145 compile_expr(val, code, new_si, env)
146 comment(f"Store {name} on the stack")
147 emit(f"mov {stack_at(new_si)}, rax")
148 new_env[name] = stack_at(new_si)
149 new_si -= WORD_SIZE
150 compile_expr(body, code, new_si, new_env)
151 case ["if", test, conseq, altern]:
152 L0 = unique_label()
153 L1 = unique_label()
154 compile_expr(test, code, si, env)
155 emit(f"cmp rax, {immediate_rep(False)}")
156 emit(f"je {L0}")
157 compile_expr(conseq, code, si, env)
158 emit(f"jmp {L1}")
159 emit(f"{L0}:")
160 compile_expr(altern, code, si, env)
161 emit(f"{L1}:")
162 case ["cons", car, cdr]:
163 comment("Compile car")
164 compile_expr(car, code, si, env)
165 emit(f"mov {stack_at(si)}, rax")
166 comment("Compile cdr")
167 compile_expr(cdr, code, si-WORD_SIZE, env)
168 emit(f"mov {heap_at(WORD_SIZE)}, rax")
169 emit(f"mov rax, {stack_at(si)}")
170 emit(f"mov {heap_at(0)}, rax")
171 comment("Tag a cons cell")
172 emit(f"lea rax, {heap_at(CONS_TAG)}")
173 size = align(2 * WORD_SIZE)
174 comment("Bump the heap pointer")
175 emit(f"add {HEAP_BASE}, {size}")
176 case ["car", cell]:
177 compile_expr(cell, code, si, env)
178 emit(f"mov rax, {indirect('rax', 0*WORD_SIZE-CONS_TAG)}")
179 case ["cdr", cell]:
180 compile_expr(cell, code, si, env)
181 emit(f"mov rax, {indirect('rax', 1*WORD_SIZE-CONS_TAG)}")
182 case ["labelcall", str(label), *args]:
183 new_si = si - WORD_SIZE # Save a word for the return address
184 for arg in args:
185 compile_expr(arg, code, new_si, env)
186 emit(f"mov {stack_at(new_si)}, rax")
187 new_si -= WORD_SIZE
188 # Align to one word before the return address
189 si_adjust = abs(si+WORD_SIZE)
190 emit(f"sub rsp, {si_adjust}")
191 emit(f"call {label}")
192 emit(f"add rsp, {si_adjust}")
193 case ["funcall", func, *args]:
194 # Save a word for the return address and the closure pointer
195 clo_si = si - WORD_SIZE
196 retaddr_si = clo_si - WORD_SIZE
197 new_si = retaddr_si
198 # Evaluate arguments
199 for arg in args:
200 compile_expr(arg, code, new_si, env)
201 emit(f"mov {stack_at(new_si)}, rax")
202 new_si -= WORD_SIZE
203 compile_expr(func, code, new_si, env)
204 # Save the current closure pointer
205 emit(f"mov {stack_at(clo_si)}, {CLOSURE_BASE}")
206 emit(f"mov {CLOSURE_BASE}, rax")
207 # Align to one word before the return address
208 si_adjust = abs(si)
209 emit(f"sub rsp, {si_adjust}")
210 emit(f"call {indirect(CLOSURE_BASE, -CLOSURE_TAG)}")
211 emit(f"add rsp, {si_adjust}")
212 emit(f"mov {CLOSURE_BASE}, {stack_at(clo_si)}")
213 case ["closure", str(lvar), *args]:
214 comment("Get a pointer to the label")
215 emit(f"lea rax, {lvar}")
216 emit(f"mov {heap_at(0)}, rax")
217 for idx, arg in enumerate(args):
218 assert isinstance(arg, str)
219 comment(f"Load closure cell #{idx}")
220 # Just a variable lookup; guaranteed not to allocate
221 compile_expr(arg, code, si, env)
222 emit(f"mov {heap_at((idx+1)*WORD_SIZE)}, rax")
223 comment("Tag a closure pointer")
224 emit(f"lea rax, {heap_at(CLOSURE_TAG)}")
225 comment("Bump the heap pointer")
226 size = align(WORD_SIZE + len(args)*WORD_SIZE)
227 emit(f"add {HEAP_BASE}, {size}")
228 case _:
229 raise NotImplementedError(expr)
230
231def compile_lexpr(lexpr, code):
232 match lexpr:
233 case ["code", params, freevars, body]:
234 env = {}
235 for idx, param in enumerate(params):
236 env[param] = stack_at(-(idx+1)*WORD_SIZE)
237 for idx, fvar in enumerate(freevars):
238 env[fvar] = indirect(CLOSURE_BASE, (idx+1)*WORD_SIZE - CLOSURE_TAG)
239 compile_expr(body, code, si=-(len(env)+1)*WORD_SIZE, env=env)
240 code.append("ret")
241 case _:
242 raise NotImplementedError(lexpr)
243
244def lift_lambdas_rec(expr, labels, bound, free):
245 match expr:
246 case int(_) | Char():
247 return expr
248 case str(_) if expr in bound or expr in BUILTINS:
249 return expr
250 case str(_):
251 free.add(expr)
252 return expr
253 case ["lambda", params, body]:
254 body_free = set()
255 assert all(isinstance(v, str) for v in params)
256 body = lift_lambdas_rec(body, labels, set(params), body_free)
257 assert all(isinstance(v, str) for v in body_free)
258 free.update(body_free - bound)
259 body_free = sorted(body_free)
260 label = f"f{len(labels)}"
261 labels[label] = ["code", params, body_free, body]
262 return ["closure", label, *body_free]
263 case ["let", bindings, body]:
264 new_bindings = []
265 names = {name for name, _ in bindings}
266 for name, val_expr in bindings:
267 new_bindings.append([name, lift_lambdas_rec(val_expr, labels, bound, free)])
268 new_body = lift_lambdas_rec(body, labels, bound | names, free)
269 return ["let", new_bindings, new_body]
270 case ["if", test, conseq, alt]:
271 return ["if",
272 lift_lambdas_rec(test, labels, bound, free),
273 lift_lambdas_rec(conseq, labels, bound, free),
274 lift_lambdas_rec(alt, labels, bound, free)]
275 case [func, *args]:
276 result = [] if isinstance(func, str) and func in BUILTINS else ["funcall"]
277 for e in expr:
278 result.append(lift_lambdas_rec(e, labels, bound, free))
279 return result
280 case _:
281 raise NotImplementedError(expr)
282
283def lift_lambdas(expr):
284 labels = {}
285 expr = lift_lambdas_rec(expr, labels, set(), set())
286 labels = [[name, code] for name, code in labels.items()]
287 return ["labels", labels, expr]
288
289def compile_program(expr):
290 code = [".intel_syntax", ".global scheme_entry"]
291 match expr:
292 case ["labels", labels, body]:
293 for (lvar, lexpr) in labels:
294 code.append(f"{lvar}:")
295 compile_lexpr(lexpr, code)
296 code.append("scheme_entry:")
297 compile_expr(body, code, si=-WORD_SIZE, env={})
298 code.append("ret")
299 case _:
300 expr = lift_lambdas(expr)
301 assert isinstance(expr, list) and expr[0] == "labels"
302 return compile_program(expr)
303 return "\n".join(code)
304
305class LambdaTests(unittest.TestCase):
306 def test_int(self):
307 self.assertEqual(lift_lambdas(3), ["labels", [], 3])
308
309 def test_bool(self):
310 self.assertEqual(lift_lambdas(True), ["labels", [], True])
311 self.assertEqual(lift_lambdas(False), ["labels", [], False])
312
313 def test_char(self):
314 self.assertEqual(lift_lambdas(Char("a")), ["labels", [], Char("a")])
315
316 def test_freevar(self):
317 self.assertEqual(lift_lambdas("x"), ["labels", [], "x"])
318
319 def test_call_plus(self):
320 self.assertEqual(lift_lambdas(["+", 3, 4]), ["labels", [], ["+", 3, 4]])
321
322 def test_call(self):
323 self.assertEqual(lift_lambdas(["f", 3, 4]), ["labels", [], ["funcall", "f", 3, 4]])
324
325 def test_lambda_no_params_no_freevars(self):
326 self.assertEqual(lift_lambdas(["lambda", [], 3]),
327 ["labels", [
328 ["f0", ["code", [], [], 3]],
329 ], ["closure", "f0"]])
330
331 def test_lambda_no_params_with_freevars(self):
332 self.assertEqual(lift_lambdas(["lambda", [], "z"]),
333 ["labels", [
334 ["f0", ["code", [], ["z"], "z"]],
335 ], ["closure", "f0", "z"]])
336
337 def test_lambda_with_params_no_freevars(self):
338 self.assertEqual(lift_lambdas(["lambda", ["x"], "x"]),
339 ["labels", [
340 ["f0", ["code", ["x"], [], "x"]],
341 ], ["closure", "f0"]])
342
343 def test_lambda_with_params_and_freevars(self):
344 self.assertEqual(lift_lambdas(["lambda", ["x"], ["+", "x", "y"]]),
345 ["labels",
346 [["f0", ["code", ["x"], ["y"], ["+", "x", "y"]]]],
347 ["closure", "f0", "y"]])
348
349 def test_let(self):
350 self.assertEqual(lift_lambdas(["let", [["x", 5]], "x"]),
351 ["labels", [], ["let", [["x", 5]], "x"]])
352
353 def test_let_lambda(self):
354 self.assertEqual(lift_lambdas(["let", [["x", 5]],
355 ["lambda", ["y"],
356 ["+", "x", "y"]]]),
357 ["labels",
358 [["f0", ["code", ["y"], ["x"], ["+", "x", "y"]]]],
359 ["let", [["x", 5]], ["closure", "f0", "x"]]])
360
361 def test_nested_lambda(self):
362 self.assertEqual(lift_lambdas(["lambda", ["x"],
363 ["lambda", ["y"],
364 ["+", "x", "y"]]]),
365 ["labels",
366 [["f0", ["code", ["y"], ["x"], ["+", "x", "y"]]],
367 ["f1", ["code", ["x"], [], ["closure", "f0", "x"]]]],
368 ["closure", "f1"]])
369
370 def test_nested_let(self):
371 self.assertEqual(lift_lambdas(["let", [["x", 5]],
372 ["let", [["y", 6]],
373 ["lambda", [],
374 ["+", "x", "y"]]]]),
375 ["labels",
376 [["f0", ["code", [], ["x", "y"], ["+", "x", "y"]]]],
377 ["let", [["x", 5]],
378 ["let", [["y", 6]],
379 ["closure", "f0", "x", "y"]]]])
380
381 def test_let_inside_lambda(self):
382 self.assertEqual(lift_lambdas(["lambda", ["x"],
383 ["let", [["y", 6]],
384 ["+", "x", "y"]]]),
385 ["labels",
386 [["f0", ["code", ["x"], [],
387 ["let", [["y", 6]],
388 ["+", "x", "y"]]]]],
389 ["closure", "f0"]])
390
391 def test_paper_example(self):
392 self.assertEqual(lift_lambdas(["let", [["x", 5]],
393 ["lambda", ["y"],
394 ["lambda", [],
395 ["+", "x", "y"]]]]),
396 ["labels", [
397 ["f0", ["code", [], ["x", "y"], ["+", "x", "y"]]],
398 ["f1", ["code", ["y"], ["x"], ["closure", "f0", "x", "y"]]],
399 ],
400 ["let", [["x", 5]], ["closure", "f1", "x"]]])
401
402 def test_freevar_inside_let_binding(self):
403 self.assertEqual(lift_lambdas(["lambda", ["x"],
404 ["let", [["y", "x"]],
405 ["+", "x", "y"]]]),
406 ["labels",
407 [["f0", ["code", ["x"], [],
408 ["let", [["y", "x"]],
409 ["+", "x", "y"]]]]],
410 ["closure", "f0"]])
411
412 def test_if(self):
413 self.assertEqual(lift_lambdas(["if", 1, 2, 3]),
414 ["labels", [], ["if", 1, 2, 3]])
415 self.assertEqual(lift_lambdas(["lambda", [],
416 ["if", "x", "y", "z"]]),
417 ["labels",
418 [["f0", ["code", [], ["x", "y", "z"],
419 ["if", "x", "y", "z"]]]],
420 ["closure", "f0", "x", "y", "z"]])
421
422def link(program, outfile=None, verbose=True):
423 if not outfile:
424 outfile = "a.out"
425 with tempfile.NamedTemporaryFile(suffix=".s") as f:
426 with tempfile.NamedTemporaryFile(suffix=".o") as runtime_o:
427 f.write(program.encode("utf-8"))
428 f.flush()
429 run(["ccache", "clang", "-O0", "-ggdb", "-c", "runtime.c", "-o", runtime_o.name], verbose=verbose)
430 compiled_object = f"{f.name}.o"
431 run(["ccache", "clang", "-masm=intel", f.name, "-c", "-o", compiled_object], verbose=verbose)
432 run(["ccache", "clang", "-O0", "-no-pie", compiled_object, runtime_o.name, "-o", outfile], verbose=verbose)
433 return outfile
434
435class EndToEndTests(unittest.TestCase):
436 def _run(self, expr):
437 return self._run_program(["labels", [], expr])
438
439 def _run_program(self, program):
440 asm = compile_program(program)
441 with tempfile.NamedTemporaryFile(suffix=".out", delete_on_close=False) as f:
442 link(asm, f.name, verbose=False)
443 f.close()
444 result = run([f.name], verbose=False, capture_output=True)
445 self.assertIsNot(result.stdout, None)
446 return result.stdout.removesuffix("\n")
447
448 def test_int(self):
449 self.assertEqual(self._run(123), "123")
450
451 def test_char(self):
452 self.assertEqual(self._run(Char("a")), "'a'")
453
454 def test_bool(self):
455 self.assertEqual(self._run(True), "#t")
456 self.assertEqual(self._run(False), "#f")
457
458 def test_empty_list(self):
459 self.assertEqual(self._run([]), "()")
460
461 def test_add1(self):
462 self.assertEqual(self._run(["add1", 3]), "4")
463 self.assertEqual(self._run(["add1", ["add1", 3]]), "5")
464
465 def test_integer_to_char(self):
466 self.assertEqual(self._run(["integer->char", 97]), "'a'")
467
468 def test_char_to_integer(self):
469 self.assertEqual(self._run(["char->integer", Char("a")]), "97")
470
471 def test_nullp(self):
472 self.assertEqual(self._run(["null?", 123]), "#f")
473 self.assertEqual(self._run(["null?", []]), "#t")
474
475 def test_zerop(self):
476 self.assertEqual(self._run(["zero?", 123]), "#f")
477 self.assertEqual(self._run(["zero?", 0]), "#t")
478 self.assertEqual(self._run(["zero?", []]), "#f")
479
480 def test_not(self):
481 self.assertEqual(self._run(["not", True]), "#f")
482 self.assertEqual(self._run(["not", False]), "#t")
483
484 def test_integerp(self):
485 self.assertEqual(self._run(["integer?", 123]), "#t")
486 self.assertEqual(self._run(["integer?", 0]), "#t")
487 self.assertEqual(self._run(["integer?", []]), "#f")
488 self.assertEqual(self._run(["integer?", Char("a")]), "#f")
489 self.assertEqual(self._run(["integer?", True]), "#f")
490 self.assertEqual(self._run(["integer?", False]), "#f")
491
492 def test_booleanp(self):
493 self.assertEqual(self._run(["boolean?", 123]), "#f")
494 self.assertEqual(self._run(["boolean?", 0]), "#f")
495 self.assertEqual(self._run(["boolean?", []]), "#f")
496 self.assertEqual(self._run(["boolean?", Char("a")]), "#f")
497 self.assertEqual(self._run(["boolean?", True]), "#t")
498 self.assertEqual(self._run(["boolean?", False]), "#t")
499
500 def test_add(self):
501 self.assertEqual(self._run(["+", 3, 4]), "7")
502 self.assertEqual(self._run(["+", ["+", 1, 2], ["+", 3, 4]]), "10")
503
504 def test_let_no_bindings(self):
505 self.assertEqual(self._run(["let", [], 3]), "3")
506
507 def test_let_one_binding(self):
508 self.assertEqual(self._run(["let", [["a", 3]], "a"]), "3")
509
510 def test_let_multiple_bindings(self):
511 self.assertEqual(self._run(["let", [["a", 3], ["b", 4]], ["+", "a", "b"]]), "7")
512
513 def test_if(self):
514 self.assertEqual(self._run(["if", True, 3, 4]), "3")
515 self.assertEqual(self._run(["if", False, 3, 4]), "4")
516
517 def test_cons(self):
518 self.assertEqual(self._run(["cons", 3, 4]), "(3 . 4)")
519 self.assertEqual(self._run(["cons", ["cons", 1, 2], ["cons", 3, 4]]), "((1 . 2) . (3 . 4))")
520
521 def test_car(self):
522 self.assertEqual(self._run(["car", ["cons", 3, 4]]), "3")
523
524 def test_cdr(self):
525 self.assertEqual(self._run(["cdr", ["cons", 3, 4]]), "4")
526
527 def test_labelcall_no_args(self):
528 self.assertEqual(self._run_program(
529 ["labels",
530 [
531 ["const", ["code", [], [], 3]]
532 ],
533 ["labelcall", "const"],
534 ]), "3")
535
536 def test_labelcall_with_args(self):
537 self.assertEqual(self._run_program(
538 ["labels",
539 [
540 ["add", ["code", ["a", "b"], [], ["+", "a", "b"]]]
541 ],
542 ["labelcall", "add", 3, 4],
543 ]), "7")
544
545 def test_labelcall_with_tmp_on_stack(self):
546 self.assertEqual(self._run_program(
547 ["labels",
548 [
549 ["add", ["code", ["a", "b"], [], ["+", "a", "b"]]]
550 ],
551 ["+", 2, ["labelcall", "add", 3, 4]],
552 ]), "9")
553
554 def test_nested_labelcall_with_args(self):
555 self.assertEqual(self._run_program(
556 ["labels",
557 [
558 ["add", ["code", ["a", "b"], [], ["+", "a", "b"]]],
559 ["g", ["code", ["a", "b"], [],
560 ["+",
561 ["labelcall", "add", "a", "b"],
562 ["labelcall", "add", "a", "b"],
563 ],
564 ]],
565 ["f", ["code", ["a"], [], ["labelcall", "g", "a", 4]]],
566 ],
567 ["labelcall", "f", 3],
568 ]), "14")
569
570 def test_empty_closure(self):
571 self.assertEqual(self._run_program(
572 ["labels",
573 [
574 ["const", ["code", [], [], 3]],
575 ],
576 ["closure", "const"],
577 ]), "<closure>")
578
579 def test_closure_one_var(self):
580 self.assertEqual(self._run_program(
581 ["labels",
582 [
583 ["const", ["code", [], [], 3]],
584 ],
585 ["let", [["a", 1]],
586 ["closure", "const", "a"],
587 ]
588 ]), "<closure>")
589
590 def test_closure_multiple_vars(self):
591 self.assertEqual(self._run_program(
592 ["labels",
593 [
594 ["const", ["code", [], [], 3]],
595 ],
596 ["let", [["a", 1]],
597 ["closure", "const", "a", "a", "a"],
598 ]
599 ]), "<closure>")
600
601 def test_funcall_empty_closure(self):
602 self.assertEqual(self._run_program(
603 ["labels",
604 [
605 ["const", ["code", [], [], 3]],
606 ],
607 ["let", [["f", ["closure", "const"]]],
608 ["funcall", "f"]]
609 ]), "3")
610
611 def test_funcall_empty_closure_with_params(self):
612 self.assertEqual(self._run_program(
613 ["labels",
614 [
615 ["const", ["code", ["x", "y"], [], ["+", "x", "y"]]],
616 ],
617 ["let", [["f", ["closure", "const"]]],
618 ["funcall", "f", 3, 4]]
619 ]), "7")
620
621 def test_funcall_closure_with_freevar(self):
622 self.assertEqual(self._run_program(
623 ["labels",
624 [
625 ["const", ["code", [], ["z"], "z"]],
626 ],
627 ["let", [["v", 3]],
628 ["let", [["f", ["closure", "const", "v"]]],
629 ["funcall", "f"]]]
630 ]), "3")
631
632 def test_lambda(self):
633 self.assertEqual(self._run_program(["lambda", ["x"], "x"]), "<closure>")
634
635 def test_lambda_one_var(self):
636 self.assertEqual(self._run_program(
637 ["let", [["y", 5]], ["lambda", [], "y"]]),
638 "<closure>")
639
640 def test_call_lambda(self):
641 self.assertEqual(self._run_program([["lambda", ["x"], "x"], 3]), "3")
642
643 def test_lambda_lift_paper_example(self):
644 self.assertEqual(self._run_program(["let", [["x", 5]],
645 ["lambda", ["y"],
646 ["lambda", [],
647 ["+", "x", "y"]]]]),
648 "<closure>")
649 self.assertEqual(self._run_program([["let", [["x", 5]],
650 ["lambda", ["y"],
651 ["lambda", [],
652 ["+", "x", "y"]]]], 4]),
653 "<closure>")
654 self.assertEqual(self._run_program([[["let", [["x", 5]],
655 ["lambda", ["y"],
656 ["lambda", [],
657 ["+", "x", "y"]]]], 4]]),
658 "9")
659
660
661if __name__ == "__main__":
662 unittest.main()