mirror of
				https://github.com/MarioSpore/Grinch-AP.git
				synced 2025-10-21 20:21:32 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			846 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			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
 | |
|     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'
 | 
