From c76ee337d35d63a2f04116a5eadbd462ae473a80 Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Mon, 9 Feb 2026 20:13:20 -0500 Subject: [PATCH] Port 4 medium opcodes to hybrid z-machine interpreter Implements op_print_addr, op_print_num, op_ret, and op_show_status following TDD approach with tests first. Each opcode now properly decodes/prints text, handles signed numbers, returns from routines, or acts as a no-op as appropriate. --- src/mudlib/zmachine/zcpu.py | 23 +++++++++++--------- tests/test_zmachine_opcodes.py | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/mudlib/zmachine/zcpu.py b/src/mudlib/zmachine/zcpu.py index 06c8411..7f277d5 100644 --- a/src/mudlib/zmachine/zcpu.py +++ b/src/mudlib/zmachine/zcpu.py @@ -405,8 +405,9 @@ class ZCpu: self._write_result(val, store_addr=variable) def op_print_addr(self, string_byte_address): - """TODO: Write docstring here.""" - raise ZCpuNotImplemented + """Print the z-encoded string at the given byte address.""" + text = self._string.get(string_byte_address) + self._ui.screen.write(text) def op_call_1s(self, routine_address): """Call the given routine and store the return value.""" @@ -421,9 +422,10 @@ class ZCpu: shortname = self._objects.get_shortname(obj) self._ui.screen.write(shortname) - def op_ret(self, *args): - """TODO: Write docstring here.""" - raise ZCpuNotImplemented + def op_ret(self, value): + """Return from the current routine with the given value.""" + pc = self._stackmanager.finish_routine(value) + self._opdecoder.program_counter = pc def op_jump(self, offset): """Jump unconditionally to the given branch offset. This @@ -535,8 +537,8 @@ class ZCpu: self._ui.screen.write("\n") def op_show_status(self, *args): - """TODO: Write docstring here.""" - raise ZCpuNotImplemented + """Update status line (V3 only). No-op in this implementation.""" + pass def op_verify(self, *args): """TODO: Write docstring here.""" @@ -592,9 +594,10 @@ class ZCpu: """Output the given ZSCII character.""" self._ui.screen.write(self._string.zscii.get([char])) - def op_print_num(self, *args): - """TODO: Write docstring here.""" - raise ZCpuNotImplemented + def op_print_num(self, value): + """Print a signed 16-bit number as text.""" + signed_value = self._make_signed(value) + self._ui.screen.write(str(signed_value)) def op_random(self, n): """Generate a random number, or seed the PRNG. diff --git a/tests/test_zmachine_opcodes.py b/tests/test_zmachine_opcodes.py index 1a3e6c5..bf08d3c 100644 --- a/tests/test_zmachine_opcodes.py +++ b/tests/test_zmachine_opcodes.py @@ -250,6 +250,44 @@ class ZMachineOpcodeTests(TestCase): # Stack should be empty self.assertEqual(len(self.stack.stack), 0) + def test_op_print_addr(self): + """Test print_addr decodes and prints text at byte address.""" + # Configure mock string decoder to return a known string + self.cpu._string.get = Mock(return_value="Hello, world!") + # Print text at address 0x5000 + self.cpu.op_print_addr(0x5000) + # Should have called string decoder with the address + self.cpu._string.get.assert_called_once_with(0x5000) + # Should have written the decoded text + self.ui.screen.write.assert_called_once_with("Hello, world!") + + def test_op_print_num_positive(self): + """Test print_num prints positive number.""" + self.cpu.op_print_num(42) + self.ui.screen.write.assert_called_once_with("42") + + def test_op_print_num_negative(self): + """Test print_num prints negative number.""" + # -1 as unsigned 16-bit is 65535 + self.cpu.op_print_num(65535) + self.ui.screen.write.assert_called_once_with("-1") + + def test_op_print_num_zero(self): + """Test print_num prints zero.""" + self.cpu.op_print_num(0) + self.ui.screen.write.assert_called_once_with("0") + + def test_op_ret(self): + """Test ret returns from routine with value.""" + self.cpu.op_ret(42) + # Should have set PC to caller's address (0x1000 from mock) + self.assertEqual(self.cpu._opdecoder.program_counter, 0x1000) + + def test_op_show_status(self): + """Test show_status is a no-op (V3 only, not needed in MUD).""" + # Should just not raise an exception + self.cpu.op_show_status() + class MockObjectParser: """Mock object parser for testing CPU opcodes."""