source/projects/shoggoth/src/python/ichor/assembler.py
2022-07-15 19:33:32 -06:00

94 lines
2.6 KiB
Python

#!/usr/bin/env python3
from dataclasses import dataclass
from random import choices
from string import ascii_lowercase, digits
from typing import Generator, List, Union, Optional, Sequence
from ichor import isa
def gensym(prefix = None) -> isa.Label:
frag = ''.join(choices(ascii_lowercase + digits, k=8))
return isa.Label(f"{prefix or 'gensym'}_{frag}")
class FuncBuilder(object):
def __init__(self) -> None:
self._opcodes = []
def _write(self, op):
self._opcodes.append(op)
def write(self, op: Union[isa.Opcode, isa.Label, Sequence[isa.Opcode]]):
def flatten(o):
for e in o:
if isinstance(e, (isa.Opcode, isa.Label)):
yield e
else:
yield from flatten(e)
if isinstance(op, (isa.Opcode, isa.Label)):
self._opcodes.append(op)
else:
for e in op:
self.write(e)
def make_label(self, prefix: Optional[str] = ""):
return gensym(prefix)
def build(self) -> List[isa.Opcode]:
"""Assemble the written body into fully resolved opcodes."""
# The trivial two-pass assembler. First pass removes labels from the
# opcode stream, marking where they occurred.
labels = {}
unassembled = []
for op in self._opcodes:
match op:
case isa.Label(_) as l:
assert l not in labels # isa.Label marks must be unique.
labels[l] = len(unassembled)
case o:
unassembled.append(o)
# Second pass rewrites instructions (which can reference forwards OR
# backwards labels) with real targets instead of labels.
assembled = []
for op in unassembled:
match op:
case isa.GOTO(isa.Label(_) as l):
assembled.append(isa.GOTO(labels[l]))
case isa.VTEST(isa.Label(_) as l):
assembled.append(isa.VTEST(labels[l]))
case o:
assembled.append(o)
return assembled
def assemble(builder_cls=FuncBuilder, /, **opcodes: List[isa.Opcode]) -> List[isa.Opcode]:
builder = builder_cls()
for o in opcodes:
builder.write(o)
return builder.build()
class LocalBuilder(FuncBuilder):
def __init__(self):
super().__init__()
self._stack = 0
self._labels = {}
def _write(self, op):
pass
def write_local(self, label: isa.Label):
pass
def get_local(self, label: isa.Label):
pass