1
0
mirror of https://github.com/mnaberez/py65.git synced 2024-06-10 17:29:34 +00:00
py65/py65/assembler.py

133 lines
4.6 KiB
Python
Raw Permalink Normal View History

2008-11-21 05:44:25 +00:00
import re
from py65.utils.addressing import AddressParser
2008-11-21 05:44:25 +00:00
2012-11-19 20:44:30 +00:00
2008-11-21 05:44:25 +00:00
class Assembler:
Statement = re.compile(r'^([A-z]{3}[0-7]?\s+'
2008-11-21 05:44:25 +00:00
r'\(?\s*)([^,\s\)]+)(\s*[,xXyY\s]*\)?'
r'[,xXyY\s]*)$')
2014-12-15 01:26:18 +00:00
Addressing = (
('zpi', "($00FF)"),
('zpx', "$00FF,X"),
('zpy', "$00FF,Y"),
('zpg', "$00FF"),
('inx', "($00FF,X)"),
('iax', "($FFFF,X)"),
('iny', "($00FF),Y"),
('ind', "($FFFF)"),
('abx', "$FFFF,X"),
('aby', "$FFFF,Y"),
('abs', "$FFFF"),
('rel', "$FFFF"),
('imp', ""),
('acc', ""),
('acc', "A"),
('imm', "#$FF")
2014-12-15 01:26:18 +00:00
)
2012-11-19 20:44:30 +00:00
def __init__(self, mpu, address_parser=None):
2008-11-21 05:44:25 +00:00
""" If a configured AddressParser is passed, symbolic addresses
may be used in the assembly statements.
"""
self._mpu = mpu
2008-11-21 05:44:25 +00:00
if address_parser is None:
address_parser = AddressParser()
self._address_parser = address_parser
self._addressing = []
numchars = mpu.BYTE_WIDTH / 4 # 1 byte = 2 chars in hex
for mode, format in self.Addressing:
pat = "^" + re.escape(format) + "$"
pat = pat.replace('00', '0{%d}' % numchars)
pat = pat.replace('FF', '([0-9A-F]{%d})' % numchars)
self._addressing.append([mode, re.compile(pat)])
2008-11-21 05:44:25 +00:00
def assemble(self, statement, pc=0000):
""" Assemble the given assembly language statement. If the statement
uses relative addressing, the program counter (pc) must also be given.
2012-11-19 01:05:12 +00:00
The result is a list of bytes. Raises when assembly fails.
2008-11-21 05:44:25 +00:00
"""
opcode, operand = self.normalize_and_split(statement)
for mode, pattern in self._addressing:
match = pattern.match(operand)
2008-11-21 05:44:25 +00:00
if match:
# check if opcode supports this addressing mode
2008-11-21 05:44:25 +00:00
try:
2012-11-19 20:44:30 +00:00
bytes = [self._mpu.disassemble.index((opcode, mode))]
2008-11-21 05:44:25 +00:00
except ValueError:
continue
operands = match.groups()
if mode == 'rel':
# relative branch
absolute = int(''.join(operands), 16)
relative = (absolute - pc) - 2
relative = relative & self._mpu.byteMask
operands = [(self._mpu.BYTE_FORMAT % relative)]
2008-11-21 05:44:25 +00:00
elif len(operands) == 2:
# swap bytes
operands = (operands[1], operands[0])
2012-11-19 20:44:30 +00:00
operands = [int(hex, 16) for hex in operands]
2008-11-21 05:44:25 +00:00
bytes.extend(operands)
# raise if the assembled bytes would exceed top of memory
if (pc + len(bytes)) > (2 ** self._mpu.ADDR_WIDTH):
raise OverflowError
2008-11-21 05:44:25 +00:00
return bytes
# assembly failed
2012-11-19 01:05:12 +00:00
raise SyntaxError(statement)
2008-11-21 05:44:25 +00:00
def normalize_and_split(self, statement):
""" Given an assembly language statement like "lda $c12,x", normalize
the statement by uppercasing it, removing unnecessary whitespace,
and parsing the address part using AddressParser. The result of
the normalization is a tuple of two strings (opcode, operand).
2008-11-21 05:44:25 +00:00
"""
statement = ' '.join(statement.split())
2008-11-21 05:44:25 +00:00
# normalize target in operand
match = self.Statement.match(statement)
if match:
before, target, after = match.groups()
# target is an immediate value
2008-11-21 05:44:25 +00:00
if target.startswith('#'):
try:
if target[1] in ("'", '"'): # quoted ascii character
number = ord(target[2])
else:
number = self._address_parser.number(target[1:])
except IndexError:
raise SyntaxError(statement)
if (number < 0) or (number > self._mpu.byteMask):
2008-11-21 05:44:25 +00:00
raise OverflowError
statement = before + '#$' + self._mpu.BYTE_FORMAT % number
2008-11-21 05:44:25 +00:00
# target is the accumulator
elif target in ('a', 'A'):
2008-11-21 05:44:25 +00:00
pass
# target is an address or label
else:
address = self._address_parser.number(target)
statement = before + '$' + self._mpu.ADDR_FORMAT % address + after
# separate opcode and operand
splitted = statement.split(" ", 2)
opcode = splitted[0].strip().upper()
if len(splitted) > 1:
operand = splitted[1].strip().upper()
else:
operand = ''
2008-11-21 05:44:25 +00:00
return (opcode, operand)