Implement V5+ call variants and fix double-byte operand decoding
New opcodes: op_call_vn, op_call_vn2, op_call_vs2, op_catch, op_check_arg_count. All call variants delegate to existing _call(). ZRoutine now tracks arg_count for check_arg_count. Fixed zopdecoder double-byte operand parsing for call_vs2/call_vn2: the old code called _parse_operands_byte() twice, but this method reads both type byte AND operands together. The second call would read operand data as a type byte. Refactored into _read_type_byte() + _parse_operand_list() so both type bytes are read before any operand data. Also fixed the double-byte detection: was checking opcode[0:7] (7 bits = 0x7A for call_vn2) instead of opcode_num (5 bits = 0x1A). The check never matched, so double-byte opcodes were always mis-parsed.
This commit is contained in:
parent
38e60ae40c
commit
d71f221277
3 changed files with 39 additions and 28 deletions
|
|
@ -644,8 +644,9 @@ class ZCpu:
|
|||
self._stackmanager.pop_stack()
|
||||
|
||||
def op_catch(self, *args):
|
||||
"""TODO: Write docstring here."""
|
||||
raise ZCpuNotImplemented
|
||||
"""Store the current stack frame index (for throw opcode)."""
|
||||
frame_index = self._stackmanager.get_stack_frame_index()
|
||||
self._write_result(frame_index)
|
||||
|
||||
def op_quit(self, *args):
|
||||
"""Quit the game."""
|
||||
|
|
@ -793,9 +794,9 @@ class ZCpu:
|
|||
"""Set the given window as the active window."""
|
||||
self._ui.screen.select_window(window_num)
|
||||
|
||||
def op_call_vs2(self, *args):
|
||||
"""TODO: Write docstring here."""
|
||||
raise ZCpuNotImplemented
|
||||
def op_call_vs2(self, routine_addr, *args):
|
||||
"""Call routine with up to 7 arguments and store the result."""
|
||||
self._call(routine_addr, args, True)
|
||||
|
||||
def op_erase_window(self, window_number):
|
||||
"""Clear the window with the given number. If # is -1, unsplit
|
||||
|
|
@ -884,13 +885,13 @@ class ZCpu:
|
|||
"""TODO: Write docstring here."""
|
||||
raise ZCpuNotImplemented
|
||||
|
||||
def op_call_vn(self, *args):
|
||||
"""TODO: Write docstring here."""
|
||||
raise ZCpuNotImplemented
|
||||
def op_call_vn(self, routine_addr, *args):
|
||||
"""Call routine with up to 3 arguments and discard the result."""
|
||||
self._call(routine_addr, args, False)
|
||||
|
||||
def op_call_vn2(self, *args):
|
||||
"""TODO: Write docstring here."""
|
||||
raise ZCpuNotImplemented
|
||||
def op_call_vn2(self, routine_addr, *args):
|
||||
"""Call routine with up to 7 arguments and discard the result."""
|
||||
self._call(routine_addr, args, False)
|
||||
|
||||
def op_tokenize(self, *args):
|
||||
"""TODO: Write docstring here."""
|
||||
|
|
@ -908,9 +909,10 @@ class ZCpu:
|
|||
"""TODO: Write docstring here."""
|
||||
raise ZCpuNotImplemented
|
||||
|
||||
def op_check_arg_count(self, *args):
|
||||
"""TODO: Write docstring here."""
|
||||
raise ZCpuNotImplemented
|
||||
def op_check_arg_count(self, arg_number):
|
||||
"""Branch if the Nth argument was passed to the current routine."""
|
||||
current_frame = self._stackmanager._call_stack[-1]
|
||||
self._branch(arg_number <= current_frame.arg_count)
|
||||
|
||||
## EXT opcodes (opcodes 256-284)
|
||||
|
||||
|
|
|
|||
|
|
@ -122,14 +122,16 @@ class ZOpDecoder:
|
|||
|
||||
opcode_num = opcode[0:5]
|
||||
|
||||
# Parse the types byte to retrieve the operands.
|
||||
operands = self._parse_operands_byte()
|
||||
|
||||
# Special case: opcodes 12 and 26 have a second operands byte.
|
||||
if opcode[0:7] == 0xC or opcode[0:7] == 0x1A:
|
||||
# Read all type bytes FIRST, before parsing any operands.
|
||||
# call_vs2 (VAR:12) and call_vn2 (VAR:26) have two type bytes;
|
||||
# all others have one. Both type bytes must be read before
|
||||
# operand data starts in the stream.
|
||||
operand_types = self._read_type_byte()
|
||||
if opcode_type == OPCODE_VAR and opcode_num in (0xC, 0x1A):
|
||||
log("Opcode has second operand byte")
|
||||
operands += self._parse_operands_byte()
|
||||
operand_types += self._read_type_byte()
|
||||
|
||||
operands = self._parse_operand_list(operand_types)
|
||||
return (opcode_type, opcode_num, operands)
|
||||
|
||||
def _parse_opcode_extended(self):
|
||||
|
|
@ -179,25 +181,31 @@ class ZOpDecoder:
|
|||
|
||||
return operand
|
||||
|
||||
def _parse_operands_byte(self):
|
||||
"""Parse operands given by the operand byte and return a list of
|
||||
values.
|
||||
"""
|
||||
def _read_type_byte(self):
|
||||
"""Read one operand type byte and return a list of type codes."""
|
||||
operand_byte = BitField(self._get_pc())
|
||||
operands = []
|
||||
for operand_type in [
|
||||
return [
|
||||
operand_byte[6:8],
|
||||
operand_byte[4:6],
|
||||
operand_byte[2:4],
|
||||
operand_byte[0:2],
|
||||
]:
|
||||
]
|
||||
|
||||
def _parse_operand_list(self, operand_types):
|
||||
"""Parse operands from a list of type codes, stopping at ABSENT."""
|
||||
operands = []
|
||||
for operand_type in operand_types:
|
||||
operand = self._parse_operand(operand_type)
|
||||
if operand is None:
|
||||
break
|
||||
operands.append(operand)
|
||||
|
||||
return operands
|
||||
|
||||
def _parse_operands_byte(self):
|
||||
"""Read one type byte and parse its operands. Used by extended
|
||||
opcodes and other callers that always have a single type byte."""
|
||||
return self._parse_operand_list(self._read_type_byte())
|
||||
|
||||
# Public funcs that the ZPU may also need to call, depending on the
|
||||
# opcode being executed:
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class ZRoutine:
|
|||
self.start_addr = start_addr
|
||||
self.return_addr = return_addr
|
||||
self.program_counter = 0 # used when execution interrupted
|
||||
self.arg_count = len(args) # track number of args passed
|
||||
|
||||
if stack is None:
|
||||
self.stack = []
|
||||
|
|
|
|||
Loading…
Reference in a new issue