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()
|
self._stackmanager.pop_stack()
|
||||||
|
|
||||||
def op_catch(self, *args):
|
def op_catch(self, *args):
|
||||||
"""TODO: Write docstring here."""
|
"""Store the current stack frame index (for throw opcode)."""
|
||||||
raise ZCpuNotImplemented
|
frame_index = self._stackmanager.get_stack_frame_index()
|
||||||
|
self._write_result(frame_index)
|
||||||
|
|
||||||
def op_quit(self, *args):
|
def op_quit(self, *args):
|
||||||
"""Quit the game."""
|
"""Quit the game."""
|
||||||
|
|
@ -793,9 +794,9 @@ class ZCpu:
|
||||||
"""Set the given window as the active window."""
|
"""Set the given window as the active window."""
|
||||||
self._ui.screen.select_window(window_num)
|
self._ui.screen.select_window(window_num)
|
||||||
|
|
||||||
def op_call_vs2(self, *args):
|
def op_call_vs2(self, routine_addr, *args):
|
||||||
"""TODO: Write docstring here."""
|
"""Call routine with up to 7 arguments and store the result."""
|
||||||
raise ZCpuNotImplemented
|
self._call(routine_addr, args, True)
|
||||||
|
|
||||||
def op_erase_window(self, window_number):
|
def op_erase_window(self, window_number):
|
||||||
"""Clear the window with the given number. If # is -1, unsplit
|
"""Clear the window with the given number. If # is -1, unsplit
|
||||||
|
|
@ -884,13 +885,13 @@ class ZCpu:
|
||||||
"""TODO: Write docstring here."""
|
"""TODO: Write docstring here."""
|
||||||
raise ZCpuNotImplemented
|
raise ZCpuNotImplemented
|
||||||
|
|
||||||
def op_call_vn(self, *args):
|
def op_call_vn(self, routine_addr, *args):
|
||||||
"""TODO: Write docstring here."""
|
"""Call routine with up to 3 arguments and discard the result."""
|
||||||
raise ZCpuNotImplemented
|
self._call(routine_addr, args, False)
|
||||||
|
|
||||||
def op_call_vn2(self, *args):
|
def op_call_vn2(self, routine_addr, *args):
|
||||||
"""TODO: Write docstring here."""
|
"""Call routine with up to 7 arguments and discard the result."""
|
||||||
raise ZCpuNotImplemented
|
self._call(routine_addr, args, False)
|
||||||
|
|
||||||
def op_tokenize(self, *args):
|
def op_tokenize(self, *args):
|
||||||
"""TODO: Write docstring here."""
|
"""TODO: Write docstring here."""
|
||||||
|
|
@ -908,9 +909,10 @@ class ZCpu:
|
||||||
"""TODO: Write docstring here."""
|
"""TODO: Write docstring here."""
|
||||||
raise ZCpuNotImplemented
|
raise ZCpuNotImplemented
|
||||||
|
|
||||||
def op_check_arg_count(self, *args):
|
def op_check_arg_count(self, arg_number):
|
||||||
"""TODO: Write docstring here."""
|
"""Branch if the Nth argument was passed to the current routine."""
|
||||||
raise ZCpuNotImplemented
|
current_frame = self._stackmanager._call_stack[-1]
|
||||||
|
self._branch(arg_number <= current_frame.arg_count)
|
||||||
|
|
||||||
## EXT opcodes (opcodes 256-284)
|
## EXT opcodes (opcodes 256-284)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,14 +122,16 @@ class ZOpDecoder:
|
||||||
|
|
||||||
opcode_num = opcode[0:5]
|
opcode_num = opcode[0:5]
|
||||||
|
|
||||||
# Parse the types byte to retrieve the operands.
|
# Read all type bytes FIRST, before parsing any operands.
|
||||||
operands = self._parse_operands_byte()
|
# call_vs2 (VAR:12) and call_vn2 (VAR:26) have two type bytes;
|
||||||
|
# all others have one. Both type bytes must be read before
|
||||||
# Special case: opcodes 12 and 26 have a second operands byte.
|
# operand data starts in the stream.
|
||||||
if opcode[0:7] == 0xC or opcode[0:7] == 0x1A:
|
operand_types = self._read_type_byte()
|
||||||
|
if opcode_type == OPCODE_VAR and opcode_num in (0xC, 0x1A):
|
||||||
log("Opcode has second operand byte")
|
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)
|
return (opcode_type, opcode_num, operands)
|
||||||
|
|
||||||
def _parse_opcode_extended(self):
|
def _parse_opcode_extended(self):
|
||||||
|
|
@ -179,25 +181,31 @@ class ZOpDecoder:
|
||||||
|
|
||||||
return operand
|
return operand
|
||||||
|
|
||||||
def _parse_operands_byte(self):
|
def _read_type_byte(self):
|
||||||
"""Parse operands given by the operand byte and return a list of
|
"""Read one operand type byte and return a list of type codes."""
|
||||||
values.
|
|
||||||
"""
|
|
||||||
operand_byte = BitField(self._get_pc())
|
operand_byte = BitField(self._get_pc())
|
||||||
operands = []
|
return [
|
||||||
for operand_type in [
|
|
||||||
operand_byte[6:8],
|
operand_byte[6:8],
|
||||||
operand_byte[4:6],
|
operand_byte[4:6],
|
||||||
operand_byte[2:4],
|
operand_byte[2:4],
|
||||||
operand_byte[0:2],
|
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)
|
operand = self._parse_operand(operand_type)
|
||||||
if operand is None:
|
if operand is None:
|
||||||
break
|
break
|
||||||
operands.append(operand)
|
operands.append(operand)
|
||||||
|
|
||||||
return operands
|
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
|
# Public funcs that the ZPU may also need to call, depending on the
|
||||||
# opcode being executed:
|
# opcode being executed:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ class ZRoutine:
|
||||||
self.start_addr = start_addr
|
self.start_addr = start_addr
|
||||||
self.return_addr = return_addr
|
self.return_addr = return_addr
|
||||||
self.program_counter = 0 # used when execution interrupted
|
self.program_counter = 0 # used when execution interrupted
|
||||||
|
self.arg_count = len(args) # track number of args passed
|
||||||
|
|
||||||
if stack is None:
|
if stack is None:
|
||||||
self.stack = []
|
self.stack = []
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue