"""Tiny assembler that writes Power C object files (.o/.obj) directly. Syntax (one statement per line, ';' comments): label: local label .def NAME export NAME at this offset (def kind 1) .byte 1,2,$ff raw bytes lda #$12 lda $14 lda $0123,x lda ($14),y lda ($14,x) bne label jmp label jsr label (jmp/jsr/abs operands on local labels are relocated) jsr ext:NAME reference to an external symbol (absolute) lda ext:NAME+1 external symbol + 1 ldx # for the high byte) lda label,x absolute operand on a local label (relocated) """ import re, sys, os sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from objfmt import Obj from pcopt import ENC, SIZE, BRANCH def num(t): t = t.strip() if t.startswith('$'): return int(t[1:], 16) return int(t, 0) def parse_operand(s): """returns (mode, value-or-label, ext) ; ext = (name, kind) for external symbols""" s = s.strip() if not s: return 'imp', None, None if s == 'a': return 'acc', None, None if s.startswith('#'): t = s[1:] part = None if t.startswith('<') or t.startswith('>'): part = t[0] t = t[1:] if t.startswith('ext:'): return 'imm', 0, (t[4:].encode(), 2 if part == '<' else 1) v = num(t) if part == '<': v &= 255 elif part == '>': v = v >> 8 & 255 return 'imm', v, None m = re.match(r'^\((.+),x\)$', s) if m: return 'izx', num(m.group(1)), None m = re.match(r'^\((.+)\),y$', s) if m: return 'izy', num(m.group(1)), None idx = '' m = re.match(r'^(.*),([xy])$', s) if m: s, idx = m.group(1), m.group(2) if s.startswith('ext:'): t = s[4:] kind = 0 m = re.match(r'^(.*)\+1$', t) if m: t, kind = m.group(1), 4 return ('abs' + idx if idx else 'abs'), 0, (t.encode(), kind) try: v = num(s) md = 'zp' if v < 256 else 'abs' return (md + idx if idx else md), v, None except ValueError: return ('abs' + idx if idx else 'abs'), s, None # local label def assemble(text, base=0): lines = [] for ln in text.splitlines(): ln = ln.split(';')[0].strip() m = re.match(r'^([A-Za-z_]\w*):\s*(.*)$', ln) while m: lines.append(m.group(1) + ':') ln = m.group(2).strip() m = re.match(r'^([A-Za-z_]\w*):\s*(.*)$', ln) if ln: lines.append(ln) # pass 1: sizes items = [] # ('label',name) ('def',name) ('bytes',[...]) ('ins',mn,md,val,ext) for ln in lines: if ln.endswith(':'): items.append(('label', ln[:-1])) continue if ln.startswith('.def'): items.append(('def', ln.split()[1])) continue if ln.startswith('.byte'): items.append(('bytes', [num(x) for x in ln[5:].split(',')])) continue parts = ln.split(None, 1) mn = parts[0].lower() md, val, ext = parse_operand(parts[1] if len(parts) > 1 else '') if mn in BRANCH: md = 'rel' elif mn in ('jmp', 'jsr'): md = 'abs' elif md == 'zp' and (mn, 'zp') not in ENC and (mn, 'abs') in ENC: md = 'abs' elif md == 'zpx' and (mn, 'zpx') not in ENC and (mn, 'abx') in ENC: md = 'abx' elif md == 'zpy' and (mn, 'zpy') not in ENC and (mn, 'aby') in ENC: md = 'aby' if (mn, md) not in ENC: raise ValueError('bad instruction: ' + ln) items.append(('ins', mn, md, val, ext)) labels, defs = {}, [] pos = base for it in items: if it[0] == 'label': labels[it[1]] = pos elif it[0] == 'def': defs.append([it[1].encode(), 1, pos]) elif it[0] == 'bytes': pos += len(it[1]) else: pos += SIZE[it[2]] code = bytearray() reloc, refs = [], [] for it in items: if it[0] == 'bytes': code += bytes(it[1]) elif it[0] == 'ins': _, mn, md, val, ext = it at = base + len(code) code.append(ENC[(mn, md)]) n = SIZE[md] if md == 'rel': t = labels[val] rel = t - (at + 2) if not -128 <= rel <= 127: raise ValueError('branch out of range at %x' % at) code.append(rel & 255) elif n == 2: if ext: refs.append([ext[0], ext[1], at]) val = ENC[(mn, md)] code.append(val & 255) elif n == 3: if ext: refs.append([ext[0], ext[1], at]) v = ENC[(mn, md)] * 0x101 elif isinstance(val, str): v = labels[val] reloc.append(at) else: v = val code += bytes((v & 255, v >> 8 & 255)) o = Obj() o.code = code o.reloc = reloc o.defs = defs o.refs = sorted(refs, key=lambda r: r[2]) o.res = [] o.trailing = b'' return o, labels