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"\s*" |
|||
r"(?:;(?P<comment>[\w\s]+))?" |
|||
) |
|||
⚫ | |||
⚫ | |||
⚫ | |||
class AssemblySyntaxError(Exception): |
|||
pass |
|||
Bytecode = Iterable[int] |
|||
Instructions = List[Instruction] |
|||
Labels = Dict[str, int] |
|||
⚫ | |||
label: Optional[str] |
|||
⚫ | |||
⚫ | |||
comment: Optional[str] |
|||
def parse(assembly: str) -> Tuple[ |
def parse(assembly: str) -> Tuple[List[Instruction], Dict[str, int]]: |
||
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 |
for line in assembly.split("\n"): |
||
match = RE_INSTRUCTION.match(line) |
|||
if ";" in line: |
|||
if not match: |
|||
⚫ | |||
⚫ | |||
# Parse label |
|||
label = match.group(1) |
|||
if label: |
|||
labels[label] = linenum |
labels[label] = linenum |
||
# Parse opcode and argument |
|||
⚫ | |||
if len(instruction) == 1: |
|||
⚫ | |||
⚫ | |||
opcode: Optional[str] = instruction[0] |
|||
argument: Union[str, int, None] = None |
|||
elif instruction[0].isnumeric(): |
|||
# Data, without opcode |
|||
opcode = None |
|||
argument = int(instruction[0]) |
|||
⚫ | |||
# 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" |
|||
⚫ | |||
else: |
|||
⚫ | |||
⚫ | |||
linenum += 1 |
linenum += 1 |
||
Line 742: | Line 722: | ||
def compile(instructions: |
def compile(instructions: List[Instruction], labels: Dict[str, int]) -> Iterable[int]: |
||
for instruction in instructions: |
for instruction in instructions: |
||
if |
if instruction.argument is None: |
||
argument = labels[instruction.argument] |
|||
elif isinstance(instruction.argument, int): |
|||
argument = instruction.argument |
|||
else: |
|||
argument = 0 |
argument = 0 |
||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
if instruction.opcode: |
if instruction.opcode: |
||
Line 785: | Line 765: | ||
break |
break |
||
else: |
else: |
||
raise Exception(": |
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 |
instructions, labels = parse(sample) |
||
bytecode = bytes(compile(instructions, labels)) |
bytecode = bytes(compile(instructions, labels)) |
||
result = run(bytecode) |
|||
print( |
print(result) |
||