Archipelago/worlds/ladx/LADXR/assembler.py

846 lines
32 KiB
Python

import binascii
from typing import Optional, Dict, ItemsView, List, Union, Tuple
import unicodedata
from . import utils
import re
REGS8 = {"A": 7, "B": 0, "C": 1, "D": 2, "E": 3, "H": 4, "L": 5, "[HL]": 6}
REGS16A = {"BC": 0, "DE": 1, "HL": 2, "SP": 3}
REGS16B = {"BC": 0, "DE": 1, "HL": 2, "AF": 3}
FLAGS = {"NZ": 0x00, "Z": 0x08, "NC": 0x10, "C": 0x18}
CONST_MAP: Dict[str, int] = {}
class ExprBase:
def asReg8(self) -> Optional[int]:
return None
def isA(self, kind: str, value: Optional[str] = None) -> bool:
return False
class Token(ExprBase):
def __init__(self, kind: str, value: Union[str, int], line_nr: int) -> None:
self.kind = kind
self.value = value
self.line_nr = line_nr
def isA(self, kind: str, value: Optional[str] = None) -> bool:
return self.kind == kind and (value is None or value == self.value)
def __repr__(self) -> str:
return "[%s:%s:%d]" % (self.kind, self.value, self.line_nr)
def asReg8(self) -> Optional[int]:
if self.kind == 'ID':
return REGS8.get(str(self.value), None)
return None
class REF(ExprBase):
def __init__(self, expr: ExprBase) -> None:
self.expr = expr
def asReg8(self) -> Optional[int]:
if self.expr.isA('ID', 'HL'):
return REGS8['[HL]']
return None
def __repr__(self) -> str:
return "[%s]" % (self.expr)
class OP(ExprBase):
def __init__(self, op: str, left: ExprBase, right: Optional[ExprBase] = None):
self.op = op
self.left = left
self.right = right
def __repr__(self) -> str:
return "%s %s %s" % (self.left, self.op, self.right)
@staticmethod
def make(op: str, left: ExprBase, right: Optional[ExprBase] = None) -> ExprBase:
if left.isA('NUMBER') and right is not None and right.isA('NUMBER'):
assert isinstance(right, Token) and isinstance(right.value, int)
assert isinstance(left, Token) and isinstance(left.value, int)
if op == '+':
left.value += right.value
return left
if op == '-':
left.value -= right.value
return left
if op == '*':
left.value *= right.value
return left
if op == '/':
left.value //= right.value
return left
if left.isA('NUMBER') and right is None:
assert isinstance(left, Token) and isinstance(left.value, int)
if op == '+':
return left
if op == '-':
left.value = -left.value
return left
return OP(op, left, right)
class Tokenizer:
TOKEN_REGEX = re.compile('|'.join('(?P<%s>%s)' % pair for pair in [
('NUMBER', r'\d+(\.\d*)?'),
('HEX', r'\$[0-9A-Fa-f]+'),
('ASSIGN', r':='),
('COMMENT', r';[^\n]+'),
('LABEL', r':'),
('DIRECTIVE', r'#[A-Za-z_]+'),
('STRING', '[a-zA-Z]?"[^"]*"'),
('ID', r'\.?[A-Za-z_][A-Za-z0-9_\.]*'),
('OP', r'[+\-*/,\(\)]'),
('REFOPEN', r'\['),
('REFCLOSE', r'\]'),
('NEWLINE', r'\n'),
('SKIP', r'[ \t]+'),
('MISMATCH', r'.'),
]))
def __init__(self, code: str) -> None:
self.__tokens: List[Token] = []
line_num = 1
for mo in self.TOKEN_REGEX.finditer(code):
kind = mo.lastgroup
assert kind is not None
value: Union[str, int] = mo.group()
if kind == 'MISMATCH':
line = code.split("\n")[line_num - 1]
raise RuntimeError(f"Syntax error on line: {line_num}: {kind}:`{line}`")
elif kind == 'SKIP':
pass
elif kind == 'COMMENT':
pass
else:
if kind == 'NUMBER':
value = int(value)
elif kind == 'HEX':
value = int(str(value)[1:], 16)
kind = 'NUMBER'
elif kind == 'ID':
value = str(value).upper()
self.__tokens.append(Token(kind, value, line_num))
if kind == 'NEWLINE':
line_num += 1
self.__tokens.append(Token('NEWLINE', '\n', line_num))
def peek(self) -> Token:
return self.__tokens[0]
def pop(self) -> Token:
return self.__tokens.pop(0)
def expect(self, kind: str, value: Optional[str] = None) -> None:
pop = self.pop()
if not pop.isA(kind, value):
if value is not None:
raise SyntaxError("%s != %s:%s" % (pop, kind, value))
raise SyntaxError("%s != %s" % (pop, kind))
def __bool__(self) -> bool:
return bool(self.__tokens)
class Assembler:
SIMPLE_INSTR = {
'NOP': 0x00,
'RLCA': 0x07,
'RRCA': 0x0F,
'STOP': 0x010,
'RLA': 0x17,
'RRA': 0x1F,
'DAA': 0x27,
'CPL': 0x2F,
'SCF': 0x37,
'CCF': 0x3F,
'HALT': 0x76,
'RETI': 0xD9,
'DI': 0xF3,
'EI': 0xFB,
}
LINK_REL8 = 0
LINK_ABS8 = 1
LINK_ABS16 = 2
def __init__(self, base_address: Optional[int] = None) -> None:
self.__base_address = base_address or -1
self.__result = bytearray()
self.__label: Dict[str, int] = {}
self.__constant: Dict[str, int] = {}
self.__link: Dict[int, Tuple[int, ExprBase]] = {}
self.__scope: Optional[str] = None
self.__tok = Tokenizer("")
def process(self, code: str) -> None:
conditional_stack = [True]
self.__tok = Tokenizer(code)
try:
while self.__tok:
start = self.__tok.pop()
if start.kind == 'NEWLINE':
pass # Empty newline
elif start.kind == 'DIRECTIVE':
if start.value == '#IF':
t = self.parseExpression()
assert isinstance(t, Token)
conditional_stack.append(conditional_stack[-1] and t.value != 0)
self.__tok.expect('NEWLINE')
elif start.value == '#ELSE':
conditional_stack[-1] = not conditional_stack[-1] and conditional_stack[-2]
self.__tok.expect('NEWLINE')
elif start.value == '#ENDIF':
conditional_stack.pop()
assert conditional_stack
self.__tok.expect('NEWLINE')
else:
raise SyntaxError(start)
elif not conditional_stack[-1]:
while not self.__tok.pop().isA('NEWLINE'):
pass
elif start.kind == 'ID':
if start.value == 'DB':
self.instrDB()
self.__tok.expect('NEWLINE')
elif start.value == 'DW':
self.instrDW()
self.__tok.expect('NEWLINE')
elif start.value == 'LD':
self.instrLD()
self.__tok.expect('NEWLINE')
elif start.value == 'LDH':
self.instrLDH()
self.__tok.expect('NEWLINE')
elif start.value == 'LDI':
self.instrLDI()
self.__tok.expect('NEWLINE')
elif start.value == 'LDD':
self.instrLDD()
self.__tok.expect('NEWLINE')
elif start.value == 'INC':
self.instrINC()
self.__tok.expect('NEWLINE')
elif start.value == 'DEC':
self.instrDEC()
self.__tok.expect('NEWLINE')
elif start.value == 'ADD':
self.instrADD()
self.__tok.expect('NEWLINE')
elif start.value == 'ADC':
self.instrALU(0x88)
self.__tok.expect('NEWLINE')
elif start.value == 'SUB':
self.instrALU(0x90)
self.__tok.expect('NEWLINE')
elif start.value == 'SBC':
self.instrALU(0x98)
self.__tok.expect('NEWLINE')
elif start.value == 'AND':
self.instrALU(0xA0)
self.__tok.expect('NEWLINE')
elif start.value == 'XOR':
self.instrALU(0xA8)
self.__tok.expect('NEWLINE')
elif start.value == 'OR':
self.instrALU(0xB0)
self.__tok.expect('NEWLINE')
elif start.value == 'CP':
self.instrALU(0xB8)
self.__tok.expect('NEWLINE')
elif start.value == 'BIT':
self.instrBIT(0x40)
self.__tok.expect('NEWLINE')
elif start.value == 'RES':
self.instrBIT(0x80)
self.__tok.expect('NEWLINE')
elif start.value == 'SET':
self.instrBIT(0xC0)
self.__tok.expect('NEWLINE')
elif start.value == 'RET':
self.instrRET()
self.__tok.expect('NEWLINE')
elif start.value == 'CALL':
self.instrCALL()
self.__tok.expect('NEWLINE')
elif start.value == 'RLC':
self.instrCB(0x00)
self.__tok.expect('NEWLINE')
elif start.value == 'RRC':
self.instrCB(0x08)
self.__tok.expect('NEWLINE')
elif start.value == 'RL':
self.instrCB(0x10)
self.__tok.expect('NEWLINE')
elif start.value == 'RR':
self.instrCB(0x18)
self.__tok.expect('NEWLINE')
elif start.value == 'SLA':
self.instrCB(0x20)
self.__tok.expect('NEWLINE')
elif start.value == 'SRA':
self.instrCB(0x28)
self.__tok.expect('NEWLINE')
elif start.value == 'SWAP':
self.instrCB(0x30)
self.__tok.expect('NEWLINE')
elif start.value == 'SRL':
self.instrCB(0x38)
self.__tok.expect('NEWLINE')
elif start.value == 'RST':
self.instrRST()
self.__tok.expect('NEWLINE')
elif start.value == 'JP':
self.instrJP()
self.__tok.expect('NEWLINE')
elif start.value == 'JR':
self.instrJR()
self.__tok.expect('NEWLINE')
elif start.value == 'PUSH':
self.instrPUSHPOP(0xC5)
self.__tok.expect('NEWLINE')
elif start.value == 'POP':
self.instrPUSHPOP(0xC1)
self.__tok.expect('NEWLINE')
elif start.value in self.SIMPLE_INSTR:
self.__result.append(self.SIMPLE_INSTR[str(start.value)])
self.__tok.expect('NEWLINE')
elif self.__tok.peek().kind == 'LABEL':
self.__tok.pop()
self.addLabel(str(start.value))
elif self.__tok.peek().kind == 'ASSIGN':
self.__tok.pop()
value = self.__tok.pop()
if value.kind != 'NUMBER':
raise SyntaxError(start)
self.addConstant(str(start.value), int(value.value))
else:
raise SyntaxError(start)
else:
raise SyntaxError(start)
except SyntaxError:
print("Syntax error on line: %s" % code.split("\n")[self.__tok.peek().line_nr-1])
raise
def insert8(self, expr: ExprBase) -> None:
if expr.isA('NUMBER'):
assert isinstance(expr, Token)
value = int(expr.value)
else:
self.__link[len(self.__result)] = (Assembler.LINK_ABS8, expr)
value = 0
assert 0 <= value < 256
self.__result.append(value)
def insertRel8(self, expr: ExprBase) -> None:
if expr.isA('NUMBER'):
assert isinstance(expr, Token)
self.__result.append(int(expr.value))
else:
self.__link[len(self.__result)] = (Assembler.LINK_REL8, expr)
self.__result.append(0x00)
def insert16(self, expr: ExprBase) -> None:
if expr.isA('NUMBER'):
assert isinstance(expr, Token)
value = int(expr.value)
else:
self.__link[len(self.__result)] = (Assembler.LINK_ABS16, expr)
value = 0
assert 0 <= value <= 0xFFFF
self.__result.append(value & 0xFF)
self.__result.append(value >> 8)
def insertString(self, string: str) -> None:
if string.startswith('"') and string.endswith('"'):
string = string[1:-1]
string = unicodedata.normalize('NFKD', string)
self.__result += string.encode("latin1", "ignore")
elif string.startswith("m\"") and string.endswith("\""):
self.__result += utils.formatText(string[2:-1].replace("|", "\n"))
else:
raise SyntaxError
def instrLD(self) -> None:
left_param = self.parseParam()
self.__tok.expect('OP', ',')
right_param = self.parseParam()
lr8 = left_param.asReg8()
rr8 = right_param.asReg8()
if lr8 is not None and rr8 is not None:
self.__result.append(0x40 | (lr8 << 3) | rr8)
elif left_param.isA('ID', 'A') and isinstance(right_param, REF):
if right_param.expr.isA('ID', 'BC'):
self.__result.append(0x0A)
elif right_param.expr.isA('ID', 'DE'):
self.__result.append(0x1A)
elif right_param.expr.isA('ID', 'HL+'): # TODO
self.__result.append(0x2A)
elif right_param.expr.isA('ID', 'HL-'): # TODO
self.__result.append(0x3A)
elif right_param.expr.isA('ID', 'C'):
self.__result.append(0xF2)
else:
self.__result.append(0xFA)
self.insert16(right_param.expr)
elif right_param.isA('ID', 'A') and isinstance(left_param, REF):
if left_param.expr.isA('ID', 'BC'):
self.__result.append(0x02)
elif left_param.expr.isA('ID', 'DE'):
self.__result.append(0x12)
elif left_param.expr.isA('ID', 'HL+'): # TODO
self.__result.append(0x22)
elif left_param.expr.isA('ID', 'HL-'): # TODO
self.__result.append(0x32)
elif left_param.expr.isA('ID', 'C'):
self.__result.append(0xE2)
else:
self.__result.append(0xEA)
self.insert16(left_param.expr)
elif left_param.isA('ID', 'BC'):
self.__result.append(0x01)
self.insert16(right_param)
elif left_param.isA('ID', 'DE'):
self.__result.append(0x11)
self.insert16(right_param)
elif left_param.isA('ID', 'HL'):
self.__result.append(0x21)
self.insert16(right_param)
elif left_param.isA('ID', 'SP'):
if right_param.isA('ID', 'HL'):
self.__result.append(0xF9)
else:
self.__result.append(0x31)
self.insert16(right_param)
elif right_param.isA('ID', 'SP') and isinstance(left_param, REF):
self.__result.append(0x08)
self.insert16(left_param.expr)
elif lr8 is not None:
self.__result.append(0x06 | (lr8 << 3))
self.insert8(right_param)
else:
raise SyntaxError
def instrLDH(self) -> None:
left_param = self.parseParam()
self.__tok.expect('OP', ',')
right_param = self.parseParam()
if left_param.isA('ID', 'A') and isinstance(right_param, REF):
if right_param.expr.isA('ID', 'C'):
self.__result.append(0xF2)
else:
self.__result.append(0xF0)
self.insert8(right_param.expr)
elif right_param.isA('ID', 'A') and isinstance(left_param, REF):
if left_param.expr.isA('ID', 'C'):
self.__result.append(0xE2)
else:
self.__result.append(0xE0)
self.insert8(left_param.expr)
else:
raise SyntaxError
def instrLDI(self) -> None:
left_param = self.parseParam()
self.__tok.expect('OP', ',')
right_param = self.parseParam()
if left_param.isA('ID', 'A') and isinstance(right_param, REF) and right_param.expr.isA('ID', 'HL'):
self.__result.append(0x2A)
elif right_param.isA('ID', 'A') and isinstance(left_param, REF) and left_param.expr.isA('ID', 'HL'):
self.__result.append(0x22)
else:
raise SyntaxError
def instrLDD(self) -> None:
left_param = self.parseParam()
self.__tok.expect('OP', ',')
right_param = self.parseParam()
if left_param.isA('ID', 'A') and isinstance(right_param, REF) and right_param.expr.isA('ID', 'HL'):
self.__result.append(0x3A)
elif right_param.isA('ID', 'A') and isinstance(left_param, REF) and left_param.expr.isA('ID', 'HL'):
self.__result.append(0x32)
else:
raise SyntaxError
def instrINC(self) -> None:
param = self.parseParam()
r8 = param.asReg8()
if r8 is not None:
self.__result.append(0x04 | (r8 << 3))
elif param.isA('ID', 'BC'):
self.__result.append(0x03)
elif param.isA('ID', 'DE'):
self.__result.append(0x13)
elif param.isA('ID', 'HL'):
self.__result.append(0x23)
elif param.isA('ID', 'SP'):
self.__result.append(0x33)
else:
raise SyntaxError
def instrDEC(self) -> None:
param = self.parseParam()
r8 = param.asReg8()
if r8 is not None:
self.__result.append(0x05 | (r8 << 3))
elif param.isA('ID', 'BC'):
self.__result.append(0x0B)
elif param.isA('ID', 'DE'):
self.__result.append(0x1B)
elif param.isA('ID', 'HL'):
self.__result.append(0x2B)
elif param.isA('ID', 'SP'):
self.__result.append(0x3B)
else:
raise SyntaxError
def instrADD(self) -> None:
left_param = self.parseParam()
self.__tok.expect('OP', ',')
right_param = self.parseParam()
if left_param.isA('ID', 'A'):
rr8 = right_param.asReg8()
if rr8 is not None:
self.__result.append(0x80 | rr8)
else:
self.__result.append(0xC6)
self.insert8(right_param)
elif left_param.isA('ID', 'HL') and right_param.isA('ID') and isinstance(right_param, Token) and right_param.value in REGS16A:
self.__result.append(0x09 | REGS16A[str(right_param.value)] << 4)
elif left_param.isA('ID', 'SP'):
self.__result.append(0xE8)
self.insert8(right_param)
else:
raise SyntaxError
def instrALU(self, code_value: int) -> None:
param = self.parseParam()
if param.isA('ID', 'A') and self.__tok.peek().isA('OP', ','):
self.__tok.pop()
param = self.parseParam()
r8 = param.asReg8()
if r8 is not None:
self.__result.append(code_value | r8)
else:
self.__result.append(code_value | 0x46)
self.insert8(param)
def instrRST(self) -> None:
param = self.parseParam()
if param.isA('NUMBER') and isinstance(param, Token) and (int(param.value) & ~0x38) == 0:
self.__result.append(0xC7 | int(param.value))
else:
raise SyntaxError
def instrPUSHPOP(self, code_value: int) -> None:
param = self.parseParam()
if param.isA('ID') and isinstance(param, Token) and str(param.value) in REGS16B:
self.__result.append(code_value | (REGS16B[str(param.value)] << 4))
else:
raise SyntaxError
def instrJR(self) -> None:
param = self.parseParam()
if self.__tok.peek().isA('OP', ','):
self.__tok.pop()
condition = param
param = self.parseParam()
if condition.isA('ID') and isinstance(condition, Token) and str(condition.value) in FLAGS:
self.__result.append(0x20 | FLAGS[str(condition.value)])
else:
raise SyntaxError
else:
self.__result.append(0x18)
self.insertRel8(param)
def instrCB(self, code_value: int) -> None:
param = self.parseParam()
r8 = param.asReg8()
if r8 is not None:
self.__result.append(0xCB)
self.__result.append(code_value | r8)
else:
raise SyntaxError
def instrBIT(self, code_value: int) -> None:
left_param = self.parseParam()
self.__tok.expect('OP', ',')
right_param = self.parseParam()
rr8 = right_param.asReg8()
if left_param.isA('NUMBER') and isinstance(left_param, Token) and rr8 is not None:
self.__result.append(0xCB)
self.__result.append(code_value | (int(left_param.value) << 3) | rr8)
else:
raise SyntaxError
def instrRET(self) -> None:
if self.__tok.peek().isA('ID'):
condition = self.__tok.pop()
if condition.isA('ID') and condition.value in FLAGS:
self.__result.append(0xC0 | FLAGS[str(condition.value)])
else:
raise SyntaxError
else:
self.__result.append(0xC9)
def instrCALL(self) -> None:
param = self.parseParam()
if self.__tok.peek().isA('OP', ','):
self.__tok.pop()
condition = param
param = self.parseParam()
if condition.isA('ID') and isinstance(condition, Token) and condition.value in FLAGS:
self.__result.append(0xC4 | FLAGS[str(condition.value)])
else:
raise SyntaxError
else:
self.__result.append(0xCD)
self.insert16(param)
def instrJP(self) -> None:
param = self.parseParam()
if self.__tok.peek().isA('OP', ','):
self.__tok.pop()
condition = param
param = self.parseParam()
if condition.isA('ID') and isinstance(condition, Token) and condition.value in FLAGS:
self.__result.append(0xC2 | FLAGS[str(condition.value)])
else:
raise SyntaxError
elif param.isA('ID', 'HL'):
self.__result.append(0xE9)
return
else:
self.__result.append(0xC3)
self.insert16(param)
def instrDW(self) -> None:
param = self.parseExpression()
self.insert16(param)
while self.__tok.peek().isA('OP', ','):
self.__tok.pop()
param = self.parseExpression()
self.insert16(param)
def instrDB(self) -> None:
param = self.parseExpression()
if param.isA('STRING'):
assert isinstance(param, Token)
self.insertString(str(param.value))
else:
self.insert8(param)
while self.__tok.peek().isA('OP', ','):
self.__tok.pop()
param = self.parseExpression()
if param.isA('STRING'):
assert isinstance(param, Token)
self.insertString(str(param.value))
else:
self.insert8(param)
def addLabel(self, label: str) -> None:
if label.startswith("."):
assert self.__scope is not None
label = self.__scope + label
else:
assert "." not in label, label
self.__scope = label
assert label not in self.__label, "Duplicate label: %s" % (label)
assert label not in self.__constant, "Duplicate label: %s" % (label)
self.__label[label] = len(self.__result)
def addConstant(self, name: str, value: int) -> None:
assert name not in self.__constant, "Duplicate constant: %s" % (name)
assert name not in self.__label, "Duplicate constant: %s" % (name)
self.__constant[name] = value
def parseParam(self) -> ExprBase:
t = self.__tok.peek()
if t.kind == 'REFOPEN':
self.__tok.pop()
expr = self.parseExpression()
self.__tok.expect('REFCLOSE')
return REF(expr)
return self.parseExpression()
def parseExpression(self) -> ExprBase:
t = self.parseAddSub()
return t
def parseAddSub(self) -> ExprBase:
t = self.parseFactor()
p = self.__tok.peek()
if p.isA('OP', '+') or p.isA('OP', '-'):
self.__tok.pop()
return OP.make(str(p.value), t, self.parseAddSub())
return t
def parseFactor(self) -> ExprBase:
t = self.parseUnary()
p = self.__tok.peek()
if p.isA('OP', '*') or p.isA('OP', '/'):
self.__tok.pop()
return OP.make(str(p.value), t, self.parseFactor())
return t
def parseUnary(self) -> ExprBase:
t = self.__tok.pop()
if t.isA('OP', '-') or t.isA('OP', '+'):
return OP.make(str(t.value), self.parseUnary())
elif t.isA('OP', '('):
result = self.parseExpression()
self.__tok.expect('OP', ')')
return result
if t.kind not in ('ID', 'NUMBER', 'STRING'):
raise SyntaxError
if t.isA('ID') and t.value in CONST_MAP:
t.kind = 'NUMBER'
t.value = CONST_MAP[str(t.value)]
elif t.isA('ID') and t.value in self.__constant:
t.kind = 'NUMBER'
t.value = self.__constant[str(t.value)]
elif t.isA('ID') and str(t.value).startswith("."):
assert self.__scope is not None
t.value = self.__scope + str(t.value)
return t
def link(self) -> None:
for offset, (link_type, link_expr) in self.__link.items():
expr = self.resolveExpr(link_expr)
assert expr is not None
assert expr.isA('NUMBER'), expr
assert isinstance(expr, Token)
value = int(expr.value)
if link_type == Assembler.LINK_REL8:
byte = (value - self.__base_address) - offset - 1
assert -128 <= byte <= 127, expr
self.__result[offset] = byte & 0xFF
elif link_type == Assembler.LINK_ABS8:
assert 0 <= value <= 0xFF
self.__result[offset] = value & 0xFF
elif link_type == Assembler.LINK_ABS16:
assert self.__base_address >= 0, "Cannot place absolute values in a relocatable code piece"
assert 0 <= value <= 0xFFFF
self.__result[offset] = value & 0xFF
self.__result[offset + 1] = value >> 8
else:
raise RuntimeError
def resolveExpr(self, expr: Optional[ExprBase]) -> Optional[ExprBase]:
if expr is None:
return None
elif isinstance(expr, OP):
left = self.resolveExpr(expr.left)
assert left is not None
return OP.make(expr.op, left, self.resolveExpr(expr.right))
elif isinstance(expr, Token) and expr.isA('ID') and isinstance(expr, Token) and expr.value in self.__label:
return Token('NUMBER', self.__label[str(expr.value)] + self.__base_address, expr.line_nr)
return expr
def getResult(self) -> bytearray:
return self.__result
def getLabels(self) -> ItemsView[str, int]:
return self.__label.items()
def const(name: str, value: int) -> None:
name = name.upper()
assert name not in CONST_MAP or CONST_MAP[name] == value
CONST_MAP[name] = value
def resetConsts() -> None:
CONST_MAP.clear()
def ASM(code: str, base_address: Optional[int] = None, labels_result: Optional[Dict[str, int]] = None) -> bytes:
asm = Assembler(base_address)
asm.process(code)
asm.link()
if labels_result is not None:
assert base_address is not None
for label, offset in asm.getLabels():
labels_result[label] = base_address + offset
return binascii.hexlify(asm.getResult())
def allOpcodesTest() -> None:
import json
opcodes = json.load(open("Opcodes.json", "rt"))
for label in (False, True):
for prefix, codes in opcodes.items():
for num, op in codes.items():
if op['mnemonic'].startswith('ILLEGAL_') or op['mnemonic'] == 'PREFIX':
continue
params = []
postfix = ''
for o in op['operands']:
name = o['name']
if name == 'd16' or name == 'a16':
if label:
name = 'LABEL'
else:
name = '$0000'
if name == 'd8' or name == 'a8':
name = '$00'
if name == 'r8':
if label and num != '0xE8':
name = 'LABEL'
else:
name = '$00'
if name[-1] == 'H' and name[0].isnumeric():
name = '$' + name[:-1]
if o['immediate']:
params.append(name)
else:
params.append("[%s]" % (name))
if 'increment' in o and o['increment']:
postfix = 'I'
if 'decrement' in o and o['decrement']:
postfix = 'D'
code = op["mnemonic"] + postfix + " " + ", ".join(params)
code = code.strip()
try:
data = ASM("LABEL:\n%s" % (code), 0x0000)
if prefix == 'cbprefixed':
assert data[0:2] == b'cb'
data = data[2:]
assert data[0:2] == num[2:].encode('ascii').lower(), data[0:2] + b"!=" + num[2:].encode('ascii').lower()
except Exception as e:
print("%s\t\t|%r|\t%s" % (code, e, num))
print(op)
if __name__ == "__main__":
#allOpcodesTest()
const("CONST1", 1)
const("CONST2", 2)
ASM("""
ld a, (123)
ld hl, $1234 + 456
ld hl, $1234 + CONST1
ld hl, label
ld hl, label.end - label
ld c, label.end - label
label:
nop
.end:
""", 0)
ASM("""
jr label
label:
""")
assert ASM("db 1 + 2 * 3") == b'07'