prog8/il65/handwritten/codegen.py

1949 lines
99 KiB
Python
Raw Normal View History

2017-12-21 13:52:30 +00:00
"""
2017-12-25 15:00:25 +00:00
Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the assembly code generator (from the parse tree)
2017-12-21 13:52:30 +00:00
2018-01-08 02:31:23 +00:00
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
2017-12-21 13:52:30 +00:00
"""
import io
2017-12-27 22:45:22 +00:00
import re
2017-12-21 13:52:30 +00:00
import datetime
import subprocess
import contextlib
from functools import partial
2018-01-01 22:46:33 +00:00
from typing import TextIO, Callable
from .parse import ProgramFormat, ParseResult, Parser
2018-01-01 22:46:33 +00:00
from .symbols import *
2017-12-21 13:52:30 +00:00
class CodeError(Exception):
pass
class CodeGenerator:
2017-12-27 22:45:22 +00:00
BREAKPOINT_COMMENT_SIGNATURE = "~~~BREAKPOINT~~~"
BREAKPOINT_COMMENT_DETECTOR = r".(?P<address>\w+)\s+ea\s+nop\s+;\s+{:s}.*".format(BREAKPOINT_COMMENT_SIGNATURE)
2017-12-21 13:52:30 +00:00
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
2017-12-31 03:45:27 +00:00
self.cur_block = None # type: Block
2017-12-21 13:52:30 +00:00
def generate(self) -> None:
print("\ngenerating assembly code")
2017-12-21 13:52:30 +00:00
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")
2017-12-21 22:05:35 +00:00
# can only contain code comments, or nothing at all
2017-12-31 03:45:27 +00:00
if not all(isinstance(s, Comment) for s in zpblock.statements):
2017-12-21 22:05:35 +00:00
raise CodeError("ZP block cannot contain code statements, only definitions and comments")
2017-12-21 13:52:30 +00:00
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))
2017-12-21 13:52:30 +00:00
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")
2017-12-21 13:52:30 +00:00
if self.parsed.format == ProgramFormat.RAW:
self.p("; ---- raw assembler program ----")
self.p("* = " + Parser.to_hex(self.parsed.start_address) + "\n")
2017-12-21 13:52:30 +00:00
@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:
2017-12-30 12:34:52 +00:00
self.p("\t\tjsr il65_lib_zp.save_zeropage")
zp_float_bytes = {}
2017-12-21 13:52:30 +00:00
# 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:
2017-12-23 00:53:48 +00:00
vname = zpblock.label + '.' + variable.name
2017-12-21 13:52:30 +00:00
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 -")
2017-12-21 13:52:30 +00:00
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")
2017-12-30 12:34:52 +00:00
self.p("\t\tjmp il65_lib_zp.restore_zeropage")
2017-12-21 13:52:30 +00:00
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")
2017-12-21 13:52:30 +00:00
def blocks(self) -> None:
2017-12-21 22:05:35 +00:00
# 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:
2017-12-31 03:45:27 +00:00
if isinstance(s, Comment):
2017-12-21 22:05:35 +00:00
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
2017-12-21 13:52:30 +00:00
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))
2017-12-21 22:05:35 +00:00
for s in zpblock.statements:
2017-12-31 03:45:27 +00:00
if isinstance(s, Comment):
2017-12-21 22:05:35 +00:00
self.p(s.text)
else:
raise CodeError("zp cannot contain any other statements beside comments")
2017-12-21 13:52:30 +00:00
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
2017-12-25 18:09:10 +00:00
block = self.parsed.find_block("main")
statements = list(block.statements)
for index, stmt in enumerate(statements):
2017-12-31 03:45:27 +00:00
if isinstance(stmt, Label) and stmt.name == "start":
2017-12-25 18:09:10 +00:00
asmlines = [
"\t\tcld\t\t\t; clear decimal flag",
2017-12-28 03:20:59 +00:00
"\t\tclc\t\t\t; clear carry flag",
"\t\tclv\t\t\t; clear overflow flag",
2017-12-25 18:09:10 +00:00
]
2017-12-31 03:45:27 +00:00
statements.insert(index+1, InlineAsm(asmlines, stmt.sourceref))
2017-12-25 18:09:10 +00:00
break
block.statements = statements
2017-12-21 13:52:30 +00:00
# generate
for block in sorted(self.parsed.blocks, key=lambda b: b.address):
2017-12-21 22:05:35 +00:00
if block.name in ("ZP", "<header>"):
continue # these blocks are already processed
2017-12-21 13:52:30 +00:00
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)
2017-12-23 23:58:55 +00:00
subroutines = list(sub for sub in block.symbols.iter_subroutines() if sub.address is not None)
2017-12-21 13:52:30 +00:00
if subroutines:
self.p("\n; external subroutines")
for subdef in subroutines:
2017-12-23 23:58:55 +00:00
assert subdef.sub_block is None
self.p("\t\t{:s} = {:s}".format(subdef.name, Parser.to_hex(subdef.address)))
2017-12-21 13:52:30 +00:00
self.p("; end external subroutines")
for stmt in block.statements:
self.generate_statement(stmt)
2017-12-25 12:20:23 +00:00
subroutines = list(sub for sub in block.symbols.iter_subroutines(True))
2017-12-23 23:58:55 +00:00
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")
2017-12-21 13:52:30 +00:00
self.p("\t.pend\n")
2017-12-31 03:45:27 +00:00
def generate_block_vars(self, block: Block) -> None:
2018-01-08 02:31:23 +00:00
# @todo block vars should be re-initialized when the program is run again, and not depend on statically prefilled data!
2017-12-23 00:53:48 +00:00
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
2017-12-23 13:36:23 +00:00
elif constdef.type in STRING_DATATYPES:
# a const string is just a string variable in the generated assembly
self._generate_string_var(constdef)
2017-12-23 00:53:48 +00:00
else:
raise CodeError("invalid const type", constdef)
2017-12-21 13:52:30 +00:00
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()))
2017-12-21 13:52:30 +00:00
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))
2017-12-21 13:52:30 +00:00
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))
2017-12-21 13:52:30 +00:00
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))
2017-12-21 13:52:30 +00:00
else:
2017-12-27 18:01:14 +00:00
raise CodeError("invalid var type")
2017-12-21 13:52:30 +00:00
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
2018-01-01 04:04:04 +00:00
sourcecomment = "\t; " + vardef.sourcecomment if vardef.sourcecomment else ""
2017-12-21 13:52:30 +00:00
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)))
2017-12-21 13:52:30 +00:00
else:
if vardef.type == DataType.BYTE:
2018-01-01 04:04:04 +00:00
self.p("{:s}\t\t.byte {:s}{:s}".format(vardef.name, Parser.to_hex(int(vardef.value)), sourcecomment))
2017-12-21 13:52:30 +00:00
elif vardef.type == DataType.WORD:
2018-01-01 04:04:04 +00:00
self.p("{:s}\t\t.word {:s}{:s}".format(vardef.name, Parser.to_hex(int(vardef.value)), sourcecomment))
2017-12-21 13:52:30 +00:00
elif vardef.type == DataType.FLOAT:
2018-01-01 04:04:04 +00:00
self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}{:s}"
.format(vardef.name, *self.to_mflpt5(float(vardef.value)), sourcecomment))
2017-12-21 13:52:30 +00:00
else:
2017-12-27 18:01:14 +00:00
raise CodeError("weird datatype")
2017-12-21 13:52:30 +00:00
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:
2018-01-01 04:04:04 +00:00
self.p("{:s}\t\t.fill {:d}, ${:02x}{:s}".format(vardef.name, vardef.length, vardef.value or 0, sourcecomment))
2017-12-21 13:52:30 +00:00
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:
2017-12-27 18:01:14 +00:00
raise CodeError("invalid datatype", vardef.type)
2017-12-21 13:52:30 +00:00
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]))
2017-12-23 13:36:23 +00:00
elif vardef.type in STRING_DATATYPES:
self._generate_string_var(vardef)
2017-12-21 13:52:30 +00:00
else:
raise CodeError("unknown variable type " + str(vardef.type))
2017-12-23 13:36:23 +00:00
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'")
2018-01-01 22:46:33 +00:00
def generate_statement(self, stmt: AstNode) -> None:
2017-12-31 03:45:27 +00:00
if isinstance(stmt, ReturnStmt):
2017-12-21 13:52:30 +00:00
if stmt.a:
2017-12-31 03:45:27 +00:00
if isinstance(stmt.a, IntegerValue):
2017-12-21 13:52:30 +00:00
self.p("\t\tlda #{:d}".format(stmt.a.value))
else:
raise CodeError("can only return immediate values for now") # XXX
if stmt.x:
2017-12-31 03:45:27 +00:00
if isinstance(stmt.x, IntegerValue):
2017-12-21 13:52:30 +00:00
self.p("\t\tldx #{:d}".format(stmt.x.value))
else:
raise CodeError("can only return immediate values for now") # XXX
if stmt.y:
2017-12-31 03:45:27 +00:00
if isinstance(stmt.y, IntegerValue):
2017-12-21 13:52:30 +00:00
self.p("\t\tldy #{:d}".format(stmt.y.value))
else:
raise CodeError("can only return immediate values for now") # XXX
self.p("\t\trts")
2017-12-31 03:45:27 +00:00
elif isinstance(stmt, AugmentedAssignmentStmt):
2017-12-28 03:20:59 +00:00
self.generate_augmented_assignment(stmt)
2017-12-31 03:45:27 +00:00
elif isinstance(stmt, AssignmentStmt):
2017-12-21 13:52:30 +00:00
self.generate_assignment(stmt)
2017-12-31 03:45:27 +00:00
elif isinstance(stmt, Label):
2017-12-31 03:10:27 +00:00
self.p("\n{:s}\t\t\t\t; {:s}".format(stmt.name, stmt.lineref))
2017-12-31 03:45:27 +00:00
elif isinstance(stmt, (InplaceIncrStmt, InplaceDecrStmt)):
2017-12-28 03:20:59 +00:00
self.generate_incr_or_decr(stmt)
2017-12-31 03:45:27 +00:00
elif isinstance(stmt, CallStmt):
2017-12-23 00:53:48 +00:00
self.generate_call(stmt)
2017-12-31 03:45:27 +00:00
elif isinstance(stmt, InlineAsm):
2017-12-31 03:10:27 +00:00
self.p("\t\t; inline asm, " + stmt.lineref)
2017-12-21 13:52:30 +00:00
for line in stmt.asmlines:
self.p(line)
2017-12-31 03:10:27 +00:00
self.p("\t\t; end inline asm, " + stmt.lineref)
2017-12-31 03:45:27 +00:00
elif isinstance(stmt, Comment):
2017-12-21 22:05:35 +00:00
self.p(stmt.text)
2017-12-31 03:45:27 +00:00
elif isinstance(stmt, BreakpointStmt):
2017-12-27 22:45:22 +00:00
# put a marker in the source so that we can generate a list of breakpoints later
2017-12-31 03:10:27 +00:00
self.p("\t\tnop\t; {:s} {:s}".format(self.BREAKPOINT_COMMENT_SIGNATURE, stmt.lineref))
2017-12-21 13:52:30 +00:00
else:
raise CodeError("unknown statement " + repr(stmt))
2017-12-31 03:45:27 +00:00
self.previous_stmt_was_assignment = isinstance(stmt, AssignmentStmt)
2017-12-21 13:52:30 +00:00
2017-12-31 03:45:27 +00:00
def generate_incr_or_decr(self, stmt: Union[InplaceIncrStmt, InplaceDecrStmt]) -> None:
2018-01-01 16:56:55 +00:00
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:
2017-12-31 14:50:50 +00:00
raise CodeError("only supports integer incr/decr by up to 255 for now") # XXX
2017-12-31 03:45:27 +00:00
is_incr = isinstance(stmt, InplaceIncrStmt)
2018-01-01 16:56:55 +00:00
howmuch = stmt.value.value
value_str = stmt.value.name or str(howmuch)
2017-12-31 03:45:27 +00:00
if isinstance(stmt.what, RegisterValue):
2017-12-29 01:28:59 +00:00
reg = stmt.what.register
2017-12-29 13:17:51 +00:00
# note: these operations below are all checked to be ok
2017-12-28 03:20:59 +00:00
if is_incr:
2017-12-29 01:28:59 +00:00
if reg == 'A':
2017-12-29 13:17:51 +00:00
# a += 1..255
2017-12-28 03:20:59 +00:00
self.p("\t\tclc")
2018-01-01 16:56:55 +00:00
self.p("\t\tadc #" + value_str)
2017-12-29 01:28:59 +00:00
elif reg in REGISTER_BYTES:
2018-01-01 16:56:55 +00:00
if howmuch == 1:
2017-12-29 13:17:51 +00:00
# x/y += 1
2017-12-29 01:28:59 +00:00
self.p("\t\tin{:s}".format(reg.lower()))
2017-12-23 00:53:48 +00:00
else:
2017-12-29 13:17:51 +00:00
# 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()))
2017-12-29 01:28:59 +00:00
elif reg == "AX":
2017-12-29 13:17:51 +00:00
# AX += 1..255
2017-12-29 01:28:59 +00:00
self.p("\t\tclc")
2018-01-01 16:56:55 +00:00
self.p("\t\tadc #" + value_str)
2017-12-29 13:17:51 +00:00
self.p("\t\tbcc +")
2017-12-29 01:28:59 +00:00
self.p("\t\tinx")
self.p("+")
elif reg == "AY":
2017-12-29 13:17:51 +00:00
# AY += 1..255
2017-12-29 01:28:59 +00:00
self.p("\t\tclc")
2018-01-01 16:56:55 +00:00
self.p("\t\tadc # " + value_str)
2017-12-29 13:17:51 +00:00
self.p("\t\tbcc +")
2017-12-29 01:28:59 +00:00
self.p("\t\tiny")
self.p("+")
elif reg == "XY":
2018-01-01 16:56:55 +00:00
if howmuch == 1:
2017-12-29 13:17:51 +00:00
# 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("+")
2017-12-28 03:20:59 +00:00
else:
2017-12-29 01:28:59 +00:00
raise CodeError("invalid incr register: " + reg)
2017-12-28 03:20:59 +00:00
else:
2017-12-29 01:28:59 +00:00
if reg == 'A':
2017-12-29 13:17:51 +00:00
# a -= 1..255
2017-12-29 00:16:39 +00:00
self.p("\t\tsec")
2018-01-01 16:56:55 +00:00
self.p("\t\tsbc #" + value_str)
2017-12-29 01:28:59 +00:00
elif reg in REGISTER_BYTES:
2018-01-01 16:56:55 +00:00
if howmuch == 1:
2017-12-29 13:17:51 +00:00
# x/y -= 1
2017-12-29 01:28:59 +00:00
self.p("\t\tde{:s}".format(reg.lower()))
2017-12-23 00:53:48 +00:00
else:
2017-12-29 13:17:51 +00:00
# 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()))
2017-12-29 01:28:59 +00:00
elif reg == "AX":
2017-12-29 13:17:51 +00:00
# AX -= 1..255
self.p("\t\tsec")
2018-01-01 16:56:55 +00:00
self.p("\t\tsbc #" + value_str)
2017-12-29 13:17:51 +00:00
self.p("\t\tbcs +")
2017-12-29 01:28:59 +00:00
self.p("\t\tdex")
2017-12-29 13:17:51 +00:00
self.p("+")
2017-12-29 01:28:59 +00:00
elif reg == "AY":
2017-12-29 13:17:51 +00:00
# AY -= 1..255
self.p("\t\tsec")
2018-01-01 16:56:55 +00:00
self.p("\t\tsbc #" + value_str)
2017-12-29 13:17:51 +00:00
self.p("\t\tbcs +")
2017-12-29 01:28:59 +00:00
self.p("\t\tdey")
2017-12-29 13:17:51 +00:00
self.p("+")
2017-12-29 01:28:59 +00:00
elif reg == "XY":
2018-01-01 16:56:55 +00:00
if howmuch == 1:
2017-12-29 13:17:51 +00:00
# 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("+")
2017-12-23 00:53:48 +00:00
else:
2017-12-29 01:28:59 +00:00
raise CodeError("invalid decr register: " + reg)
2017-12-31 03:45:27 +00:00
elif isinstance(stmt.what, (MemMappedValue, IndirectValue)):
2017-12-28 03:20:59 +00:00
what = stmt.what
2017-12-31 03:45:27 +00:00
if isinstance(what, IndirectValue):
if isinstance(what.value, IntegerValue):
2018-01-01 16:56:55 +00:00
what_str = what.value.name or Parser.to_hex(what.value.value)
2017-12-28 03:20:59 +00:00
else:
raise CodeError("invalid incr indirect type", what.value)
else:
2018-01-01 16:56:55 +00:00
what_str = what.name or Parser.to_hex(what.address)
2017-12-28 03:20:59 +00:00
if what.datatype == DataType.BYTE:
2018-01-01 16:56:55 +00:00
if howmuch == 1:
self.p("\t\t{:s} {:s}".format("inc" if is_incr else "dec", what_str))
2017-12-28 03:20:59 +00:00
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)
2017-12-28 03:20:59 +00:00
elif what.datatype == DataType.WORD:
2018-01-01 16:56:55 +00:00
if howmuch == 1:
2017-12-30 12:34:52 +00:00
# mem.word +=/-= 1
2017-12-28 03:20:59 +00:00
if is_incr:
2018-01-01 16:56:55 +00:00
self.p("\t\tinc " + what_str)
2017-12-23 00:53:48 +00:00
self.p("\t\tbne +")
2018-01-01 16:56:55 +00:00
self.p("\t\tinc {:s}+1".format(what_str))
2017-12-23 00:53:48 +00:00
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)
2017-12-23 00:53:48 +00:00
else:
2017-12-30 12:34:52 +00:00
# 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("+")
2017-12-30 12:34:52 +00:00
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("+")
2017-12-31 14:50:50 +00:00
elif what.datatype == DataType.FLOAT:
2018-01-01 16:56:55 +00:00
if howmuch == 1.0:
2018-01-01 04:04:04 +00:00
# special case for +/-1
2017-12-31 14:50:50 +00:00
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
2018-01-01 16:56:55 +00:00
self.p("\t\tldx #<" + what_str)
self.p("\t\tldy #>" + what_str)
2017-12-31 14:50:50 +00:00
if is_incr:
2018-01-01 16:56:55 +00:00
self.p("\t\tjsr c64flt.float_add_one")
2017-12-31 14:50:50 +00:00
else:
2018-01-01 16:56:55 +00:00
self.p("\t\tjsr c64flt.float_sub_one")
elif stmt.value.name:
2018-01-01 04:04:04 +00:00
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
2018-01-01 16:56:55 +00:00
self.p("\t\tlda #<" + stmt.value.name)
2018-01-01 04:04:04 +00:00
self.p("\t\tsta c64.SCRATCH_ZPWORD1")
2018-01-01 16:56:55 +00:00
self.p("\t\tlda #>" + stmt.value.name)
2018-01-01 04:04:04 +00:00
self.p("\t\tsta c64.SCRATCH_ZPWORD1+1")
2018-01-01 16:56:55 +00:00
self.p("\t\tldx #<" + what_str)
self.p("\t\tldy #>" + what_str)
2018-01-01 04:04:04 +00:00
if is_incr:
2018-01-01 16:56:55 +00:00
self.p("\t\tjsr c64flt.float_add_SW1_to_XY")
2018-01-01 04:04:04 +00:00
else:
2018-01-01 16:56:55 +00:00
self.p("\t\tjsr c64flt.float_sub_SW1_from_XY")
2017-12-31 14:50:50 +00:00
else:
2018-01-01 04:04:04 +00:00
raise CodeError("incr/decr missing float constant definition")
2017-12-23 00:53:48 +00:00
else:
2018-01-01 16:56:55 +00:00
raise CodeError("cannot in/decrement memory of type " + str(what.datatype), howmuch)
2017-12-28 03:20:59 +00:00
else:
raise CodeError("cannot in/decrement " + str(stmt.what))
2017-12-23 00:53:48 +00:00
2017-12-31 03:45:27 +00:00
def generate_call(self, stmt: CallStmt) -> None:
2017-12-31 03:10:27 +00:00
self.p("\t\t\t\t\t; " + stmt.lineref)
2017-12-27 18:01:14 +00:00
if stmt.condition:
assert stmt.is_goto
if stmt.condition.lvalue:
if stmt.condition.comparison_op:
2017-12-28 18:08:33 +00:00
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))
2017-12-27 18:01:14 +00:00
else:
2017-12-28 18:08:33 +00:00
self.p("\t\tjmp {:s}".format(targetstr))
2017-12-27 18:01:14 +00:00
else:
2017-12-28 18:08:33 +00:00
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)
2017-12-28 18:08:33 +00:00
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)
2017-12-31 03:45:27 +00:00
def _generate_goto_conditional_truthvalue(self, stmt: CallStmt) -> None:
2017-12-28 18:08:33 +00:00
# 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
2017-12-31 03:45:27 +00:00
assert isinstance(cv, MemMappedValue)
2017-12-28 18:08:33 +00:00
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
2017-12-31 03:10:27 +00:00
cv.datatype, str(cv), stmt.sourceref)
2017-12-28 18:08:33 +00:00
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
2017-12-31 03:45:27 +00:00
assert isinstance(cv, RegisterValue)
2017-12-28 18:08:33 +00:00
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")
2017-12-30 12:34:52 +00:00
assert not target_indirect
2017-12-28 18:08:33 +00:00
cv = stmt.condition.lvalue.value # type: ignore
2017-12-31 03:45:27 +00:00
if isinstance(cv, RegisterValue):
2017-12-30 12:34:52 +00:00
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
2017-12-30 20:36:42 +00:00
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")
2017-12-30 12:34:52 +00:00
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
2017-12-31 03:45:27 +00:00
elif isinstance(cv, MemMappedValue):
2017-12-28 18:08:33 +00:00
raise CodeError("memmapped indirect should not occur, use the variable without indirection")
2017-12-31 03:45:27 +00:00
elif isinstance(cv, IntegerValue) and cv.constant:
2017-12-28 18:08:33 +00:00
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
2017-12-27 18:01:14 +00:00
else:
2017-12-28 18:08:33 +00:00
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
2017-12-31 03:10:27 +00:00
cv.datatype, str(cv), stmt.sourceref)
2017-12-28 18:08:33 +00:00
else:
raise CodeError("weird indirect type", str(cv))
2017-12-27 18:01:14 +00:00
2017-12-28 18:08:33 +00:00
cv = stmt.condition.lvalue
2017-12-31 03:45:27 +00:00
if isinstance(cv, RegisterValue):
2017-12-28 18:08:33 +00:00
self._generate_call_or_goto(stmt, branch_emitter_reg)
2017-12-31 03:45:27 +00:00
elif isinstance(cv, MemMappedValue):
2017-12-28 18:08:33 +00:00
self._generate_call_or_goto(stmt, branch_emitter_mmap)
2017-12-31 03:45:27 +00:00
elif isinstance(cv, IndirectValue):
if isinstance(cv.value, RegisterValue):
2017-12-28 18:08:33 +00:00
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
2017-12-31 03:45:27 +00:00
elif isinstance(cv.value, MemMappedValue):
2017-12-28 18:08:33 +00:00
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
2017-12-31 03:45:27 +00:00
elif isinstance(cv.value, IntegerValue) and cv.value.constant:
2017-12-28 18:08:33 +00:00
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
else:
raise CodeError("weird indirect type", str(cv))
2017-12-27 18:01:14 +00:00
else:
2017-12-28 18:08:33 +00:00
raise CodeError("need register, memmapped or indirect value", str(cv))
2017-12-27 18:01:14 +00:00
2017-12-31 03:45:27 +00:00
def _generate_goto_conditional_comparison(self, stmt: CallStmt) -> None:
2017-12-28 18:08:33 +00:00
# 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()
2017-12-31 03:45:27 +00:00
if isinstance(rv, RegisterValue):
2017-12-28 18:08:33 +00:00
# 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
2017-12-31 03:45:27 +00:00
if isinstance(lv, RegisterValue):
if isinstance(rv, RegisterValue):
2017-12-28 18:08:33 +00:00
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")
2017-12-31 03:45:27 +00:00
elif isinstance(rv, IntegerValue):
2017-12-28 18:08:33 +00:00
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")
2017-12-31 03:45:27 +00:00
elif isinstance(rv, MemMappedValue):
2017-12-28 18:08:33 +00:00
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)
2017-12-31 03:45:27 +00:00
elif isinstance(lv, MemMappedValue):
assert not isinstance(rv, RegisterValue), "registers as rvalue should have been swapped with lvalue"
if isinstance(rv, IntegerValue):
2017-12-28 18:08:33 +00:00
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
2017-12-31 03:45:27 +00:00
elif isinstance(rv, MemMappedValue):
2017-12-28 18:08:33 +00:00
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)
2017-12-27 18:01:14 +00:00
2017-12-31 03:45:27 +00:00
def _generate_call_or_goto(self, stmt: CallStmt, branch_emitter: Callable[[str, bool, bool], None]) -> None:
2017-12-25 18:22:22 +00:00
def generate_param_assignments() -> None:
for assign_stmt in stmt.desugared_call_arguments:
self.generate_assignment(assign_stmt)
2017-12-25 18:22:22 +00:00
2017-12-26 00:30:22 +00:00
def generate_result_assignments() -> None:
for assign_stmt in stmt.desugared_output_assignments:
self.generate_assignment(assign_stmt)
2017-12-25 18:22:22 +00:00
def params_load_a() -> bool:
for assign_stmt in stmt.desugared_call_arguments:
for lv in assign_stmt.leftvalues:
2017-12-31 03:45:27 +00:00
if isinstance(lv, RegisterValue):
2017-12-25 18:22:22 +00:00
if lv.register == 'A':
return True
return False
def unclobber_result_registers(registers: Set[str], output_assignments: List[AssignmentStmt]) -> Set[str]:
result = registers.copy()
2017-12-26 00:30:22 +00:00
for a in output_assignments:
for lv in a.leftvalues:
2017-12-31 03:45:27 +00:00
if isinstance(lv, RegisterValue):
2017-12-26 00:30:22 +00:00
if len(lv.register) == 1:
result.discard(lv.register)
2017-12-26 00:30:22 +00:00
else:
for r in lv.register:
result.discard(r)
return result
2017-12-26 00:30:22 +00:00
2017-12-23 00:53:48 +00:00
if stmt.target.name:
symblock, targetdef = self.cur_block.lookup(stmt.target.name)
else:
symblock = None
targetdef = None
if isinstance(targetdef, SubroutineDef):
2017-12-31 03:45:27 +00:00
if isinstance(stmt.target, MemMappedValue):
2017-12-23 00:53:48 +00:00
targetstr = stmt.target.name or Parser.to_hex(stmt.address)
else:
2017-12-27 18:01:14 +00:00
raise CodeError("call sub target should be mmapped")
2017-12-23 01:41:41 +00:00
if stmt.is_goto:
2017-12-25 02:42:20 +00:00
generate_param_assignments()
2017-12-28 18:08:33 +00:00
branch_emitter(targetstr, True, False)
2017-12-26 00:30:22 +00:00
# no result assignments because it's a goto
2017-12-23 01:41:41 +00:00
return
2017-12-23 00:53:48 +00:00
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()
2017-12-28 18:08:33 +00:00
branch_emitter(targetstr, False, False)
2017-12-26 00:30:22 +00:00
generate_result_assignments()
2017-12-23 00:53:48 +00:00
return
2017-12-31 03:45:27 +00:00
if isinstance(stmt.target, IndirectValue):
2017-12-23 00:53:48 +00:00
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
2017-12-31 03:45:27 +00:00
elif isinstance(stmt.target.value, RegisterValue):
2017-12-23 00:53:48 +00:00
targetstr = stmt.target.value.register
2017-12-31 03:45:27 +00:00
elif isinstance(stmt.target.value, IntegerValue):
2017-12-23 00:53:48 +00:00
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()
2017-12-23 00:53:48 +00:00
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)))
2017-12-28 18:08:33 +00:00
branch_emitter(Parser.to_hex(Zeropage.SCRATCH_B1), True, True)
2017-12-23 00:53:48 +00:00
else:
2017-12-28 18:08:33 +00:00
branch_emitter(targetstr, True, True)
2017-12-26 00:30:22 +00:00
# no result assignments because it's a goto
2017-12-23 00:53:48 +00:00
else:
2017-12-28 18:08:33 +00:00
# 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()
2017-12-23 00:53:48 +00:00
if targetstr in REGISTER_WORDS:
2017-12-31 03:10:27 +00:00
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:
2017-12-30 12:34:52 +00:00
# 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)
2017-12-23 00:53:48 +00:00
else:
2017-12-30 12:34:52 +00:00
self.p("\t\tjsr il65_lib.jsr_indirect_"+targetstr)
2017-12-23 00:53:48 +00:00
else:
self.p("\t\tjsr +")
self.p("\t\tjmp ++")
self.p("+\t\tjmp ({:s})".format(targetstr))
self.p("+")
2017-12-26 00:30:22 +00:00
generate_result_assignments()
2017-12-23 00:53:48 +00:00
else:
2017-12-28 18:08:33 +00:00
# call to a label or immediate address
2017-12-23 00:53:48 +00:00
if stmt.target.name:
targetstr = stmt.target.name
elif stmt.address is not None:
targetstr = Parser.to_hex(stmt.address)
2017-12-31 03:45:27 +00:00
elif isinstance(stmt.target, IntegerValue):
2017-12-23 00:53:48 +00:00
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()
2017-12-28 18:08:33 +00:00
branch_emitter(targetstr, True, False)
2017-12-26 00:30:22 +00:00
# no result assignments because it's a goto
2017-12-23 00:53:48 +00:00
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()
2017-12-28 18:08:33 +00:00
branch_emitter(targetstr, False, False)
2017-12-26 00:30:22 +00:00
generate_result_assignments()
2017-12-23 00:53:48 +00:00
2017-12-31 03:45:27 +00:00
def generate_augmented_assignment(self, stmt: AugmentedAssignmentStmt) -> None:
2017-12-28 03:20:59 +00:00
# for instance: value += 3
lvalue = stmt.leftvalues[0]
rvalue = stmt.right
2017-12-31 03:10:27 +00:00
self.p("\t\t\t\t\t; " + stmt.lineref)
2017-12-31 03:45:27 +00:00
if isinstance(lvalue, RegisterValue):
if isinstance(rvalue, IntegerValue):
2017-12-28 03:20:59 +00:00
self._generate_aug_reg_int(lvalue, stmt.operator, rvalue)
2017-12-31 03:45:27 +00:00
elif isinstance(rvalue, RegisterValue):
2017-12-28 03:20:59 +00:00
self._generate_aug_reg_reg(lvalue, stmt.operator, rvalue)
2017-12-31 03:45:27 +00:00
elif isinstance(rvalue, MemMappedValue):
2017-12-28 18:08:33 +00:00
self._generate_aug_reg_mem(lvalue, stmt.operator, rvalue)
2017-12-28 03:20:59 +00:00
else:
2017-12-28 18:08:33 +00:00
raise CodeError("invalid rvalue for augmented assignment on register", str(rvalue))
2017-12-28 03:20:59 +00:00
else:
2018-01-01 04:04:04 +00:00
raise CodeError("augmented assignment only implemented for registers for now", str(rvalue)) # XXX
2017-12-28 18:08:33 +00:00
2017-12-31 03:45:27 +00:00
def _generate_aug_reg_mem(self, lvalue: RegisterValue, operator: str, rvalue: MemMappedValue) -> None:
2017-12-28 18:08:33 +00:00
r_str = rvalue.name or Parser.to_hex(rvalue.address)
if operator == "+=":
if lvalue.register == "A":
2017-12-29 00:16:39 +00:00
self.p("\t\tclc")
2017-12-28 18:08:33 +00:00
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")
2017-12-28 18:08:33 +00:00
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")
2017-12-28 18:08:33 +00:00
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
if lvalue.register == "A":
2017-12-29 00:16:39 +00:00
self.p("\t\tsec")
2017-12-28 18:08:33 +00:00
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")
2017-12-28 18:08:33 +00:00
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")
2017-12-28 18:08:33 +00:00
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")
2017-12-28 18:08:33 +00:00
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tand " + r_str)
self.p("\t\ttay")
2017-12-28 18:08:33 +00:00
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")
2017-12-28 18:08:33 +00:00
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tora " + r_str)
self.p("\t\ttay")
2017-12-28 18:08:33 +00:00
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")
2017-12-28 18:08:33 +00:00
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\teor " + r_str)
self.p("\t\ttay")
2017-12-28 18:08:33 +00:00
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
2018-01-01 17:57:12 +00:00
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")
2018-01-01 17:57:12 +00:00
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tlsr " + r_str)
self.p("\t\ttay")
2018-01-01 17:57:12 +00:00
else:
raise CodeError("unsupported lvalue register for shift", str(lvalue)) # @todo >>=.word
2017-12-28 18:08:33 +00:00
elif operator == "<<=":
2018-01-01 17:57:12 +00:00
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")
2018-01-01 17:57:12 +00:00
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tasl " + r_str)
self.p("\t\ttay")
2018-01-01 17:57:12 +00:00
else:
raise CodeError("unsupported lvalue register for shift", str(lvalue)) # @todo >>=.word
2017-12-28 03:20:59 +00:00
2017-12-31 03:45:27 +00:00
def _generate_aug_reg_int(self, lvalue: RegisterValue, operator: str, rvalue: IntegerValue) -> None:
2017-12-28 03:20:59 +00:00
r_str = rvalue.name or Parser.to_hex(rvalue.value)
if operator == "+=":
if lvalue.register == "A":
2017-12-29 00:16:39 +00:00
self.p("\t\tclc")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
if lvalue.register == "A":
2017-12-29 00:16:39 +00:00
self.p("\t\tsec")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tand #" + r_str)
self.p("\t\ttay")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tora #" + r_str)
self.p("\t\ttay")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\teor #" + r_str)
self.p("\t\ttay")
2017-12-28 03:20:59 +00:00
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
2018-01-01 17:57:12 +00:00
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")
2018-01-01 17:57:12 +00:00
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
shifts_A(rvalue.value)
self.p("\t\ttay")
2018-01-01 17:57:12 +00:00
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo >>=.word
2017-12-28 03:20:59 +00:00
elif operator == "<<=":
2018-01-01 17:57:12 +00:00
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")
2018-01-01 17:57:12 +00:00
elif lvalue.register == "Y":
with self.preserving_registers({'A'}):
self.p("\t\ttya")
shifts_A(rvalue.value)
self.p("\t\ttay")
2018-01-01 17:57:12 +00:00
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo <<=.word
2017-12-28 03:20:59 +00:00
2017-12-31 03:45:27 +00:00
def _generate_aug_reg_reg(self, lvalue: RegisterValue, operator: str, rvalue: RegisterValue) -> None:
2017-12-28 03:20:59 +00:00
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)))
2017-12-29 00:16:39 +00:00
self.p("\t\tclc")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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)))
2017-12-29 00:16:39 +00:00
self.p("\t\tsec")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
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")
2017-12-28 03:20:59 +00:00
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo <<=.word
2017-12-31 03:45:27 +00:00
def generate_assignment(self, stmt: AssignmentStmt) -> None:
def unwrap_indirect(iv: IndirectValue) -> MemMappedValue:
if isinstance(iv.value, MemMappedValue):
2017-12-23 00:53:48 +00:00
return iv.value
2017-12-31 03:45:27 +00:00
elif iv.value.constant and isinstance(iv.value, IntegerValue):
return MemMappedValue(iv.value.value, iv.datatype, 1, stmt.sourceref, iv.name)
2017-12-23 00:53:48 +00:00
else:
raise CodeError("cannot yet generate code for assignment: non-constant and non-memmapped indirect") # XXX
2017-12-25 12:20:23 +00:00
rvalue = stmt.right
2017-12-31 03:45:27 +00:00
if isinstance(rvalue, IndirectValue):
2017-12-23 00:53:48 +00:00
rvalue = unwrap_indirect(rvalue)
2017-12-31 03:10:27 +00:00
self.p("\t\t\t\t\t; " + stmt.lineref)
2017-12-31 03:45:27 +00:00
if isinstance(rvalue, IntegerValue):
2017-12-21 13:52:30 +00:00
for lv in stmt.leftvalues:
2017-12-31 03:45:27 +00:00
if isinstance(lv, RegisterValue):
2017-12-23 00:53:48 +00:00
self.generate_assign_integer_to_reg(lv.register, rvalue)
2017-12-31 03:45:27 +00:00
elif isinstance(lv, MemMappedValue):
2017-12-23 00:53:48 +00:00
self.generate_assign_integer_to_mem(lv, rvalue)
2017-12-31 03:45:27 +00:00
elif isinstance(lv, IndirectValue):
2017-12-23 00:53:48 +00:00
lv = unwrap_indirect(lv)
self.generate_assign_integer_to_mem(lv, rvalue)
2017-12-21 13:52:30 +00:00
else:
raise CodeError("invalid assignment target (1)", str(stmt))
2017-12-31 03:45:27 +00:00
elif isinstance(rvalue, RegisterValue):
2017-12-21 13:52:30 +00:00
for lv in stmt.leftvalues:
2017-12-31 03:45:27 +00:00
if isinstance(lv, RegisterValue):
2017-12-23 00:53:48 +00:00
self.generate_assign_reg_to_reg(lv, rvalue.register)
2017-12-31 03:45:27 +00:00
elif isinstance(lv, MemMappedValue):
2017-12-23 00:53:48 +00:00
self.generate_assign_reg_to_memory(lv, rvalue.register)
2017-12-31 03:45:27 +00:00
elif isinstance(lv, IndirectValue):
2017-12-23 00:53:48 +00:00
lv = unwrap_indirect(lv)
self.generate_assign_reg_to_memory(lv, rvalue.register)
2017-12-21 13:52:30 +00:00
else:
raise CodeError("invalid assignment target (2)", str(stmt))
2017-12-31 03:45:27 +00:00
elif isinstance(rvalue, StringValue):
2017-12-23 00:53:48 +00:00
r_str = self.output_string(rvalue.value, True)
2017-12-21 13:52:30 +00:00
for lv in stmt.leftvalues:
2017-12-31 03:45:27 +00:00
if isinstance(lv, RegisterValue):
2017-12-23 00:53:48 +00:00
if len(rvalue.value) == 1:
2017-12-21 13:52:30 +00:00
self.generate_assign_char_to_reg(lv, r_str)
else:
2017-12-23 00:53:48 +00:00
self.generate_assign_string_to_reg(lv, rvalue)
2017-12-31 03:45:27 +00:00
elif isinstance(lv, MemMappedValue):
2017-12-23 00:53:48 +00:00
if len(rvalue.value) == 1:
self.generate_assign_char_to_memory(lv, r_str)
else:
self.generate_assign_string_to_memory(lv, rvalue)
2017-12-31 03:45:27 +00:00
elif isinstance(lv, IndirectValue):
2017-12-23 00:53:48 +00:00
lv = unwrap_indirect(lv)
if len(rvalue.value) == 1:
2017-12-21 13:52:30 +00:00
self.generate_assign_char_to_memory(lv, r_str)
else:
2017-12-23 00:53:48 +00:00
self.generate_assign_string_to_memory(lv, rvalue)
2017-12-21 13:52:30 +00:00
else:
raise CodeError("invalid assignment target (2)", str(stmt))
2017-12-31 03:45:27 +00:00
elif isinstance(rvalue, MemMappedValue):
2017-12-21 13:52:30 +00:00
for lv in stmt.leftvalues:
2017-12-31 03:45:27 +00:00
if isinstance(lv, RegisterValue):
2017-12-23 00:53:48 +00:00
self.generate_assign_mem_to_reg(lv.register, rvalue)
2017-12-31 03:45:27 +00:00
elif isinstance(lv, MemMappedValue):
2017-12-23 00:53:48 +00:00
self.generate_assign_mem_to_mem(lv, rvalue)
2017-12-31 03:45:27 +00:00
elif isinstance(lv, IndirectValue):
2017-12-23 00:53:48 +00:00
lv = unwrap_indirect(lv)
self.generate_assign_mem_to_mem(lv, rvalue)
2017-12-21 13:52:30 +00:00
else:
raise CodeError("invalid assignment target (4)", str(stmt))
2017-12-31 03:45:27 +00:00
elif isinstance(rvalue, FloatValue):
2017-12-21 13:52:30 +00:00
for lv in stmt.leftvalues:
2017-12-31 03:45:27 +00:00
if isinstance(lv, MemMappedValue) and lv.datatype == DataType.FLOAT:
2017-12-23 13:36:23 +00:00
self.generate_assign_float_to_mem(lv, rvalue)
2017-12-31 03:45:27 +00:00
elif isinstance(lv, IndirectValue):
2017-12-23 00:53:48 +00:00
lv = unwrap_indirect(lv)
assert lv.datatype == DataType.FLOAT
2017-12-23 13:36:23 +00:00
self.generate_assign_float_to_mem(lv, rvalue)
2017-12-21 13:52:30 +00:00
else:
raise CodeError("cannot assign float to ", str(lv))
else:
raise CodeError("invalid assignment value type", str(stmt))
2017-12-31 03:45:27 +00:00
def generate_assign_float_to_mem(self, mmv: MemMappedValue,
rvalue: Union[FloatValue, IntegerValue]) -> None:
2017-12-23 13:36:23 +00:00
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))
2017-12-21 13:52:30 +00:00
2017-12-31 03:45:27 +00:00
def generate_assign_reg_to_memory(self, lv: MemMappedValue, r_register: str) -> None:
2017-12-21 13:52:30 +00:00
# Memory = Register
lv_string = lv.name or Parser.to_hex(lv.address)
2017-12-21 13:52:30 +00:00
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
2017-12-25 18:22:22 +00:00
with self.preserving_registers({'A'}, loads_a_within=True):
2017-12-21 13:52:30 +00:00
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:
2017-12-25 01:29:14 +00:00
# assigning a register to a float requires c64 ROM routines
2017-12-24 23:15:04 +00:00
if r_register in REGISTER_WORDS:
2017-12-25 01:29:14 +00:00
def do_rom_calls():
2018-01-01 16:56:55 +00:00
self.p("\t\tjsr c64flt.GIVUAYF") # uword AY -> fac1
2017-12-25 01:29:14 +00:00
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
2017-12-25 18:22:22 +00:00
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
2017-12-25 01:29:14 +00:00
self.p("\t\ttxa") # X->A (so we have AY now)
do_rom_calls()
2017-12-24 23:15:04 +00:00
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":
2017-12-25 18:22:22 +00:00
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
2017-12-24 23:15:04 +00:00
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)
2017-12-21 13:52:30 +00:00
else:
raise CodeError("invalid lvalue type", lv.datatype)
2017-12-31 03:45:27 +00:00
def generate_assign_reg_to_reg(self, lv: RegisterValue, r_register: str) -> None:
2017-12-21 13:52:30 +00:00
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":
2017-12-29 01:28:59 +00:00
# x -> a, y -> x, 6502 doesn't have tyx
2017-12-21 13:52:30 +00:00
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":
2017-12-29 01:28:59 +00:00
# x -> a
2017-12-21 13:52:30 +00:00
self.p("\t\ttxa")
elif lv.register == "XY" and r_register == "AX":
2017-12-29 01:28:59 +00:00
# x -> y, a -> x, 6502 doesn't have txy
2017-12-21 13:52:30 +00:00
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
2017-12-29 01:28:59 +00:00
self.p("\t\ttax")
2017-12-21 13:52:30 +00:00
elif lv.register == "XY" and r_register == "AY":
2017-12-29 01:28:59 +00:00
# a -> x
2017-12-21 13:52:30 +00:00
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
2017-12-21 13:52:30 +00:00
# see http://6502.org/tutorials/register_preservation.html
if not self.cur_block.preserve_registers and not always_preserve:
yield
return
2017-12-21 13:52:30 +00:00
if registers == {'A'}:
self.p("\t\tpha")
yield
self.p("\t\tpla")
elif registers:
2017-12-25 18:22:22 +00:00
if not loads_a_within:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
2017-12-21 13:52:30 +00:00
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")
2017-12-25 18:22:22 +00:00
if not loads_a_within:
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
2017-12-21 13:52:30 +00:00
yield
2017-12-28 18:08:33 +00:00
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")
2017-12-21 13:52:30 +00:00
if 'A' in registers:
self.p("\t\tpla")
else:
yield
2017-12-31 03:45:27 +00:00
def generate_assign_integer_to_mem(self, lv: MemMappedValue, rvalue: IntegerValue) -> None:
2017-12-21 13:52:30 +00:00
if lv.name:
symblock, sym = self.cur_block.lookup(lv.name)
if not isinstance(sym, VariableDef):
2017-12-27 18:01:14 +00:00
raise CodeError("invalid lvalue type " + str(sym))
2017-12-23 00:53:48 +00:00
assign_target = symblock.label + '.' + sym.name if symblock is not self.cur_block else lv.name
2017-12-21 13:52:30 +00:00
lvdatatype = sym.type
else:
assign_target = Parser.to_hex(lv.address)
2017-12-21 13:52:30 +00:00
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")
2017-12-25 18:22:22 +00:00
with self.preserving_registers({'A'}, loads_a_within=True):
2017-12-21 13:52:30 +00:00
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")
2017-12-25 18:22:22 +00:00
with self.preserving_registers({'A'}, loads_a_within=True):
2017-12-21 13:52:30 +00:00
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):
2017-12-27 18:01:14 +00:00
raise CodeError("value cannot be assigned to a float")
self.generate_assign_float_to_mem(lv, rvalue)
2017-12-21 13:52:30 +00:00
else:
2017-12-27 18:01:14 +00:00
raise CodeError("invalid lvalue type " + str(lvdatatype))
2017-12-21 13:52:30 +00:00
2017-12-31 03:45:27 +00:00
def generate_assign_mem_to_reg(self, l_register: str, rvalue: MemMappedValue) -> None:
2017-12-21 13:52:30 +00:00
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:
2017-12-23 13:36:23 +00:00
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")
2017-12-21 13:52:30 +00:00
2017-12-31 03:45:27 +00:00
def generate_assign_mem_to_mem(self, lv: MemMappedValue, rvalue: MemMappedValue) -> None:
2017-12-25 11:58:52 +00:00
r_str = rvalue.name or Parser.to_hex(rvalue.address)
l_str = lv.name or Parser.to_hex(lv.address)
2017-12-21 13:52:30 +00:00
if lv.datatype == DataType.BYTE:
if rvalue.datatype != DataType.BYTE:
2017-12-23 13:36:23 +00:00
raise CodeError("can only assign a byte to a byte", str(rvalue))
2017-12-25 18:22:22 +00:00
with self.preserving_registers({'A'}, loads_a_within=True):
2017-12-21 13:52:30 +00:00
self.p("\t\tlda " + r_str)
2017-12-25 11:58:52 +00:00
self.p("\t\tsta " + l_str)
2017-12-21 13:52:30 +00:00
elif lv.datatype == DataType.WORD:
if rvalue.datatype == DataType.BYTE:
2017-12-25 18:22:22 +00:00
with self.preserving_registers({'A'}, loads_a_within=True):
2017-12-21 13:52:30 +00:00
self.p("\t\tlda " + r_str)
2017-12-23 13:36:23 +00:00
self.p("\t\tsta " + l_str)
self.p("\t\tlda #0")
2017-12-21 13:52:30 +00:00
self.p("\t\tsta {:s}+1".format(l_str))
elif rvalue.datatype == DataType.WORD:
2017-12-25 18:22:22 +00:00
with self.preserving_registers({'A'}, loads_a_within=True):
2017-12-21 13:52:30 +00:00
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:
2017-12-23 13:36:23 +00:00
raise CodeError("can only assign a byte or word to a word", str(rvalue))
2017-12-25 11:58:52 +00:00
elif lv.datatype == DataType.FLOAT:
if rvalue.datatype == DataType.FLOAT:
2017-12-30 12:34:52 +00:00
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\tlda #<" + r_str)
2017-12-30 19:03:19 +00:00
self.p("\t\tsta c64.SCRATCH_ZPWORD1")
2017-12-30 12:34:52 +00:00
self.p("\t\tlda #>" + r_str)
2017-12-30 19:03:19 +00:00
self.p("\t\tsta c64.SCRATCH_ZPWORD1+1")
2017-12-30 12:34:52 +00:00
self.p("\t\tldx #<" + l_str)
self.p("\t\tldy #>" + l_str)
2018-01-01 16:56:55 +00:00
self.p("\t\tjsr c64flt.copy_mflt")
2017-12-25 11:58:52 +00:00
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:
2017-12-25 18:22:22 +00:00
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
2017-12-25 11:58:52 +00:00
self.p("\t\tlda " + r_str)
self.p("\t\tldy {:s}+1".format(r_str))
2018-01-01 16:56:55 +00:00
self.p("\t\tjsr c64flt.GIVUAYF") # uword AY -> fac1
2017-12-25 11:58:52 +00:00
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))
2017-12-21 13:52:30 +00:00
else:
2017-12-25 11:58:52 +00:00
raise CodeError("invalid lvalue memmapped datatype", str(lv))
2017-12-21 13:52:30 +00:00
2017-12-31 03:45:27 +00:00
def generate_assign_char_to_memory(self, lv: MemMappedValue, char_str: str) -> None:
2017-12-21 13:52:30 +00:00
# Memory = Character
2017-12-25 18:22:22 +00:00
with self.preserving_registers({'A'}, loads_a_within=True):
2017-12-21 13:52:30 +00:00
self.p("\t\tlda #" + char_str)
if not lv.name:
self.p("\t\tsta " + Parser.to_hex(lv.address))
2017-12-21 13:52:30 +00:00
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:
2017-12-23 00:53:48 +00:00
assign_target = symblock.label + '.' + sym.name
2017-12-21 13:52:30 +00:00
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:
2017-12-27 18:01:14 +00:00
raise CodeError("invalid lvalue type " + str(sym))
2017-12-21 13:52:30 +00:00
else:
2017-12-27 18:01:14 +00:00
raise CodeError("invalid lvalue type " + str(sym))
2017-12-21 13:52:30 +00:00
2017-12-31 03:45:27 +00:00
def generate_assign_integer_to_reg(self, l_register: str, rvalue: IntegerValue) -> None:
2017-12-21 13:52:30 +00:00
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")
2017-12-25 20:43:06 +00:00
elif l_register == "SI":
# interrupt disable bit
if rvalue.value:
self.p("\t\tsei")
else:
self.p("\t\tcli")
2017-12-21 13:52:30 +00:00
else:
raise CodeError("invalid register in immediate integer assignment", l_register, rvalue.value)
2017-12-31 03:45:27 +00:00
def generate_assign_char_to_reg(self, lv: RegisterValue, char_str: str) -> None:
2017-12-21 13:52:30 +00:00
# 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))
2017-12-31 03:45:27 +00:00
def generate_assign_string_to_reg(self, lv: RegisterValue, rvalue: StringValue) -> None:
2017-12-21 13:52:30 +00:00
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 should be a string variable")
2017-12-31 03:45:27 +00:00
def generate_assign_string_to_memory(self, lv: MemMappedValue, rvalue: StringValue) -> None:
2017-12-21 13:52:30 +00:00
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)
2017-12-21 13:52:30 +00:00
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 should 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:
2017-12-28 18:08:33 +00:00
if char == '\f':
2017-12-21 13:52:30 +00:00
result += "{clear}"
2017-12-28 18:08:33 +00:00
elif char == '\b':
2017-12-21 13:52:30 +00:00
result += "{delete}"
2017-12-28 18:08:33 +00:00
elif char == '\n':
2017-12-21 13:52:30 +00:00
result += "{cr}"
2017-12-28 18:08:33 +00:00
elif char == '\r':
result += "{down}"
elif char == '\t':
2017-12-21 13:52:30 +00:00
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:
2017-12-27 22:45:22 +00:00
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]
2017-12-21 13:52:30 +00:00
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:
2017-12-28 18:08:33 +00:00
print("\ncreating C-64 prg")
2017-12-21 13:52:30 +00:00
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))
2017-12-21 13:52:30 +00:00
except subprocess.CalledProcessError as x:
2017-12-28 18:08:33 +00:00
raise SystemExit("assembler failed with returncode " + str(x.returncode))
2017-12-27 22:45:22 +00:00
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