From 05eacd2ae3eda65838e5eac0af9d51cf82b24910 Mon Sep 17 00:00:00 2001 From: Nick Downing Date: Tue, 9 Jan 2024 04:09:07 +1100 Subject: [PATCH] Add /ocode_vm.py, can execute test.bcpl, run ./ocode_vm.py --list --trace OCODE --- ocode_vm.py | 769 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 769 insertions(+) create mode 100755 ocode_vm.py diff --git a/ocode_vm.py b/ocode_vm.py new file mode 100755 index 0000000..d981f38 --- /dev/null +++ b/ocode_vm.py @@ -0,0 +1,769 @@ +#!/usr/bin/env python3 + +import sys + +EXIT_SUCCESS = 0 +EXIT_FAILURE = 1 + +GLOBAL_MASK = 0o777 +GLOBAL_SIZE = 0o1000 +MEM_MASK = 0o777777 +MEM_SIZE = 0o1000000 +BYTE_MASK = 0o777 +WORD_MASK = 0o777777777777 + +OP_DATALAB = 0 +OP_DIV = 1 +OP_END = 2 +OP_EQ = 3 +OP_EQV = 4 +OP_FALSE = 5 +OP_FINISH = 6 +OP_FNAP = 7 +OP_FNRN = 8 +OP_GE = 9 +OP_GLOBAL = 10 +OP_GOTO = 11 +OP_GR = 12 +OP_ITEML = 13 +OP_ITEMN = 14 +OP_JF = 15 +OP_JT = 16 +OP_JUMP = 17 +OP_LAB = 18 +OP_LE = 19 +OP_LG = 20 +OP_LL = 21 +OP_LLG = 22 +OP_LLL = 23 +OP_LLP = 24 +OP_LN = 25 +OP_LOGAND = 26 +OP_LOGOR = 27 +OP_LP = 28 +OP_LS = 29 +OP_LSHIFT = 30 +OP_LSTR = 31 +OP_MINUS = 32 +OP_MULT = 33 +OP_NE = 34 +OP_NEG = 35 +OP_NEQV = 36 +OP_NOT = 37 +OP_PLUS = 38 +OP_REM = 39 +OP_RES = 40 +OP_RSHIFT = 41 +OP_RSTACK = 42 +OP_RTAP = 43 +OP_RTRN = 44 +OP_RV = 45 +OP_SAVE = 46 +OP_SG = 47 +OP_SL = 48 +OP_SP = 49 +OP_STACK = 50 +OP_STIND = 51 +OP_STORE = 52 +OP_SWITCHON = 53 +OP_TRUE = 54 +N_OPS = 55 + +op_to_str_n_operands = [ + ('DATALAB', 1), + ('DIV', 0), + ('END', 0), + ('EQ', 0), + ('EQV', 0), + ('FALSE', 0), + ('FINISH', 0), + ('FNAP', 1), + ('FNRN', 0), + ('GE', 0), + ('GLOBAL', 1), + ('GOTO', 0), + ('GR', 0), + ('ITEML', 1), + ('ITEMN', 1), + ('JF', 1), + ('JT', 1), + ('JUMP', 1), + ('LAB', 1), + ('LE', 0), + ('LG', 1), + ('LL', 1), + ('LLG', 1), + ('LLL', 1), + ('LLP', 1), + ('LN', 1), + ('LOGAND', 0), + ('LOGOR', 0), + ('LP', 1), + ('LS', 0), + ('LSHIFT', 0), + ('LSTR', 1), + ('MINUS', 0), + ('MULT', 0), + ('NE', 0), + ('NEG', 0), + ('NEQV', 0), + ('NOT', 0), + ('PLUS', 0), + ('REM', 0), + ('RES', 1), + ('RSHIFT', 0), + ('RSTACK', 1), + ('RTAP', 1), + ('RTRN', 0), + ('RV', 0), + ('SAVE', 1), + ('SG', 1), + ('SL', 1), + ('SP', 1), + ('STACK', 1), + ('STIND', 0), + ('STORE', 0), + ('SWITCHON', 2), + ('TRUE', 0), +] +str_to_op = { + 'DATALAB': OP_DATALAB, + 'DIV': OP_DIV, + 'END': OP_END, + 'EQ': OP_EQ, + 'EQV': OP_EQV, + 'FALSE': OP_FALSE, + 'FINISH': OP_FINISH, + 'FNAP': OP_FNAP, + 'FNRN': OP_FNRN, + 'GE': OP_GE, + 'GLOBAL': OP_GLOBAL, + 'GOTO': OP_GOTO, + 'GR': OP_GR, + 'ITEML': OP_ITEML, + 'ITEMN': OP_ITEMN, + 'JF': OP_JF, + 'JT': OP_JT, + 'JUMP': OP_JUMP, + 'LAB': OP_LAB, + 'LE': OP_LE, + 'LG': OP_LG, + 'LL': OP_LL, + 'LLG': OP_LLG, + 'LLL': OP_LLL, + 'LLP': OP_LLP, + 'LN': OP_LN, + 'LOGAND': OP_LOGAND, + 'LOGOR': OP_LOGOR, + 'LP': OP_LP, + 'LS': OP_LS, + 'LSHIFT': OP_LSHIFT, + 'LSTR': OP_LSTR, + 'MINUS': OP_MINUS, + 'MULT': OP_MULT, + 'NE': OP_NE, + 'NEG': OP_NEG, + 'NEQV': OP_NEQV, + 'NOT': OP_NOT, + 'PLUS': OP_PLUS, + 'REM': OP_REM, + 'RES': OP_RES, + 'RSHIFT': OP_RSHIFT, + 'RSTACK': OP_RSTACK, + 'RTAP': OP_RTAP, + 'RTRN': OP_RTRN, + 'RV': OP_RV, + 'SAVE': OP_SAVE, + 'SG': OP_SG, + 'SL': OP_SL, + 'SP': OP_SP, + 'STACK': OP_STACK, + 'STIND': OP_STIND, + 'STORE': OP_STORE, + 'SWITCHON': OP_SWITCHON, + 'TRUE': OP_TRUE, +} + +GLOBAL_Createoutput = 11 +SYS_Createoutput = MEM_MASK + +GLOBAL_InitializeIO = 21 +SYS_InitializeIO = MEM_MASK ^ 1 + +list = False +trace = False +while True: + if len(sys.argv) >= 2 and sys.argv[1] == '--list': + list = True + del sys.argv[1] + elif len(sys.argv) >= 2 and sys.argv[1] == '--trace': + trace = True + del sys.argv[1] + else: + break +if len(sys.argv) < 2: + print(f'usage: {sys.argv[0]:s} [--list] [--trace] file.ocode ...') + sys.exit(EXIT_FAILURE) +files_ocode = sys.argv[1:] + +# assemble +_pass = -1 +mem = [0] * MEM_SIZE +dot = GLOBAL_SIZE +def assemble(values, fields): + global dot + if _pass == 1: + for i in range(len(values)): + mem[(dot + i) & MEM_MASK] = values[i] + if list: + print( + f'{dot:012o}: ' + + ' '.join([f'{value:012o}' for value in values]) + + ' ; ' + + ' '.join(fields) + ) + dot = (dot + len(values)) & WORD_MASK +for file_ocode in files_ocode: + if list: + print(f'; {file_ocode:s}') + labels = [] + start = dot + for _pass in range(2): + dot = start + with open(file_ocode) as fin: + for line in fin: + fields = line.split() + assert len(fields) >= 1 + op = str_to_op[fields[0]] + #op_to_str_n_operands[op] = (fields[0], len(fields) - 1) + assert len(fields) - 1 == op_to_str_n_operands[op][1] + + if op == OP_LAB or op == OP_DATALAB: + assert len(fields) == 2 and fields[1][:1] == 'L' + label = int(fields[1][1:]) + if _pass == 0: + if len(labels) <= label: + labels.extend([-1] * (label + 1 - len(labels))) + assert labels[label] == -1 + labels[label] = dot + elif _pass == 1: + assert label < len(labels) and labels[label] == dot + if list: + print(f'{dot:012o}: ; ' + ' '.join(fields)) + else: + assert False + elif op == OP_END: + break + else: + # ITEMx does not store the opcode + values = [] if op == OP_ITEML or op == OP_ITEMN else [op] + for operand in fields[1:]: + if operand[:1] == 'L': + label = int(operand[1:]) + if len(labels) <= label: + labels.extend([-1] * (label + 1 - len(labels))) + value = labels[label] + else: + value = int(operand) + assert (value & ~WORD_MASK) == 0 + values.append(value) + assemble(values, fields) + + # certain instructions are followed by data + if op == OP_GLOBAL or op == OP_SWITCHON: + assert len(fields) == 2 if op == OP_GLOBAL else 3 + n = int(fields[1]) + + for i in range(n): + line = fin.readline() + fields = line.split() + assert len(fields) == 2 and fields[1][:1] == 'L' + + value = int(fields[0]) + label = int(fields[1][1:]) + + # GLOBAL, SWITCHON do not use forward references, hence this + # test is not conditioned by _pass == 1, but could be if needed + assert label < len(labels) and labels[label] != -1 + assemble([value, labels[label]], fields) + elif op == OP_LSTR: + assert len(fields) == 2 + n = int(fields[1]) + + line = fin.readline() + fields = line.split() + assert len(fields) == n + + i = 0 + values = [] # 36-bit words containing 4 9-bit bytes, big endian + value = 0 # word being constructed + while i < n: + byte_value = int(fields[i]) + assert (byte_value & ~BYTE_MASK) == 0 + value |= byte_value << (((i & 3) ^ 3) * 9) + i += 1 + if (i & 3) == 0: + values.append(value) + value = 0 + + # append the partial word if it has an unused (zeroed) byte, + # or a new zeroed word if the string was a multiple of 4 bytes + assemble(values + [value], fields) + +# assemble startup code +mem[dot & MEM_MASK] = OP_LG +mem[(dot + 1) & MEM_MASK] = 1 # Start +mem[(dot + 2) & MEM_MASK] = OP_GOTO +dot = (dot + 3) & WORD_MASK + +# assemble system calls +mem[GLOBAL_Createoutput] = SYS_Createoutput +mem[SYS_Createoutput] = OP_FNRN + +mem[GLOBAL_InitializeIO] = SYS_InitializeIO +mem[SYS_InitializeIO] = OP_RTRN + +# set up interpreter +ap = dot +sp = dot +pc = GLOBAL_SIZE + +def op_datalab(): + assert False +def op_div(): + # todo: fix negatives + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = ( + ((mem[sp & MEM_MASK] ^ 0o400000000000) - 0o400000000000) / + ((mem[(sp + 1) & MEM_MASK] ^ 0o400000000000) - 0o400000000000) + ) & WORD_MASK + sp = (sp + 1) & WORD_MASK +def op_end(): + assert False +def op_eq(): + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = int( + mem[sp & MEM_MASK] == mem[(sp + 1) & MEM_MASK] + ) + sp = (sp + 1) & WORD_MASK +def op_eqv(): + # xnor + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = ( + mem[sp & MEM_MASK] ^ mem[(sp + 1) & MEM_MASK] ^ WORD_MASK + ) + sp = (sp + 1) & WORD_MASK +def op_false(): + mem[sp & MEM_MASK] = 0 + sp = (sp + 1) & WORD_MASK +def op_finish(): + # seems to be like a system call + sys.exit(0) +def op_fnap(): + # the same as rtap + global pc, sp, ap + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + mem[(ap + n) & MEM_MASK] = ap + mem[(ap + n + 1) & MEM_MASK] = pc + ap = (ap + n) & WORD_MASK + sp = (sp - 1) & WORD_MASK + pc = mem[sp & MEM_MASK] +def op_fnrn(): + global pc, sp, ap + sp = (sp - 1) & WORD_MASK + value = mem[sp & MEM_MASK] + pc = mem[(ap + 1) & MEM_MASK] + ap = mem[ap & MEM_MASK] + sp = ap + mem[sp & MEM_MASK] = value + sp = (sp + 1) & WORD_MASK +def op_ge(): + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = int( + (mem[sp & MEM_MASK] ^ 0x400000000000) >= + (mem[(sp + 1) & MEM_MASK] ^ 0x400000000000) + ) + sp = (sp + 1) & WORD_MASK +def op_global(): + global pc + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + for i in range(n): + n = mem[pc & MEM_MASK] + addr = mem[(pc + 1) & MEM_MASK] + pc = (pc + 2) & WORD_MASK + mem[n & GLOBAL_MASK] = addr +def op_goto(): + global pc, sp + sp = (sp - 1) & WORD_MASK + pc = mem[sp & MEM_MASK] +def op_gr(): + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = int( + (mem[sp & MEM_MASK] ^ 0x400000000000) > + (mem[(sp + 1) & MEM_MASK] ^ 0x400000000000) + ) + sp = (sp + 1) & WORD_MASK +def op_iteml(): + assert False +def op_itemn(): + assert False +def op_jf(): + # seems to be non-destructive? + global pc + if mem[(sp - 1) & MEM_MASK]: + pc = (pc + 1) & WORD_MASK + else: + pc = mem[pc & MEM_MASK] +def op_jt(): + # seems to be non-destructive? + global pc + if mem[(sp - 1) & MEM_MASK]: + pc = mem[pc & MEM_MASK] + else: + pc = (pc + 1) & WORD_MASK +def op_jump(): + # same as res + global pc + pc = mem[pc & MEM_MASK] +def op_lab(): + assert False +def op_le(): + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = int( + (mem[sp & MEM_MASK] ^ 0x400000000000) <= + (mem[(sp + 1) & MEM_MASK] ^ 0x400000000000) + ) + sp = (sp + 1) & WORD_MASK +def op_lg(): + global pc, sp + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + mem[sp & MEM_MASK] = mem[n & GLOBAL_MASK] + sp = (sp + 1) & WORD_MASK +def op_ll(): + global pc, sp + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + mem[sp & MEM_MASK] = mem[n & MEM_MASK] + sp = (sp + 1) & WORD_MASK +def op_llg(): + global pc, sp + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + mem[sp & MEM_MASK] = n & GLOBAL_MASK + sp = (sp + 1) & WORD_MASK +def op_lll(): + global pc, sp + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + mem[sp & MEM_MASK] = n + sp = (sp + 1) & WORD_MASK +def op_llp(): + global pc, sp + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + mem[sp & MEM_MASK] = (ap + n) & WORD_MASK + sp = (sp + 1) & WORD_MASK +def op_ln(): + global pc, sp + mem[sp & MEM_MASK] = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + sp = (sp + 1) & WORD_MASK +def op_logand(): + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = ( + mem[sp & MEM_MASK] & mem[(sp + 1) & MEM_MASK] + ) + sp = (sp + 1) & WORD_MASK +def op_logor(): + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = ( + mem[sp & MEM_MASK] | mem[(sp + 1) & MEM_MASK] + ) + sp = (sp + 1) & WORD_MASK +def op_lp(): + global pc, sp + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + mem[sp & MEM_MASK] = mem[(ap + n) & MEM_MASK] + sp = (sp + 1) & WORD_MASK +def op_ls(): + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = int( + (mem[sp & MEM_MASK] ^ 0x400000000000) < + (mem[(sp + 1) & MEM_MASK] ^ 0x400000000000) + ) + sp = (sp + 1) & WORD_MASK +def op_lshift(): + # what about negative shifts? + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = ( + mem[sp & MEM_MASK] << mem[(sp + 1) & MEM_MASK] + ) & WORD_MASK + sp = (sp + 1) & WORD_MASK +def op_lstr(): + global pc, sp + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + mem[sp & MEM_MASK] = pc + sp = (sp + 1) & WORD_MASK + pc = (pc + ((n + 4) >> 2)) & WORD_MASK +def op_minus(): + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = ( + mem[sp & MEM_MASK] - mem[(sp + 1) & MEM_MASK] + ) & WORD_MASK + sp = (sp + 1) & WORD_MASK +def op_mult(): + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = ( + mem[sp & MEM_MASK] * mem[(sp + 1) & MEM_MASK] + ) & WORD_MASK + sp = (sp + 1) & WORD_MASK +def op_ne(): + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = int( + mem[sp & MEM_MASK] != mem[(sp + 1) & MEM_MASK] + ) + sp = (sp + 1) & WORD_MASK +def op_neg(): + sp = (sp - 1) & WORD_MASK + mem[sp & MEM_MASK] = -mem[sp & MEM_MASK] & WORD_MASK + sp = (sp + 1) & WORD_MASK +def op_neqv(): + # xor + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = ( + mem[sp & MEM_MASK] ^ mem[(sp + 1) & MEM_MASK] + ) + sp = (sp + 1) & WORD_MASK +def op_not(): + sp = (sp - 1) & WORD_MASK + mem[sp & MEM_MASK] = mem[sp & MEM_MASK] ^ WORD_MASK + sp = (sp + 1) & WORD_MASK +def op_plus(): + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = ( + mem[sp & MEM_MASK] + mem[(sp + 1) & MEM_MASK] + ) & WORD_MASK + sp = (sp + 1) & WORD_MASK +def op_rem(): + # todo: fix negatives + mem[sp & MEM_MASK] = ( + ((mem[sp & MEM_MASK] ^ 0o400000000000) - 0o400000000000) % + ((mem[(sp + 1) & MEM_MASK] ^ 0o400000000000) - 0o400000000000) + ) & WORD_MASK +def op_res(): + # same as jump + global pc + pc = mem[pc & MEM_MASK] +def op_rshift(): + # what about negative shifts? + global sp + sp = (sp - 2) & WORD_MASK + mem[sp & MEM_MASK] = ( + ((mem[sp & MEM_MASK] ^ 0x400000000000) - 0x400000000000) >> + mem[(sp + 1) & MEM_MASK] + ) & WORD_MASK + sp = (sp + 1) & WORD_MASK +def op_rstack(): + # ignore, occurs at end of function and seems to be counterpart to save + # (but don't treat as stack/save in case return value is already on stack?) + pass +def op_rtap(): + # the same as fnap + global pc, sp, ap + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + mem[(ap + n) & MEM_MASK] = ap + mem[(ap + n + 1) & MEM_MASK] = pc + ap = (ap + n) & WORD_MASK + sp = (sp - 1) & WORD_MASK + pc = mem[sp & MEM_MASK] +def op_rtrn(): + global pc, sp, ap + pc = mem[(ap + 1) & MEM_MASK] + ap = mem[ap & MEM_MASK] + sp = ap +def op_rv(): + global sp + sp = (sp - 1) & WORD_MASK + mem[sp & MEM_MASK] = mem[mem[sp & MEM_MASK] & MEM_MASK] + sp = (sp + 1) & WORD_MASK +def op_save(): + # the same as save + global pc, sp + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + sp = (ap + n) & WORD_MASK +def op_sg(): + global pc, sp + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + sp = (sp - 1) & WORD_MASK + mem[n & GLOBAL_MASK] = mem[sp & MEM_MASK] +def op_sl(): + global pc, sp + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + sp = (sp - 1) & WORD_MASK + mem[n & MEM_MASK] = mem[sp & MEM_MASK] +def op_sp(): + global pc, sp + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + sp = (sp - 1) & WORD_MASK + mem[(ap + n) & MEM_MASK] = mem[sp & MEM_MASK] +def op_stack(): + # the same as save + global pc, sp + n = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + sp = (ap + n) & WORD_MASK +def op_stind(): + global sp + sp = (sp - 2) & WORD_MASK + mem[mem[(sp + 1) & MEM_MASK] & MEM_MASK] = mem[sp & MEM_MASK] +def op_store(): + # tells compiler to spill stack after pushing automatic variable values + pass +def op_switchon(): + global pc, sp + n = mem[pc & MEM_MASK] + default_pc = mem[(pc + 1) & MEM_MASK] + sp = (sp - 1) & WORD_MASK + value = mem[sp & MEM_MASK] + for i in range(n): + pc = (pc + 2) & WORD_MASK + if value == mem[pc & MEM_MASK]: + pc = mem[(pc + 1) & MEM_MASK] + break + else: + pc = default_pc +def op_true(): + mem[sp & MEM_MASK] = 1 + sp = (sp + 1) & WORD_MASK +op_handlers = [ + op_datalab, + op_div, + op_end, + op_eq, + op_eqv, + op_false, + op_finish, + op_fnap, + op_fnrn, + op_ge, + op_global, + op_goto, + op_gr, + op_iteml, + op_itemn, + op_jf, + op_jt, + op_jump, + op_lab, + op_le, + op_lg, + op_ll, + op_llg, + op_lll, + op_llp, + op_ln, + op_logand, + op_logor, + op_lp, + op_ls, + op_lshift, + op_lstr, + op_minus, + op_mult, + op_ne, + op_neg, + op_neqv, + op_not, + op_plus, + op_rem, + op_res, + op_rshift, + op_rstack, + op_rtap, + op_rtrn, + op_rv, + op_save, + op_sg, + op_sl, + op_sp, + op_stack, + op_stind, + op_store, + op_switchon, + op_true, +] + +def sys_Createoutput(): + arg0 = mem[ap & MEM_MASK] + arg1 = mem[(ap + 1) & MEM_MASK] + arg2 = mem[(ap + 2) & MEM_MASK] + sp = (ap + 5) & WORD_MASK + if trace: + sys.stderr.write(f'Createoutput({arg0:012o}, {arg1:012o}, {arg2:012o})\n') + value = 0 + if trace: + sys.stderr.write(f'= {value:012o}\n') + mem[sp & MEM_MASK] = value + sp = (sp + 1) & WORD_MASK +def sys_InitializeIO(): + arg0 = mem[ap & MEM_MASK] + arg1 = mem[(ap + 1) & MEM_MASK] + sp = (ap + 4) & WORD_MASK + if trace: + sys.stderr.write(f'InitializeIO({arg0:012o}, {arg1:012o})\n') +sys_handlers = { + SYS_Createoutput: sys_Createoutput, + SYS_InitializeIO: sys_InitializeIO, +} + +# interpret +while True: + if pc in sys_handlers: + sys_handlers[pc]() + + op = mem[pc & MEM_MASK] + pc = (pc + 1) & WORD_MASK + if trace: + op_str, n_operands = ( + op_to_str_n_operands[op] + if op < N_OPS else + ('???', 0) + ) + sys.stderr.write( + 'ap={0:012o} sp={1:012o} pc={2:012o} op={3:03o} {4:s}{5:s}\n'.format( + ap, + sp, + (pc - 1) & WORD_MASK, + op & BYTE_MASK, + op_str, + ''.join( + [f' {mem[(pc + i) & MEM_MASK]:012o}' for i in range(n_operands)] + ) + ) + ) + op_handlers[op]() -- 2.34.1