prog8/il65/handwritten/codegen.py
2018-01-13 02:13:32 +01:00

1948 lines
99 KiB
Python

"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the assembly code generator (from the parse tree)
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
import io
import re
import datetime
import subprocess
import contextlib
from functools import partial
from typing import TextIO, Callable
from .parse import ProgramFormat, ParseResult, Parser
from .symbols import *
class CodeError(Exception):
pass
class CodeGenerator:
BREAKPOINT_COMMENT_SIGNATURE = "~~~BREAKPOINT~~~"
BREAKPOINT_COMMENT_DETECTOR = r".(?P<address>\w+)\s+ea\s+nop\s+;\s+{:s}.*".format(BREAKPOINT_COMMENT_SIGNATURE)
def __init__(self, parsed: ParseResult) -> None:
self.parsed = parsed
self.generated_code = io.StringIO()
self.p = partial(print, file=self.generated_code)
self.previous_stmt_was_assignment = False
self.cur_block = None # type: Block
def generate(self) -> None:
print("\ngenerating assembly code")
self.sanitycheck()
self.header()
self.initialize_variables()
self.blocks()
self.footer()
def sanitycheck(self) -> None:
# duplicate block names?
all_blocknames = [b.name for b in self.parsed.blocks if b.name]
unique_blocknames = set(all_blocknames)
if len(all_blocknames) != len(unique_blocknames):
for name in unique_blocknames:
all_blocknames.remove(name)
raise CodeError("there are duplicate block names", all_blocknames)
# ZP block contains no code?
for zpblock in [b for b in self.parsed.blocks if b.name == "ZP"]:
if zpblock.label_names:
raise CodeError("ZP block cannot contain labels")
# can only contain code comments, or nothing at all
if not all(isinstance(s, Comment) for s in zpblock.statements):
raise CodeError("ZP block cannot contain code statements, only definitions and comments")
def optimize(self) -> None:
# optimize the generated assembly code
pass
def write_assembly(self, out: TextIO) -> None:
out.write(self.generated_code.getvalue())
def header(self) -> None:
self.p("; code generated by il65.py - codename 'Sick'")
self.p("; source file:", self.parsed.sourcefile)
if self.parsed.with_sys:
self.p("; output format:", self.parsed.format.value, " (with basic program SYS)")
else:
self.p("; output format:", self.parsed.format.value)
self.p("; assembler syntax is for 64tasm")
self.p(".cpu '6502'\n.enc 'none'\n")
if self.parsed.format == ProgramFormat.PRG:
if self.parsed.with_sys:
self.p("; ---- basic program with sys call ----")
self.p("* = " + Parser.to_hex(self.parsed.start_address))
year = datetime.datetime.now().year
self.p("\t\t.word (+), {:d}".format(year))
self.p("\t\t.null $9e, format(' %d ', _il65_sysaddr), $3a, $8f, ' il65 by idj'")
self.p("+\t\t.word 0")
self.p("_il65_sysaddr\t\t; assembly code starts here\n")
else:
self.p("; ---- program without sys call ----")
self.p("* = " + Parser.to_hex(self.parsed.start_address) + "\n")
if self.parsed.format == ProgramFormat.RAW:
self.p("; ---- raw assembler program ----")
self.p("* = " + Parser.to_hex(self.parsed.start_address) + "\n")
@staticmethod
def to_mflpt5(number: float) -> bytearray:
# algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
number = float(number)
if number < FLOAT_MAX_NEGATIVE or number > FLOAT_MAX_POSITIVE:
raise OverflowError("floating point number out of 5-byte mflpt range", number)
if number == 0.0:
return bytearray([0, 0, 0, 0, 0])
if number < 0.0:
sign = 0x80000000
number = -number
else:
sign = 0x00000000
mant, exp = math.frexp(number)
exp += 128
if exp < 1:
# underflow, use zero instead
return bytearray([0, 0, 0, 0, 0])
if exp > 255:
raise OverflowError("floating point number out of 5-byte mflpt range", number)
mant = sign | int(mant * 0x100000000) & 0x7fffffff
return bytearray([exp]) + int.to_bytes(mant, 4, "big")
@staticmethod
def mflpt5_to_float(mflpt: bytearray) -> float:
if mflpt == bytearray([0, 0, 0, 0, 0]):
return 0.0
exp = mflpt[0] - 128
sign = mflpt[1] & 0x80
number = 0x80000000 | int.from_bytes(mflpt[1:], "big")
number = float(number) * 2**exp / 0x100000000
return -number if sign else number
def initialize_variables(self) -> None:
must_save_zp = self.parsed.clobberzp and self.parsed.restorezp
if must_save_zp:
self.p("\t\tjsr il65_lib_zp.save_zeropage")
zp_float_bytes = {}
# Only the vars from the ZeroPage need to be initialized here,
# the vars in all other blocks are just defined and pre-filled there.
zpblocks = [b for b in self.parsed.blocks if b.name == "ZP"]
if zpblocks:
assert len(zpblocks) == 1
zpblock = zpblocks[0]
vars_to_init = [v for v in zpblock.symbols.iter_variables()
if v.allocate and v.type in (DataType.BYTE, DataType.WORD, DataType.FLOAT)]
# @todo optimize sort order (sort on value first, then type, then blockname, then address/name)
# (str(self.value) or "", self.blockname, self.name or "", self.address or 0, self.seq_nr)
prev_value = 0 # type: Union[str, int, float]
if vars_to_init:
self.p("; init zp vars")
self.p("\t\tlda #0\n\t\tldx #0")
for variable in vars_to_init:
vname = zpblock.label + '.' + variable.name
vvalue = variable.value
if variable.type == DataType.BYTE:
if vvalue != prev_value:
self.p("\t\tlda #${:02x}".format(vvalue))
prev_value = vvalue
self.p("\t\tsta {:s}".format(vname))
elif variable.type == DataType.WORD:
if vvalue != prev_value:
self.p("\t\tlda #<${:04x}".format(vvalue))
self.p("\t\tldx #>${:04x}".format(vvalue))
prev_value = vvalue
self.p("\t\tsta {:s}".format(vname))
self.p("\t\tstx {:s}+1".format(vname))
elif variable.type == DataType.FLOAT:
bytes = self.to_mflpt5(vvalue) # type: ignore
zp_float_bytes[variable.name] = (vname, bytes, vvalue)
if zp_float_bytes:
self.p("\t\tldx #4")
self.p("-")
for varname, (vname, b, fv) in zp_float_bytes.items():
self.p("\t\tlda _float_bytes_{:s},x".format(varname))
self.p("\t\tsta {:s},x".format(vname))
self.p("\t\tdex")
self.p("\t\tbpl -")
self.p("; end init zp vars")
else:
self.p("\t\t; there are no zp vars to initialize")
else:
self.p("\t\t; there is no zp block to initialize")
main_block_label = [b.label for b in self.parsed.blocks if b.name == "main"][0]
if must_save_zp:
self.p("\t\tjsr {:s}.start\t\t; call user code".format(main_block_label))
self.p("\t\tcld")
self.p("\t\tjmp il65_lib_zp.restore_zeropage")
else:
self.p("\t\tjmp {:s}.start\t\t; call user code".format(main_block_label))
self.p("")
for varname, (vname, bytes, fpvalue) in zp_float_bytes.items():
self.p("_float_bytes_{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *bytes, fpvalue))
self.p("\n")
def blocks(self) -> None:
# if there's a <header> block, it always goes second
for block in [b for b in self.parsed.blocks if b.name == "<header>"]:
self.cur_block = block
for s in block.statements:
if isinstance(s, Comment):
self.p(s.text)
else:
raise CodeError("header block cannot contain any other statements beside comments")
self.p("\n")
# if there's a Zeropage block, it always goes second
for zpblock in [b for b in self.parsed.blocks if b.name == "ZP"]:
self.cur_block = zpblock
self.p("\n; ---- zero page block: '{:s}' ----\t\t; src l. {:d}\n".format(zpblock.sourceref.file, zpblock.sourceref.line))
for s in zpblock.statements:
if isinstance(s, Comment):
self.p(s.text)
else:
raise CodeError("zp cannot contain any other statements beside comments")
self.p("{:s}\t.proc\n".format(zpblock.label))
self.generate_block_vars(zpblock)
self.p("\t.pend\n")
# make sure the main.start routine clears the decimal and carry flags as first steps
block = self.parsed.find_block("main")
statements = list(block.statements)
for index, stmt in enumerate(statements):
if isinstance(stmt, Label) and stmt.name == "start":
asmlines = [
"\t\tcld\t\t\t; clear decimal flag",
"\t\tclc\t\t\t; clear carry flag",
"\t\tclv\t\t\t; clear overflow flag",
]
statements.insert(index+1, InlineAsm(asmlines, stmt.sourceref))
break
block.statements = statements
# generate
for block in sorted(self.parsed.blocks, key=lambda b: b.address):
if block.name in ("ZP", "<header>"):
continue # these blocks are already processed
self.cur_block = block
self.p("\n; ---- next block: '{:s}' ----\t\t; src l. {:d}\n".format(block.sourceref.file, block.sourceref.line))
if block.address:
self.p(".cerror * > ${0:04x}, 'block address overlaps by ', *-${0:04x},' bytes'".format(block.address))
self.p("* = ${:04x}".format(block.address))
self.p("{:s}\t.proc\n".format(block.label))
self.generate_block_vars(block)
subroutines = list(sub for sub in block.symbols.iter_subroutines() if sub.address is not None)
if subroutines:
self.p("\n; external subroutines")
for subdef in subroutines:
assert subdef.sub_block is None
self.p("\t\t{:s} = {:s}".format(subdef.name, Parser.to_hex(subdef.address)))
self.p("; end external subroutines")
for stmt in block.statements:
self.generate_statement(stmt)
subroutines = list(sub for sub in block.symbols.iter_subroutines(True))
if subroutines:
self.p("\n; block subroutines")
for subdef in subroutines:
assert subdef.sub_block is not None
self.p("{:s}\t\t; src l. {:d}".format(subdef.name, subdef.sourceref.line))
params = ", ".join("{:s} -> {:s}".format(p[0] or "<unnamed>", p[1]) for p in subdef.parameters)
returns = ",".join(sorted(subdef.return_registers))
clobbers = ",".join(sorted(subdef.clobbered_registers))
self.p("\t\t; params: {}\n\t\t; returns: {} clobbers: {}"
.format(params or "-", returns or "-", clobbers or "-"))
cur_block = self.cur_block
self.cur_block = subdef.sub_block
for stmt in subdef.sub_block.statements:
self.generate_statement(stmt)
self.cur_block = cur_block
self.p("")
self.p("; end external subroutines")
self.p("\t.pend\n")
def generate_block_vars(self, block: Block) -> None:
consts = [c for c in block.symbols.iter_constants()]
if consts:
self.p("; constants")
for constdef in consts:
if constdef.type == DataType.FLOAT:
self.p("\t\t{:s} = {}".format(constdef.name, constdef.value))
elif constdef.type in (DataType.BYTE, DataType.WORD):
self.p("\t\t{:s} = {:s}".format(constdef.name, Parser.to_hex(constdef.value))) # type: ignore
elif constdef.type in STRING_DATATYPES:
# a const string is just a string variable in the generated assembly
self._generate_string_var(constdef)
else:
raise CodeError("invalid const type", constdef)
mem_vars = [vi for vi in block.symbols.iter_variables() if not vi.allocate and not vi.register]
if mem_vars:
self.p("; memory mapped variables")
for vardef in mem_vars:
# create a definition for variables at a specific place in memory (memory-mapped)
if vardef.type in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
self.p("\t\t{:s} = {:s}\t; {:s}".format(vardef.name, Parser.to_hex(vardef.address), vardef.type.name.lower()))
elif vardef.type == DataType.BYTEARRAY:
self.p("\t\t{:s} = {:s}\t; array of {:d} bytes".format(vardef.name, Parser.to_hex(vardef.address), vardef.length))
elif vardef.type == DataType.WORDARRAY:
self.p("\t\t{:s} = {:s}\t; array of {:d} words".format(vardef.name, Parser.to_hex(vardef.address), vardef.length))
elif vardef.type == DataType.MATRIX:
self.p("\t\t{:s} = {:s}\t; matrix {:d} by {:d} = {:d} bytes"
.format(vardef.name, Parser.to_hex(vardef.address), vardef.matrixsize[0], vardef.matrixsize[1], vardef.length))
else:
raise CodeError("invalid var type")
non_mem_vars = [vi for vi in block.symbols.iter_variables() if vi.allocate]
if non_mem_vars:
self.p("; normal variables")
for vardef in non_mem_vars:
# create a definition for a variable that takes up space and will be initialized at startup
sourcecomment = "\t; " + vardef.sourcecomment if vardef.sourcecomment else ""
if vardef.type in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
if vardef.address:
assert block.name == "ZP", "only ZP-variables can be put on an address"
self.p("\t\t{:s} = {:s}".format(vardef.name, Parser.to_hex(vardef.address)))
else:
if vardef.type == DataType.BYTE:
self.p("{:s}\t\t.byte {:s}{:s}".format(vardef.name, Parser.to_hex(int(vardef.value)), sourcecomment))
elif vardef.type == DataType.WORD:
self.p("{:s}\t\t.word {:s}{:s}".format(vardef.name, Parser.to_hex(int(vardef.value)), sourcecomment))
elif vardef.type == DataType.FLOAT:
self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}{:s}"
.format(vardef.name, *self.to_mflpt5(float(vardef.value)), sourcecomment))
else:
raise CodeError("weird datatype")
elif vardef.type in (DataType.BYTEARRAY, DataType.WORDARRAY):
if vardef.address:
raise CodeError("array or wordarray vars must not have address; will be allocated by assembler")
if vardef.type == DataType.BYTEARRAY:
self.p("{:s}\t\t.fill {:d}, ${:02x}{:s}".format(vardef.name, vardef.length, vardef.value or 0, sourcecomment))
elif vardef.type == DataType.WORDARRAY:
f_hi, f_lo = divmod(vardef.value or 0, 256) # type: ignore
self.p("{:s}\t\t.fill {:d}, [${:02x}, ${:02x}]\t; {:d} words of ${:04x}"
.format(vardef.name, vardef.length * 2, f_lo, f_hi, vardef.length, vardef.value or 0))
else:
raise CodeError("invalid datatype", vardef.type)
elif vardef.type == DataType.MATRIX:
if vardef.address:
raise CodeError("matrix vars must not have address; will be allocated by assembler")
self.p("{:s}\t\t.fill {:d}, ${:02x}\t\t; matrix {:d}*{:d} bytes"
.format(vardef.name,
vardef.matrixsize[0] * vardef.matrixsize[1],
vardef.value or 0,
vardef.matrixsize[0], vardef.matrixsize[1]))
elif vardef.type in STRING_DATATYPES:
self._generate_string_var(vardef)
else:
raise CodeError("unknown variable type " + str(vardef.type))
def _generate_string_var(self, vardef: Union[ConstantDef, VariableDef]) -> None:
if vardef.type == DataType.STRING:
# 0-terminated string
self.p("{:s}\n\t\t.null {:s}".format(vardef.name, self.output_string(str(vardef.value))))
elif vardef.type == DataType.STRING_P:
# pascal string
self.p("{:s}\n\t\t.ptext {:s}".format(vardef.name, self.output_string(str(vardef.value))))
elif vardef.type == DataType.STRING_S:
# 0-terminated string in screencode encoding
self.p(".enc 'screen'")
self.p("{:s}\n\t\t.null {:s}".format(vardef.name, self.output_string(str(vardef.value), True)))
self.p(".enc 'none'")
elif vardef.type == DataType.STRING_PS:
# 0-terminated pascal string in screencode encoding
self.p(".enc 'screen'")
self.p("{:s}\n\t\t.ptext {:s}".format(vardef.name, self.output_string(str(vardef.value), True)))
self.p(".enc 'none'")
def generate_statement(self, stmt: AstNode) -> None:
if isinstance(stmt, ReturnStmt):
if stmt.a:
if isinstance(stmt.a, IntegerValue):
self.p("\t\tlda #{:d}".format(stmt.a.value))
else:
raise CodeError("can only return immediate values for now") # XXX
if stmt.x:
if isinstance(stmt.x, IntegerValue):
self.p("\t\tldx #{:d}".format(stmt.x.value))
else:
raise CodeError("can only return immediate values for now") # XXX
if stmt.y:
if isinstance(stmt.y, IntegerValue):
self.p("\t\tldy #{:d}".format(stmt.y.value))
else:
raise CodeError("can only return immediate values for now") # XXX
self.p("\t\trts")
elif isinstance(stmt, AugmentedAssignmentStmt):
self.generate_augmented_assignment(stmt)
elif isinstance(stmt, AssignmentStmt):
self.generate_assignment(stmt)
elif isinstance(stmt, Label):
self.p("\n{:s}\t\t\t\t; {:s}".format(stmt.name, stmt.lineref))
elif isinstance(stmt, (InplaceIncrStmt, InplaceDecrStmt)):
self.generate_incr_or_decr(stmt)
elif isinstance(stmt, CallStmt):
self.generate_call(stmt)
elif isinstance(stmt, InlineAsm):
self.p("\t\t; inline asm, " + stmt.lineref)
for line in stmt.asmlines:
self.p(line)
self.p("\t\t; end inline asm, " + stmt.lineref)
elif isinstance(stmt, Comment):
self.p(stmt.text)
elif isinstance(stmt, BreakpointStmt):
# put a marker in the source so that we can generate a list of breakpoints later
self.p("\t\tnop\t; {:s} {:s}".format(self.BREAKPOINT_COMMENT_SIGNATURE, stmt.lineref))
else:
raise CodeError("unknown statement " + repr(stmt))
self.previous_stmt_was_assignment = isinstance(stmt, AssignmentStmt)
def generate_incr_or_decr(self, stmt: Union[InplaceIncrStmt, InplaceDecrStmt]) -> None:
assert stmt.value.constant
assert (stmt.value.value is None and stmt.value.name) or stmt.value.value > 0
if stmt.what.datatype != DataType.FLOAT and not stmt.value.name and stmt.value.value > 0xff:
raise CodeError("only supports integer incr/decr by up to 255 for now") # XXX
is_incr = isinstance(stmt, InplaceIncrStmt)
howmuch = stmt.value.value
value_str = stmt.value.name or str(howmuch)
if isinstance(stmt.what, RegisterValue):
reg = stmt.what.register
# note: these operations below are all checked to be ok
if is_incr:
if reg == 'A':
# a += 1..255
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
elif reg in REGISTER_BYTES:
if howmuch == 1:
# x/y += 1
self.p("\t\tin{:s}".format(reg.lower()))
else:
# x/y += 2..255
with self.preserving_registers({'A'}):
self.p("\t\tt{:s}a".format(reg.lower()))
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
self.p("\t\tta{:s}".format(reg.lower()))
elif reg == "AX":
# AX += 1..255
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
self.p("\t\tbcc +")
self.p("\t\tinx")
self.p("+")
elif reg == "AY":
# AY += 1..255
self.p("\t\tclc")
self.p("\t\tadc # " + value_str)
self.p("\t\tbcc +")
self.p("\t\tiny")
self.p("+")
elif reg == "XY":
if howmuch == 1:
# XY += 1
self.p("\t\tinx")
self.p("\t\tbne +")
self.p("\t\tiny")
self.p("+")
else:
# XY += 2..255
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
self.p("\t\ttax")
self.p("\t\tbcc +")
self.p("\t\tiny")
self.p("+")
else:
raise CodeError("invalid incr register: " + reg)
else:
if reg == 'A':
# a -= 1..255
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
elif reg in REGISTER_BYTES:
if howmuch == 1:
# x/y -= 1
self.p("\t\tde{:s}".format(reg.lower()))
else:
# x/y -= 2..255
with self.preserving_registers({'A'}):
self.p("\t\tt{:s}a".format(reg.lower()))
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\tta{:s}".format(reg.lower()))
elif reg == "AX":
# AX -= 1..255
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\tbcs +")
self.p("\t\tdex")
self.p("+")
elif reg == "AY":
# AY -= 1..255
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\tbcs +")
self.p("\t\tdey")
self.p("+")
elif reg == "XY":
if howmuch == 1:
# XY -= 1
self.p("\t\tcpx #0")
self.p("\t\tbne +")
self.p("\t\tdey")
self.p("+\t\tdex")
else:
# XY -= 2..255
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\ttax")
self.p("\t\tbcs +")
self.p("\t\tdey")
self.p("+")
else:
raise CodeError("invalid decr register: " + reg)
elif isinstance(stmt.what, (MemMappedValue, IndirectValue)):
what = stmt.what
if isinstance(what, IndirectValue):
if isinstance(what.value, IntegerValue):
what_str = what.value.name or Parser.to_hex(what.value.value)
else:
raise CodeError("invalid incr indirect type", what.value)
else:
what_str = what.name or Parser.to_hex(what.address)
if what.datatype == DataType.BYTE:
if howmuch == 1:
self.p("\t\t{:s} {:s}".format("inc" if is_incr else "dec", what_str))
else:
with self.preserving_registers({'A'}):
self.p("\t\tlda " + what_str)
if is_incr:
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
else:
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\tsta " + what_str)
elif what.datatype == DataType.WORD:
if howmuch == 1:
# mem.word +=/-= 1
if is_incr:
self.p("\t\tinc " + what_str)
self.p("\t\tbne +")
self.p("\t\tinc {:s}+1".format(what_str))
self.p("+")
else:
with self.preserving_registers({'A'}):
self.p("\t\tlda " + what_str)
self.p("\t\tbne +")
self.p("\t\tdec {:s}+1".format(what_str))
self.p("+\t\tdec " + what_str)
else:
# mem.word +=/-= 2..255
if is_incr:
with self.preserving_registers({'A'}):
self.p("\t\tclc")
self.p("\t\tlda " + what_str)
self.p("\t\tadc #" + value_str)
self.p("\t\tsta " + what_str)
self.p("\t\tbcc +")
self.p("\t\tinc {:s}+1".format(what_str))
self.p("+")
else:
with self.preserving_registers({'A'}):
self.p("\t\tsec")
self.p("\t\tlda " + what_str)
self.p("\t\tsbc #" + value_str)
self.p("\t\tsta " + what_str)
self.p("\t\tbcs +")
self.p("\t\tdec {:s}+1".format(what_str))
self.p("+")
elif what.datatype == DataType.FLOAT:
if howmuch == 1.0:
# special case for +/-1
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\tldx #<" + what_str)
self.p("\t\tldy #>" + what_str)
if is_incr:
self.p("\t\tjsr c64flt.float_add_one")
else:
self.p("\t\tjsr c64flt.float_sub_one")
elif stmt.value.name:
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\tlda #<" + stmt.value.name)
self.p("\t\tsta c64.SCRATCH_ZPWORD1")
self.p("\t\tlda #>" + stmt.value.name)
self.p("\t\tsta c64.SCRATCH_ZPWORD1+1")
self.p("\t\tldx #<" + what_str)
self.p("\t\tldy #>" + what_str)
if is_incr:
self.p("\t\tjsr c64flt.float_add_SW1_to_XY")
else:
self.p("\t\tjsr c64flt.float_sub_SW1_from_XY")
else:
raise CodeError("incr/decr missing float constant definition")
else:
raise CodeError("cannot in/decrement memory of type " + str(what.datatype), howmuch)
else:
raise CodeError("cannot in/decrement " + str(stmt.what))
def generate_call(self, stmt: CallStmt) -> None:
self.p("\t\t\t\t\t; " + stmt.lineref)
if stmt.condition:
assert stmt.is_goto
if stmt.condition.lvalue:
if stmt.condition.comparison_op:
self._generate_goto_conditional_comparison(stmt)
else:
self._generate_goto_conditional_truthvalue(stmt)
else:
self._generate_goto_conditional_if(stmt)
else:
# unconditional goto or subroutine call.
def branch_emitter(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
if is_goto:
if target_indirect:
self.p("\t\tjmp ({:s})".format(targetstr))
else:
self.p("\t\tjmp {:s}".format(targetstr))
else:
assert not target_indirect
self.p("\t\tjsr " + targetstr)
self._generate_call_or_goto(stmt, branch_emitter)
def _generate_goto_conditional_if(self, stmt):
# a goto with just an if-condition, no condition expression
def branch_emitter(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
assert is_goto and not stmt.condition.comparison_op
ifs = stmt.condition.ifstatus
if target_indirect:
if ifs == "true":
self.p("\t\tbeq +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs in ("not", "zero"):
self.p("\t\tbne +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs in ("cc", "cs", "vc", "vs", "eq", "ne"):
if ifs == "cc":
self.p("\t\tbcs +")
elif ifs == "cs":
self.p("\t\tbcc +")
elif ifs == "vc":
self.p("\t\tbvs +")
elif ifs == "vs":
self.p("\t\tbvc +")
elif ifs == "eq":
self.p("\t\tbne +")
elif ifs == "ne":
self.p("\t\tbeq +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs == "lt":
self.p("\t\tbcs +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs == "gt":
self.p("\t\tbcc +")
self.p("\t\tbeq +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs == "ge":
self.p("\t\tbcc +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs == "le":
self.p("\t\tbeq +")
self.p("\t\tbcs ++")
self.p("+\t\tjmp ({:s})".format(targetstr))
self.p("+")
else:
raise CodeError("invalid if status " + ifs)
else:
if ifs == "true":
self.p("\t\tbne " + targetstr)
elif ifs in ("not", "zero"):
self.p("\t\tbeq " + targetstr)
elif ifs in ("cc", "cs", "vc", "vs", "eq", "ne"):
self.p("\t\tb{:s} {:s}".format(ifs, targetstr))
elif ifs == "pos":
self.p("\t\tbpl " + targetstr)
elif ifs == "neg":
self.p("\t\tbmi " + targetstr)
elif ifs == "lt":
self.p("\t\tbcc " + targetstr)
elif ifs == "gt":
self.p("\t\tbeq +")
self.p("\t\tbcs " + targetstr)
self.p("+")
elif ifs == "ge":
self.p("\t\tbcs " + targetstr)
elif ifs == "le":
self.p("\t\tbcc " + targetstr)
self.p("\t\tbeq " + targetstr)
else:
raise CodeError("invalid if status " + ifs)
self._generate_call_or_goto(stmt, branch_emitter)
def _generate_goto_conditional_truthvalue(self, stmt: CallStmt) -> None:
# the condition is just the 'truth value' of the single value,
# this is translated into assembly by comparing the argument to zero.
def branch_emitter_mmap(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
assert is_goto and not stmt.condition.comparison_op
assert stmt.condition.lvalue and not stmt.condition.rvalue
assert not target_indirect
assert stmt.condition.ifstatus in ("true", "not", "zero")
branch, inverse_branch = ("bne", "beq") if stmt.condition.ifstatus == "true" else ("beq", "bne")
cv = stmt.condition.lvalue
assert isinstance(cv, MemMappedValue)
cv_str = cv.name or Parser.to_hex(cv.address)
if cv.datatype == DataType.BYTE:
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + cv_str)
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
elif cv.datatype == DataType.WORD:
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + cv_str)
if stmt.condition.ifstatus == "true":
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda {:s}+1".format(cv_str))
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
else:
self.p("\t\t{:s} +".format(inverse_branch, targetstr))
self.p("\t\tlda {:s}+1".format(cv_str))
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("+\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
else:
raise CodeError("conditions cannot yet use other types than byte or word", # @todo comparisons of other types
cv.datatype, str(cv), stmt.sourceref)
def branch_emitter_reg(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
assert is_goto and not stmt.condition.comparison_op
assert stmt.condition.lvalue and not stmt.condition.rvalue
assert not target_indirect
assert stmt.condition.ifstatus in ("true", "not", "zero")
branch, inverse_branch = ("bne", "beq") if stmt.condition.ifstatus == "true" else ("beq", "bne")
line_after_branch = ""
cv = stmt.condition.lvalue
assert isinstance(cv, RegisterValue)
if cv.register == 'A':
self.p("\t\tcmp #0")
elif cv.register == 'X':
self.p("\t\tcpx #0")
elif cv.register == 'Y':
self.p("\t\tcpy #0")
else:
if cv.register == 'AX':
line_after_branch = "+"
self.p("\t\tcmp #0")
self.p("\t\t{:s} {:s}".format(inverse_branch, line_after_branch))
self.p("\t\tcpx #0")
elif cv.register == 'AY':
line_after_branch = "+"
self.p("\t\tcmp #0")
self.p("\t\t{:s} {:s}".format(inverse_branch, line_after_branch))
self.p("\t\tcpy #0")
elif cv.register == 'XY':
line_after_branch = "+"
self.p("\t\tcpx #0")
self.p("\t\t{:s} {:s}".format(inverse_branch, line_after_branch))
self.p("\t\tcpy #0")
else:
raise CodeError("invalid register", cv.register)
self.p("\t\t{:s} {:s}".format(branch, targetstr))
if line_after_branch:
self.p(line_after_branch)
def branch_emitter_indirect_cond(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
assert is_goto and not stmt.condition.comparison_op
assert stmt.condition.lvalue and not stmt.condition.rvalue
assert stmt.condition.ifstatus in ("true", "not", "zero")
assert not target_indirect
cv = stmt.condition.lvalue.value # type: ignore
if isinstance(cv, RegisterValue):
branch = "bne" if stmt.condition.ifstatus == "true" else "beq"
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
if cv.register == 'Y':
self.p("\t\tlda ($00),y")
elif cv.register == 'X':
self.p("\t\tstx *+2\t; self-modify")
self.p("\t\tlda $ff")
elif cv.register == 'A':
self.p("\t\tsta *+2\t; self-modify")
self.p("\t\tlda $ff")
else:
self.p("\t\tst{:s} (+)+1\t; self-modify".format(cv.register[0].lower()))
self.p("\t\tst{:s} (+)+2\t; self-modify".format(cv.register[1].lower()))
self.p("+\t\tlda $ffff")
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
elif isinstance(cv, MemMappedValue):
raise CodeError("memmapped indirect should not occur, use the variable without indirection")
elif isinstance(cv, IntegerValue) and cv.constant:
branch, inverse_branch = ("bne", "beq") if stmt.condition.ifstatus == "true" else ("beq", "bne")
cv_str = cv.name or Parser.to_hex(cv.value)
if cv.datatype == DataType.BYTE:
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + cv_str)
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
elif cv.datatype == DataType.WORD:
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + cv_str)
if stmt.condition.ifstatus == "true":
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda {:s}+1".format(cv_str))
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
else:
self.p("\t\t{:s} +".format(inverse_branch, targetstr))
self.p("\t\tlda {:s}+1".format(cv_str))
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("+\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
else:
raise CodeError("conditions cannot yet use other types than byte or word", # @todo comparisons of other types
cv.datatype, str(cv), stmt.sourceref)
else:
raise CodeError("weird indirect type", str(cv))
cv = stmt.condition.lvalue
if isinstance(cv, RegisterValue):
self._generate_call_or_goto(stmt, branch_emitter_reg)
elif isinstance(cv, MemMappedValue):
self._generate_call_or_goto(stmt, branch_emitter_mmap)
elif isinstance(cv, IndirectValue):
if isinstance(cv.value, RegisterValue):
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
elif isinstance(cv.value, MemMappedValue):
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
elif isinstance(cv.value, IntegerValue) and cv.value.constant:
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
else:
raise CodeError("weird indirect type", str(cv))
else:
raise CodeError("need register, memmapped or indirect value", str(cv))
def _generate_goto_conditional_comparison(self, stmt: CallStmt) -> None:
# the condition is lvalue operator rvalue
raise NotImplementedError("no comparisons yet") # XXX comparisons
assert stmt.condition.ifstatus in ("true", "not", "zero")
assert stmt.condition.lvalue != stmt.condition.rvalue # so we know we actually have to compare different things
lv, compare_operator, rv = stmt.condition.lvalue, stmt.condition.comparison_op, stmt.condition.rvalue
if lv.constant and not rv.constant:
# if lv is a constant, swap the whole thing around so the constant is on the right
lv, compare_operator, rv = stmt.condition.swap()
if isinstance(rv, RegisterValue):
# if rv is a register, make sure it comes first instead
lv, compare_operator, rv = stmt.condition.swap()
if lv.datatype != DataType.BYTE or rv.datatype != DataType.BYTE:
raise CodeError("can only generate comparison code for byte values for now") # @todo compare non-bytes
if isinstance(lv, RegisterValue):
if isinstance(rv, RegisterValue):
self.p("\t\tst{:s} {:s}".format(rv.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
if lv.register == "A":
self.p("\t\tcmp " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lv.register == "X":
self.p("\t\tcpx " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lv.register == "Y":
self.p("\t\tcpy " + Parser.to_hex(Zeropage.SCRATCH_B1))
else:
raise CodeError("wrong lvalue register")
elif isinstance(rv, IntegerValue):
rvstr = rv.name or Parser.to_hex(rv.value)
if lv.register == "A":
self.p("\t\tcmp #" + rvstr)
elif lv.register == "X":
self.p("\t\tcpx #" + rvstr)
elif lv.register == "Y":
self.p("\t\tcpy #" + rvstr)
else:
raise CodeError("wrong lvalue register")
elif isinstance(rv, MemMappedValue):
rvstr = rv.name or Parser.to_hex(rv.address)
if lv.register == "A":
self.p("\t\tcmp " + rvstr)
elif lv.register == "X":
self.p("\t\tcpx #" + rvstr)
elif lv.register == "Y":
self.p("\t\tcpy #" + rvstr)
else:
raise CodeError("wrong lvalue register")
else:
raise CodeError("invalid rvalue type in comparison", rv)
elif isinstance(lv, MemMappedValue):
assert not isinstance(rv, RegisterValue), "registers as rvalue should have been swapped with lvalue"
if isinstance(rv, IntegerValue):
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + (lv.name or Parser.to_hex(lv.address)))
self.p("\t\tcmp #" + (rv.name or Parser.to_hex(rv.value)))
line_after_goto = "\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
elif isinstance(rv, MemMappedValue):
rvstr = rv.name or Parser.to_hex(rv.address)
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + (lv.name or Parser.to_hex(lv.address)))
self.p("\t\tcmp " + rvstr)
line_after_goto = "\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
else:
raise CodeError("invalid rvalue type in comparison", rv)
else:
raise CodeError("invalid lvalue type in comparison", lv)
def _generate_call_or_goto(self, stmt: CallStmt, branch_emitter: Callable[[str, bool, bool], None]) -> None:
def generate_param_assignments() -> None:
for assign_stmt in stmt.desugared_call_arguments:
self.generate_assignment(assign_stmt)
def generate_result_assignments() -> None:
for assign_stmt in stmt.desugared_output_assignments:
self.generate_assignment(assign_stmt)
def params_load_a() -> bool:
for assign_stmt in stmt.desugared_call_arguments:
for lv in assign_stmt.leftvalues:
if isinstance(lv, RegisterValue):
if lv.register == 'A':
return True
return False
def unclobber_result_registers(registers: Set[str], output_assignments: List[AssignmentStmt]) -> Set[str]:
result = registers.copy()
for a in output_assignments:
for lv in a.leftvalues:
if isinstance(lv, RegisterValue):
if len(lv.register) == 1:
result.discard(lv.register)
else:
for r in lv.register:
result.discard(r)
return result
if stmt.target.name:
symblock, targetdef = self.cur_block.lookup(stmt.target.name)
else:
symblock = None
targetdef = None
if isinstance(targetdef, SubroutineDef):
if isinstance(stmt.target, MemMappedValue):
targetstr = stmt.target.name or Parser.to_hex(stmt.address)
else:
raise CodeError("call sub target must be mmapped")
if stmt.is_goto:
generate_param_assignments()
branch_emitter(targetstr, True, False)
# no result assignments because it's a goto
return
clobbered = set() # type: Set[str]
if targetdef.clobbered_registers:
if stmt.preserve_regs is not None:
clobbered = targetdef.clobbered_registers & stmt.preserve_regs
clobbered = unclobber_result_registers(clobbered, stmt.desugared_output_assignments)
with self.preserving_registers(clobbered, loads_a_within=params_load_a(), always_preserve=stmt.preserve_regs is not None):
generate_param_assignments()
branch_emitter(targetstr, False, False)
generate_result_assignments()
return
if isinstance(stmt.target, IndirectValue):
if stmt.target.name:
targetstr = stmt.target.name
elif stmt.address is not None:
targetstr = Parser.to_hex(stmt.address)
elif stmt.target.value.name:
targetstr = stmt.target.value.name
elif isinstance(stmt.target.value, RegisterValue):
targetstr = stmt.target.value.register
elif isinstance(stmt.target.value, IntegerValue):
targetstr = stmt.target.value.name or Parser.to_hex(stmt.target.value.value)
else:
raise CodeError("missing name", stmt.target.value)
if stmt.is_goto:
# no need to preserve registers for a goto
generate_param_assignments()
if targetstr in REGISTER_WORDS:
self.p("\t\tst{:s} {:s}".format(targetstr[0].lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tst{:s} {:s}".format(targetstr[1].lower(), Parser.to_hex(Zeropage.SCRATCH_B2)))
branch_emitter(Parser.to_hex(Zeropage.SCRATCH_B1), True, True)
else:
branch_emitter(targetstr, True, True)
# no result assignments because it's a goto
else:
# indirect call to subroutine
preserve_regs = unclobber_result_registers(stmt.preserve_regs or set(), stmt.desugared_output_assignments)
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a(),
always_preserve=stmt.preserve_regs is not None):
generate_param_assignments()
if targetstr in REGISTER_WORDS:
print("warning: {}: indirect register pair call is quite inefficient, use a jump table in memory instead?"
.format(stmt.sourceref))
if stmt.preserve_regs is not None:
# cannot use zp scratch because it may be used by the register backup. This is very inefficient code!
self.p("\t\tjsr il65_lib.jsr_indirect_nozpuse_"+targetstr)
else:
self.p("\t\tjsr il65_lib.jsr_indirect_"+targetstr)
else:
self.p("\t\tjsr +")
self.p("\t\tjmp ++")
self.p("+\t\tjmp ({:s})".format(targetstr))
self.p("+")
generate_result_assignments()
else:
# call to a label or immediate address
if stmt.target.name:
targetstr = stmt.target.name
elif stmt.address is not None:
targetstr = Parser.to_hex(stmt.address)
elif isinstance(stmt.target, IntegerValue):
targetstr = stmt.target.name or Parser.to_hex(stmt.target.value)
else:
raise CodeError("missing name", stmt.target)
if stmt.is_goto:
# no need to preserve registers for a goto
generate_param_assignments()
branch_emitter(targetstr, True, False)
# no result assignments because it's a goto
else:
preserve_regs = unclobber_result_registers(stmt.preserve_regs or set(), stmt.desugared_output_assignments)
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a(),
always_preserve=stmt.preserve_regs is not None):
generate_param_assignments()
branch_emitter(targetstr, False, False)
generate_result_assignments()
def generate_augmented_assignment(self, stmt: AugmentedAssignmentStmt) -> None:
# for instance: value += 3
lvalue = stmt.leftvalues[0]
rvalue = stmt.right
self.p("\t\t\t\t\t; " + stmt.lineref)
if isinstance(lvalue, RegisterValue):
if isinstance(rvalue, IntegerValue):
self._generate_aug_reg_int(lvalue, stmt.operator, rvalue)
elif isinstance(rvalue, RegisterValue):
self._generate_aug_reg_reg(lvalue, stmt.operator, rvalue)
elif isinstance(rvalue, MemMappedValue):
self._generate_aug_reg_mem(lvalue, stmt.operator, rvalue)
else:
raise CodeError("invalid rvalue for augmented assignment on register", str(rvalue))
else:
raise CodeError("augmented assignment only implemented for registers for now", str(rvalue)) # XXX
def _generate_aug_reg_mem(self, lvalue: RegisterValue, operator: str, rvalue: MemMappedValue) -> None:
r_str = rvalue.name or Parser.to_hex(rvalue.address)
if operator == "+=":
if lvalue.register == "A":
self.p("\t\tclc")
self.p("\t\tadc " + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tclc")
self.p("\t\tadc " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
if lvalue.register == "A":
self.p("\t\tsec")
self.p("\t\tsbc " + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tsec")
self.p("\t\tsbc " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=":
if lvalue.register == "A":
self.p("\t\tand " + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tand " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tand " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=":
if lvalue.register == "A":
self.p("\t\tora " + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tora " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tora " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=":
if lvalue.register == "A":
self.p("\t\teor " + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\teor " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\teor " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
if rvalue.datatype != DataType.BYTE:
raise CodeError("can only shift by a byte value", str(rvalue))
r_str = rvalue.name or Parser.to_hex(rvalue.address)
if lvalue.register == "A":
self.p("\t\tlsr " + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tlsr " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tlsr " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for shift", str(lvalue)) # @todo >>=.word
elif operator == "<<=":
if rvalue.datatype != DataType.BYTE:
raise CodeError("can only shift by a byte value", str(rvalue))
r_str = rvalue.name or Parser.to_hex(rvalue.address)
if lvalue.register == "A":
self.p("\t\tasl " + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tasl " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tasl " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for shift", str(lvalue)) # @todo >>=.word
def _generate_aug_reg_int(self, lvalue: RegisterValue, operator: str, rvalue: IntegerValue) -> None:
r_str = rvalue.name or Parser.to_hex(rvalue.value)
if operator == "+=":
if lvalue.register == "A":
self.p("\t\tclc")
self.p("\t\tadc #" + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tclc")
self.p("\t\tadc #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
if lvalue.register == "A":
self.p("\t\tsec")
self.p("\t\tsbc #" + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tsec")
self.p("\t\tsbc #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=":
if lvalue.register == "A":
self.p("\t\tand #" + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tand #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tand #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=":
if lvalue.register == "A":
self.p("\t\tora #" + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tora #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tora #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=":
if lvalue.register == "A":
self.p("\t\teor #" + r_str)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\teor #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\teor #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
if rvalue.value > 0:
def shifts_A(times: int) -> None:
if times >= 8:
self.p("\t\tlda #0")
else:
for _ in range(min(8, times)):
self.p("\t\tlsr a")
if lvalue.register == "A":
shifts_A(rvalue.value)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
shifts_A(rvalue.value)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
shifts_A(rvalue.value)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo >>=.word
elif operator == "<<=":
if rvalue.value > 0:
def shifts_A(times: int) -> None:
if times >= 8:
self.p("\t\tlda #0")
else:
for _ in range(min(8, times)):
self.p("\t\tasl a")
if lvalue.register == "A":
shifts_A(rvalue.value)
elif lvalue.register == "X":
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
shifts_A(rvalue.value)
self.p("\t\ttax")
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
shifts_A(rvalue.value)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo <<=.word
def _generate_aug_reg_reg(self, lvalue: RegisterValue, operator: str, rvalue: RegisterValue) -> None:
if operator == "+=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo +=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tclc")
self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tclc")
self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo -=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tsec")
self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tsec")
self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo &=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo |=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo ^=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo >>=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo >>=.word
elif operator == "<<=":
if rvalue.register not in REGISTER_BYTES:
raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo <<=.word
if lvalue.register == "A":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo <<=.word
def generate_assignment(self, stmt: AssignmentStmt) -> None:
def unwrap_indirect(iv: IndirectValue) -> MemMappedValue:
if isinstance(iv.value, MemMappedValue):
return iv.value
elif iv.value.constant and isinstance(iv.value, IntegerValue):
return MemMappedValue(iv.value.value, iv.datatype, 1, stmt.sourceref, iv.name)
else:
raise CodeError("cannot yet generate code for assignment: non-constant and non-memmapped indirect") # XXX
rvalue = stmt.right
if isinstance(rvalue, IndirectValue):
rvalue = unwrap_indirect(rvalue)
self.p("\t\t\t\t\t; " + stmt.lineref)
if isinstance(rvalue, IntegerValue):
for lv in stmt.leftvalues:
if isinstance(lv, RegisterValue):
self.generate_assign_integer_to_reg(lv.register, rvalue)
elif isinstance(lv, MemMappedValue):
self.generate_assign_integer_to_mem(lv, rvalue)
elif isinstance(lv, IndirectValue):
lv = unwrap_indirect(lv)
self.generate_assign_integer_to_mem(lv, rvalue)
else:
raise CodeError("invalid assignment target (1)", str(stmt))
elif isinstance(rvalue, RegisterValue):
for lv in stmt.leftvalues:
if isinstance(lv, RegisterValue):
self.generate_assign_reg_to_reg(lv, rvalue.register)
elif isinstance(lv, MemMappedValue):
self.generate_assign_reg_to_memory(lv, rvalue.register)
elif isinstance(lv, IndirectValue):
lv = unwrap_indirect(lv)
self.generate_assign_reg_to_memory(lv, rvalue.register)
else:
raise CodeError("invalid assignment target (2)", str(stmt))
elif isinstance(rvalue, StringValue):
r_str = self.output_string(rvalue.value, True)
for lv in stmt.leftvalues:
if isinstance(lv, RegisterValue):
if len(rvalue.value) == 1:
self.generate_assign_char_to_reg(lv, r_str)
else:
self.generate_assign_string_to_reg(lv, rvalue)
elif isinstance(lv, MemMappedValue):
if len(rvalue.value) == 1:
self.generate_assign_char_to_memory(lv, r_str)
else:
self.generate_assign_string_to_memory(lv, rvalue)
elif isinstance(lv, IndirectValue):
lv = unwrap_indirect(lv)
if len(rvalue.value) == 1:
self.generate_assign_char_to_memory(lv, r_str)
else:
self.generate_assign_string_to_memory(lv, rvalue)
else:
raise CodeError("invalid assignment target (2)", str(stmt))
elif isinstance(rvalue, MemMappedValue):
for lv in stmt.leftvalues:
if isinstance(lv, RegisterValue):
self.generate_assign_mem_to_reg(lv.register, rvalue)
elif isinstance(lv, MemMappedValue):
self.generate_assign_mem_to_mem(lv, rvalue)
elif isinstance(lv, IndirectValue):
lv = unwrap_indirect(lv)
self.generate_assign_mem_to_mem(lv, rvalue)
else:
raise CodeError("invalid assignment target (4)", str(stmt))
elif isinstance(rvalue, FloatValue):
for lv in stmt.leftvalues:
if isinstance(lv, MemMappedValue) and lv.datatype == DataType.FLOAT:
self.generate_assign_float_to_mem(lv, rvalue)
elif isinstance(lv, IndirectValue):
lv = unwrap_indirect(lv)
assert lv.datatype == DataType.FLOAT
self.generate_assign_float_to_mem(lv, rvalue)
else:
raise CodeError("cannot assign float to ", str(lv))
else:
raise CodeError("invalid assignment value type", str(stmt))
def generate_assign_float_to_mem(self, mmv: MemMappedValue,
rvalue: Union[FloatValue, IntegerValue]) -> None:
floatvalue = float(rvalue.value)
mflpt = self.to_mflpt5(floatvalue)
target = mmv.name or Parser.to_hex(mmv.address)
with self.preserving_registers({'A'}):
self.p("\t\t\t\t\t; {:s} = {}".format(target, rvalue.name or floatvalue))
a_reg_value = None
for i, byte in enumerate(mflpt):
if byte != a_reg_value:
self.p("\t\tlda #${:02x}".format(byte))
a_reg_value = byte
self.p("\t\tsta {:s}+{:d}".format(target, i))
def generate_assign_reg_to_memory(self, lv: MemMappedValue, r_register: str) -> None:
# Memory = Register
lv_string = lv.name or Parser.to_hex(lv.address)
if lv.datatype == DataType.BYTE:
if len(r_register) > 1:
raise CodeError("cannot assign register pair to single byte memory")
self.p("\t\tst{:s} {}".format(r_register.lower(), lv_string))
elif lv.datatype == DataType.WORD:
if len(r_register) == 1:
self.p("\t\tst{:s} {}".format(r_register.lower(), lv_string)) # lsb
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda #0")
self.p("\t\tsta {:s}+1".format(lv_string)) # msb
else:
self.p("\t\tst{:s} {}".format(r_register[0].lower(), lv_string))
self.p("\t\tst{:s} {}+1".format(r_register[1].lower(), lv_string))
elif lv.datatype == DataType.FLOAT:
# assigning a register to a float requires c64 ROM routines
if r_register in REGISTER_WORDS:
def do_rom_calls():
self.p("\t\tjsr c64flt.GIVUAYF") # uword AY -> fac1
self.p("\t\tldx #<" + lv_string)
self.p("\t\tldy #>" + lv_string)
self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY
if r_register == "AY":
with self.preserving_registers({'A', 'X', 'Y'}):
do_rom_calls()
elif r_register == "AX":
with self.preserving_registers({'A', 'X', 'Y'}):
self.p("\t\tpha\n\t\ttxa\n\t\ttay\n\t\tpla") # X->Y (so we have AY now)
do_rom_calls()
else: # XY
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\ttxa") # X->A (so we have AY now)
do_rom_calls()
elif r_register in "AXY":
def do_rom_calls():
self.p("\t\tjsr c64.FREADUY") # ubyte Y -> fac1
self.p("\t\tldx #<" + lv_string)
self.p("\t\tldy #>" + lv_string)
self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY
if r_register == "A":
with self.preserving_registers({'A', 'X', 'Y'}):
self.p("\t\ttay")
do_rom_calls()
elif r_register == "X":
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\ttxa")
self.p("\t\ttay")
do_rom_calls()
elif r_register == "Y":
with self.preserving_registers({'A', 'X', 'Y'}):
do_rom_calls()
else:
raise CodeError("invalid register to assign to float", r_register)
else:
raise CodeError("invalid lvalue type", lv.datatype)
def generate_assign_reg_to_reg(self, lv: RegisterValue, r_register: str) -> None:
if lv.register != r_register:
if lv.register == 'A': # x/y -> a
self.p("\t\tt{:s}a".format(r_register.lower()))
elif lv.register == 'Y':
if r_register == 'A':
# a -> y
self.p("\t\ttay")
else:
# x -> y, 6502 doesn't have txy
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == 'X':
if r_register == 'A':
# a -> x
self.p("\t\ttax")
else:
# y -> x, 6502 doesn't have tyx
self.p("\t\tsty ${0:02x}\n\t\tldx ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register in REGISTER_WORDS:
if len(r_register) == 1:
# assign one register to a pair, so the hi byte is zero.
if lv.register == "AX" and r_register == "A":
self.p("\t\tldx #0")
elif lv.register == "AX" and r_register == "X":
self.p("\t\ttxa\n\t\tldx #0")
elif lv.register == "AX" and r_register == "Y":
self.p("\t\ttya\n\t\tldx #0")
elif lv.register == "AY" and r_register == "A":
self.p("\t\tldy #0")
elif lv.register == "AY" and r_register == "X":
self.p("\t\ttxa\n\t\tldy #0")
elif lv.register == "AY" and r_register == "Y":
self.p("\t\ttya\n\t\tldy #0")
elif lv.register == "XY" and r_register == "A":
self.p("\t\ttax\n\t\tldy #0")
elif lv.register == "XY" and r_register == "X":
self.p("\t\tldy #0")
elif lv.register == "XY" and r_register == "Y":
self.p("\t\ttyx\n\t\tldy #0")
else:
raise CodeError("invalid register combination", lv.register, r_register)
elif lv.register == "AX" and r_register == "AY":
# y -> x, 6502 doesn't have tyx
self.p("\t\tsty ${0:02x}\n\t\tldx ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == "AX" and r_register == "XY":
# x -> a, y -> x, 6502 doesn't have tyx
self.p("\t\ttxa")
self.p("\t\tsty ${0:02x}\n\t\tldx ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == "AY" and r_register == "AX":
# x -> y, 6502 doesn't have txy
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == "AY" and r_register == "XY":
# x -> a
self.p("\t\ttxa")
elif lv.register == "XY" and r_register == "AX":
# x -> y, a -> x, 6502 doesn't have txy
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lv.register == "XY" and r_register == "AY":
# a -> x
self.p("\t\ttax")
else:
raise CodeError("invalid register combination", lv.register, r_register)
else:
raise CodeError("invalid register " + lv.register)
@contextlib.contextmanager
def preserving_registers(self, registers: Set[str], loads_a_within: bool=False, always_preserve: bool=False):
# this sometimes clobbers a ZP scratch register and is therefore NOT safe to use in interrupts
# see http://6502.org/tutorials/register_preservation.html
if not self.cur_block.preserve_registers and not always_preserve:
yield
return
if registers == {'A'}:
self.p("\t\tpha")
yield
self.p("\t\tpla")
elif registers:
if not loads_a_within:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
if 'A' in registers:
self.p("\t\tpha")
if 'X' in registers:
self.p("\t\ttxa\n\t\tpha")
if 'Y' in registers:
self.p("\t\ttya\n\t\tpha")
if not loads_a_within:
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
yield
if 'X' in registers and 'Y' in registers:
if 'A' not in registers:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
self.p("\t\tpla\n\t\ttay")
self.p("\t\tpla\n\t\ttax")
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
else:
self.p("\t\tpla\n\t\ttay")
self.p("\t\tpla\n\t\ttax")
else:
if 'Y' in registers:
if 'A' not in registers:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
self.p("\t\tpla\n\t\ttay")
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
else:
self.p("\t\tpla\n\t\ttay")
if 'X' in registers:
if 'A' not in registers:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
self.p("\t\tpla\n\t\ttax")
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
else:
self.p("\t\tpla\n\t\ttax")
if 'A' in registers:
self.p("\t\tpla")
else:
yield
def generate_assign_integer_to_mem(self, lv: MemMappedValue, rvalue: IntegerValue) -> None:
if lv.name:
symblock, sym = self.cur_block.lookup(lv.name)
if not isinstance(sym, VariableDef):
raise CodeError("invalid lvalue type " + str(sym))
assign_target = symblock.label + '.' + sym.name if symblock is not self.cur_block else lv.name
lvdatatype = sym.type
else:
assign_target = Parser.to_hex(lv.address)
lvdatatype = lv.datatype
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.value)
if lvdatatype == DataType.BYTE:
if rvalue.value is not None and not lv.assignable_from(rvalue) or rvalue.datatype != DataType.BYTE:
raise OverflowError("value doesn't fit in a byte")
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda #" + r_str)
self.p("\t\tsta " + assign_target)
elif lvdatatype == DataType.WORD:
if rvalue.value is not None and not lv.assignable_from(rvalue):
raise OverflowError("value doesn't fit in a word")
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda #<" + r_str)
self.p("\t\tsta " + assign_target)
self.p("\t\tlda #>" + r_str)
self.p("\t\tsta {}+1".format(assign_target))
elif lvdatatype == DataType.FLOAT:
if rvalue.value is not None and not DataType.FLOAT.assignable_from_value(rvalue.value):
raise CodeError("value cannot be assigned to a float")
self.generate_assign_float_to_mem(lv, rvalue)
else:
raise CodeError("invalid lvalue type " + str(lvdatatype))
def generate_assign_mem_to_reg(self, l_register: str, rvalue: MemMappedValue) -> None:
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.address)
if len(l_register) == 1:
if rvalue.datatype != DataType.BYTE:
raise CodeError("can only assign a byte to a register")
self.p("\t\tld{:s} {:s}".format(l_register.lower(), r_str))
else:
if rvalue.datatype == DataType.BYTE:
self.p("\t\tld{:s} {:s}".format(l_register[0].lower(), r_str))
self.p("\t\tld{:s} #0".format(l_register[1].lower()))
elif rvalue.datatype == DataType.WORD:
self.p("\t\tld{:s} {:s}".format(l_register[0].lower(), r_str))
self.p("\t\tld{:s} {:s}+1".format(l_register[1].lower(), r_str))
else:
raise CodeError("can only assign a byte or word to a register pair")
def generate_assign_mem_to_mem(self, lv: MemMappedValue, rvalue: MemMappedValue) -> None:
r_str = rvalue.name or Parser.to_hex(rvalue.address)
l_str = lv.name or Parser.to_hex(lv.address)
if lv.datatype == DataType.BYTE:
if rvalue.datatype != DataType.BYTE:
raise CodeError("can only assign a byte to a byte", str(rvalue))
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda " + r_str)
self.p("\t\tsta " + l_str)
elif lv.datatype == DataType.WORD:
if rvalue.datatype == DataType.BYTE:
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda " + r_str)
self.p("\t\tsta " + l_str)
self.p("\t\tlda #0")
self.p("\t\tsta {:s}+1".format(l_str))
elif rvalue.datatype == DataType.WORD:
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda {:s}".format(r_str))
self.p("\t\tsta {:s}".format(l_str))
self.p("\t\tlda {:s}+1".format(r_str))
self.p("\t\tsta {:s}+1".format(l_str))
else:
raise CodeError("can only assign a byte or word to a word", str(rvalue))
elif lv.datatype == DataType.FLOAT:
if rvalue.datatype == DataType.FLOAT:
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\tlda #<" + r_str)
self.p("\t\tsta c64.SCRATCH_ZPWORD1")
self.p("\t\tlda #>" + r_str)
self.p("\t\tsta c64.SCRATCH_ZPWORD1+1")
self.p("\t\tldx #<" + l_str)
self.p("\t\tldy #>" + l_str)
self.p("\t\tjsr c64flt.copy_mflt")
elif rvalue.datatype == DataType.BYTE:
with self.preserving_registers({'A', 'X', 'Y'}):
self.p("\t\tldy " + r_str)
self.p("\t\tjsr c64.FREADUY") # ubyte Y -> fac1
self.p("\t\tldx #<" + l_str)
self.p("\t\tldy #>" + l_str)
self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY
elif rvalue.datatype == DataType.WORD:
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\tlda " + r_str)
self.p("\t\tldy {:s}+1".format(r_str))
self.p("\t\tjsr c64flt.GIVUAYF") # uword AY -> fac1
self.p("\t\tldx #<" + l_str)
self.p("\t\tldy #>" + l_str)
self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY
else:
raise CodeError("unsupported rvalue to memfloat", str(rvalue))
else:
raise CodeError("invalid lvalue memmapped datatype", str(lv))
def generate_assign_char_to_memory(self, lv: MemMappedValue, char_str: str) -> None:
# Memory = Character
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda #" + char_str)
if not lv.name:
self.p("\t\tsta " + Parser.to_hex(lv.address))
return
# assign char value to a memory location by symbol name
symblock, sym = self.cur_block.lookup(lv.name)
if isinstance(sym, VariableDef):
assign_target = lv.name
if symblock is not self.cur_block:
assign_target = symblock.label + '.' + sym.name
if sym.type == DataType.BYTE:
self.p("\t\tsta " + assign_target)
elif sym.type == DataType.WORD:
self.p("\t\tsta " + assign_target)
self.p("\t\tlda #0")
self.p("\t\tsta {}+1".format(assign_target))
else:
raise CodeError("invalid lvalue type " + str(sym))
else:
raise CodeError("invalid lvalue type " + str(sym))
def generate_assign_integer_to_reg(self, l_register: str, rvalue: IntegerValue) -> None:
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.value)
if l_register in ('A', 'X', 'Y'):
self.p("\t\tld{:s} #{:s}".format(l_register.lower(), r_str))
elif l_register in REGISTER_WORDS:
self.p("\t\tld{:s} #<{:s}".format(l_register[0].lower(), r_str))
self.p("\t\tld{:s} #>{:s}".format(l_register[1].lower(), r_str))
elif l_register == "SC":
# set/clear S carry bit
if rvalue.value:
self.p("\t\tsec")
else:
self.p("\t\tclc")
elif l_register == "SI":
# interrupt disable bit
if rvalue.value:
self.p("\t\tsei")
else:
self.p("\t\tcli")
else:
raise CodeError("invalid register in immediate integer assignment", l_register, rvalue.value)
def generate_assign_char_to_reg(self, lv: RegisterValue, char_str: str) -> None:
# Register = Char (string of length 1)
if lv.register not in ('A', 'X', 'Y'):
raise CodeError("invalid register for char assignment", lv.register)
self.p("\t\tld{:s} #{:s}".format(lv.register.lower(), char_str))
def generate_assign_string_to_reg(self, lv: RegisterValue, rvalue: StringValue) -> None:
if lv.register not in ("AX", "AY", "XY"):
raise CodeError("need register pair AX, AY or XY for string address assignment", lv.register)
if rvalue.name:
self.p("\t\tld{:s} #<{:s}".format(lv.register[0].lower(), rvalue.name))
self.p("\t\tld{:s} #>{:s}".format(lv.register[1].lower(), rvalue.name))
else:
raise CodeError("cannot assign immediate string, it must be a string variable")
def generate_assign_string_to_memory(self, lv: MemMappedValue, rvalue: StringValue) -> None:
if lv.datatype != DataType.WORD:
raise CodeError("need word memory type for string address assignment")
if rvalue.name:
assign_target = lv.name if lv.name else Parser.to_hex(lv.address)
self.p("\t\tlda #<{:s}".format(rvalue.name))
self.p("\t\tsta " + assign_target)
self.p("\t\tlda #>{:s}".format(rvalue.name))
self.p("\t\tsta {}+1".format(assign_target))
else:
raise CodeError("cannot assign immediate string, it must be a string variable")
def footer(self) -> None:
self.p("\n\n.end")
def output_string(self, value: str, screencodes: bool = False) -> str:
if len(value) == 1 and screencodes:
if value[0].isprintable() and ord(value[0]) < 128:
return "'{:s}'".format(value[0])
else:
return str(ord(value[0]))
result = '"'
for char in value:
if char in "{}":
result += '", {:d}, "'.format(ord(char))
elif char.isprintable() and ord(char) < 128:
result += char
else:
if screencodes:
result += '", {:d}, "'.format(ord(char))
else:
if char == '\f':
result += "{clear}"
elif char == '\b':
result += "{delete}"
elif char == '\n':
result += "{cr}"
elif char == '\r':
result += "{down}"
elif char == '\t':
result += "{tab}"
else:
result += '", {:d}, "'.format(ord(char))
return result + '"'
class Assembler64Tass:
def __init__(self, format: ProgramFormat) -> None:
self.format = format
def assemble(self, inputfilename: str, outputfilename: str) -> None:
args = ["64tass", "--ascii", "--case-sensitive", "-Wall", "-Wno-strict-bool",
"--dump-labels", "--vice-labels", "-l", outputfilename+".vice-mon-list",
"-L", outputfilename+".final-asm", "--no-monitor", "--output", outputfilename, inputfilename]
if self.format == ProgramFormat.PRG:
args.append("--cbm-prg")
elif self.format == ProgramFormat.RAW:
args.append("--nostart")
else:
raise ValueError("don't know how to create format "+str(self.format))
try:
if self.format == ProgramFormat.PRG:
print("\ncreating C-64 prg")
elif self.format == ProgramFormat.RAW:
print("\ncreating raw binary")
try:
subprocess.check_call(args)
except FileNotFoundError as x:
raise SystemExit("ERROR: cannot run assembler program: "+str(x))
except subprocess.CalledProcessError as x:
raise SystemExit("assembler failed with returncode " + str(x.returncode))
def generate_breakpoint_list(self, program_filename: str) -> str:
breakpoints = []
with open(program_filename + ".final-asm", "rU") as f:
for line in f:
match = re.fullmatch(CodeGenerator.BREAKPOINT_COMMENT_DETECTOR, line, re.DOTALL)
if match:
breakpoints.append("$" + match.group("address"))
cmdfile = program_filename + ".vice-mon-list"
with open(cmdfile, "at") as f:
print("; vice monitor breakpoint list now follows", file=f)
print("; {:d} breakpoints have been defined here".format(len(breakpoints)), file=f)
print("del", file=f)
for b in breakpoints:
print("break", b, file=f)
return cmdfile