File size: 3,643 Bytes
fc3e70a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | # This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
"""Tests: Phases 12-13 — SUBLEQ IR and VM."""
import pytest
from semantic_engine.subleq import (
SubleqInstruction, SubleqProgram, SubleqMemory, SubleqVM,
HALT_ADDRESS, MAX_MEMORY,
)
from semantic_engine.types import PipelineError
def halt_prog() -> SubleqProgram:
prog = SubleqProgram()
prog.data[0] = 0 # zero cell
prog.add(SubleqInstruction(A=0, B=0, C=HALT_ADDRESS, comment="HALT"))
return prog
def test_instruction_repr():
instr = SubleqInstruction(A=1, B=2, C=3)
r = repr(instr)
assert "SUBLEQ" in r
def test_instruction_halt_repr():
instr = SubleqInstruction(A=0, B=0, C=HALT_ADDRESS)
assert "HALT" in repr(instr)
def test_program_add_and_label():
prog = SubleqProgram()
prog.add(SubleqInstruction(A=0, B=0, C=HALT_ADDRESS, label="start"))
assert "start" in prog.labels
assert prog.labels["start"] == 0
def test_program_resolve_label():
prog = SubleqProgram()
prog.add(SubleqInstruction(A=0, B=0, C=HALT_ADDRESS, label="end"))
assert prog.resolve_label("end") == 0
def test_program_resolve_missing_label():
prog = SubleqProgram()
with pytest.raises(PipelineError):
prog.resolve_label("missing")
def test_assemble_disassemble_roundtrip():
prog = SubleqProgram()
prog.add(SubleqInstruction(A=1, B=2, C=3))
prog.add(SubleqInstruction(A=4, B=5, C=HALT_ADDRESS))
binary = prog.assemble()
recovered = SubleqProgram.disassemble(binary)
assert len(recovered.instructions) == 2
assert recovered.instructions[0].A == 1
assert recovered.instructions[0].B == 2
assert recovered.instructions[1].C == HALT_ADDRESS
def test_assemble_bad_alignment():
with pytest.raises(PipelineError):
SubleqProgram.disassemble(b"\x00\x01\x02\x03")
def test_memory_read_write():
mem = SubleqMemory()
mem.write(100, 42)
assert mem.read(100) == 42
def test_memory_out_of_bounds():
mem = SubleqMemory()
with pytest.raises(PipelineError):
mem.read(MAX_MEMORY + 1)
with pytest.raises(PipelineError):
mem.write(-1, 0)
def test_vm_halts():
prog = halt_prog()
vm = SubleqVM()
vm.load(prog)
vm.execute()
assert vm.halted
def test_vm_step_returns_false_on_halt():
prog = halt_prog()
vm = SubleqVM()
vm.load(prog)
while not vm.halted:
result = vm.step()
assert not result
def test_vm_trace_recorded():
prog = halt_prog()
vm = SubleqVM()
vm.load(prog)
trace = vm.execute()
assert len(trace) >= 1
assert trace[0].pc >= 0 # PC starts at base offset
def test_vm_max_steps_exceeded():
prog = SubleqProgram()
# Infinite loop: SUBLEQ 0 0 0
prog.data[0] = 0
prog.add(SubleqInstruction(A=0, B=0, C=0))
vm = SubleqVM(max_steps=10)
vm.load(prog)
with pytest.raises(PipelineError) as exc:
vm.execute()
assert "max" in str(exc.value).lower() or "steps" in str(exc.value).lower()
def test_verify_halts():
prog = halt_prog()
vm = SubleqVM()
assert vm.verify_halts(prog)
def test_subleq_arithmetic():
"""SUBLEQ(A, B, C): mem[B] -= mem[A]; branch if <=0"""
prog = SubleqProgram()
# Use addresses 10, 11 for data (well below base=100 for instructions)
prog.data[10] = 5
prog.data[11] = 3
prog.add(SubleqInstruction(A=10, B=11, C=HALT_ADDRESS))
vm = SubleqVM()
vm.load(prog)
vm.execute()
assert vm.memory.read(11) == -2
|