Execute Computer/Zero: Difference between revisions

Content added Content deleted
(Add Python)
(Python improve assembly parser)
Line 647: Line 647:
=={{header|Python}}==
=={{header|Python}}==
<lang python>"""Computer/zero Assembly emulator. Requires Python >= 3.7"""
<lang python>"""Computer/zero Assembly emulator. Requires Python >= 3.7"""

import re

from typing import Dict
from typing import Dict
from typing import Iterable
from typing import Iterable
Line 653: Line 656:
from typing import Optional
from typing import Optional
from typing import Tuple
from typing import Tuple
from typing import Union




Line 676: Line 678:
}
}


RE_INSTRUCTION = re.compile(
r"\s*"
r"(?:(?P<label>\w+):)?"
r"\s*"
rf"(?P<opcode>{'|'.join(OPCODES)})?"
r"\s*"
r"(?P<argument>\w+)?"
r"\s*"
r"(?:;(?P<comment>[\w\s]+))?"
)


class Instruction(NamedTuple):
opcode: Optional[str]
argument: Union[str, int, None]


class AssemblySyntaxError(Exception):
pass


Bytecode = Iterable[int]
Instructions = List[Instruction]
Labels = Dict[str, int]


class Instruction(NamedTuple):
label: Optional[str]
opcode: Optional[str]
argument: Optional[str]
comment: Optional[str]



def parse(assembly: str) -> Tuple[Instructions, Labels]:
def parse(assembly: str) -> Tuple[List[Instruction], Dict[str, int]]:
instructions: Instructions = []
instructions: List[Instruction] = []
labels: Dict[str, int] = {}
labels: Dict[str, int] = {}
linenum: int = 0
linenum: int = 0
lines = [line.strip() for line in assembly.split("\n")]


for line in lines:
for line in assembly.split("\n"):
# Discard comments
match = RE_INSTRUCTION.match(line)

if ";" in line:
line = line.split(";", 1)[0]
if not match:
raise AssemblySyntaxError(f"{line}: {linenum}")


instructions.append(Instruction(**match.groupdict()))
# Parse label
if ":" in line:
label = match.group(1)
label, line = line.split(":", 1)
if label:
labels[label] = linenum
labels[label] = linenum


# Parse opcode and argument
instruction = line.strip().split()
if len(instruction) == 1:
if instruction[0] in OPCODES:
# Opcode without argument
opcode: Optional[str] = instruction[0]
argument: Union[str, int, None] = None
elif instruction[0].isnumeric():
# Data, without opcode
opcode = None
argument = int(instruction[0])
else:
# Unexpected
raise Exception(f"unknown instruction '{line}', {linenum}")
elif len(instruction) == 2:
# Opcode and argument
opcode, argument = instruction

if opcode not in OPCODES:
raise Exception(f"unknown instruction '{line}', {linenum}")

if argument.isnumeric():
argument = int(argument)
else:
# Argument is a label
argument = argument
elif not instruction:
# blank line
opcode = "NOP"
argument = None
else:
raise Exception(f"unknown instruction '{line}', {linenum}")

instructions.append(Instruction(opcode, argument))
linenum += 1
linenum += 1


Line 742: Line 722:




def compile(instructions: Instructions, labels: Labels) -> Bytecode:
def compile(instructions: List[Instruction], labels: Dict[str, int]) -> Iterable[int]:
for instruction in instructions:
for instruction in instructions:
if isinstance(instruction.argument, str):
if instruction.argument is None:
argument = labels[instruction.argument]
elif isinstance(instruction.argument, int):
argument = instruction.argument
else:
argument = 0
argument = 0
elif instruction.argument.isnumeric():
argument = int(instruction.argument)
else:
argument = labels[instruction.argument]


if instruction.opcode:
if instruction.opcode:
Line 785: Line 765:
break
break
else:
else:
raise Exception(":( " + str(operation))
raise Exception(f"error: {operation} {argument}")


return accumulator
return accumulator
Line 928: Line 908:
def main() -> None:
def main() -> None:
for sample in SAMPLES:
for sample in SAMPLES:
instructions, labels = parse(sample.strip())
instructions, labels = parse(sample)
bytecode = bytes(compile(instructions, labels))
bytecode = bytes(compile(instructions, labels))
# print(bytecode)
result = run(bytecode)
print(run(bytecode))
print(result)