mirror of
https://github.com/irmen/prog8.git
synced 2024-11-01 00:10:48 +00:00
1019 lines
52 KiB
Python
1019 lines
52 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, see LICENSE
|
|
"""
|
|
|
|
import io
|
|
import math
|
|
import datetime
|
|
import subprocess
|
|
import contextlib
|
|
from functools import partial
|
|
from typing import TextIO, Set, Union
|
|
from .parse import ProgramFormat, ParseResult, Parser
|
|
from .symbols import Zeropage, DataType, ConstantDef, VariableDef, SubroutineDef, \
|
|
STRING_DATATYPES, REGISTER_WORDS, FLOAT_MAX_NEGATIVE, FLOAT_MAX_POSITIVE
|
|
|
|
|
|
class CodeError(Exception):
|
|
pass
|
|
|
|
|
|
class CodeGenerator:
|
|
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: ParseResult.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, ParseResult.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("; save zp")
|
|
self.p("\t\tsei")
|
|
self.p("\t\tldx #2")
|
|
self.p("-\t\tlda $00,x")
|
|
self.p("\t\tsta _il65_zp_backup-2,x")
|
|
self.p("\t\tinx")
|
|
self.p("\t\tbne -")
|
|
|
|
# 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:
|
|
raise TypeError("floats cannot be stored in the zp")
|
|
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("; restore zp")
|
|
self.p("\t\tcld")
|
|
self.p("\t\tphp\n\t\tpha\n\t\ttxa\n\t\tpha\n\t\tsei")
|
|
self.p("\t\tldx #2")
|
|
self.p("-\t\tlda _il65_zp_backup-2,x")
|
|
self.p("\t\tsta $00,x")
|
|
self.p("\t\tinx")
|
|
self.p("\t\tbne -")
|
|
self.p("\t\tcli\n\t\tpla\n\t\ttax\n\t\tpla\n\t\tplp")
|
|
self.p("\t\trts")
|
|
self.p("_il65_zp_backup\t\t.fill 254, 0")
|
|
else:
|
|
self.p("\t\tjmp {:s}.start\t\t; call user code".format(main_block_label))
|
|
|
|
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, ParseResult.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, ParseResult.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, ParseResult.Label) and stmt.name == "start":
|
|
asmlines = [
|
|
"\t\tcld\t\t\t; clear decimal flag",
|
|
"\t\tclc\t\t\t; clear carry flag"
|
|
]
|
|
statements.insert(index+1, ParseResult.InlineAsm(0, asmlines))
|
|
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: ParseResult.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 ValueError("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
|
|
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}".format(vardef.name, Parser.to_hex(int(vardef.value))))
|
|
elif vardef.type == DataType.WORD:
|
|
self.p("{:s}\t\t.word {:s}".format(vardef.name, Parser.to_hex(int(vardef.value))))
|
|
elif vardef.type == DataType.FLOAT:
|
|
self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}"
|
|
.format(vardef.name, *self.to_mflpt5(float(vardef.value))))
|
|
else:
|
|
raise TypeError("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}".format(vardef.name, vardef.length, vardef.value or 0))
|
|
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 TypeError("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: ParseResult._AstNode) -> None:
|
|
if isinstance(stmt, ParseResult.ReturnStmt):
|
|
if stmt.a:
|
|
if isinstance(stmt.a, ParseResult.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, ParseResult.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, ParseResult.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, ParseResult.AssignmentStmt):
|
|
self.generate_assignment(stmt)
|
|
elif isinstance(stmt, ParseResult.Label):
|
|
self.p("\n{:s}\t\t\t\t; src l. {:d}".format(stmt.name, stmt.lineno))
|
|
elif isinstance(stmt, ParseResult.IncrDecrStmt):
|
|
self.generate_incrdecr(stmt)
|
|
elif isinstance(stmt, ParseResult.CallStmt):
|
|
self.generate_call(stmt)
|
|
elif isinstance(stmt, ParseResult.InlineAsm):
|
|
self.p("\t\t; inline asm, src l. {:d}".format(stmt.lineno))
|
|
for line in stmt.asmlines:
|
|
self.p(line)
|
|
self.p("\t\t; end inline asm, src l. {:d}".format(stmt.lineno))
|
|
elif isinstance(stmt, ParseResult.Comment):
|
|
self.p(stmt.text)
|
|
else:
|
|
raise CodeError("unknown statement " + repr(stmt))
|
|
self.previous_stmt_was_assignment = isinstance(stmt, ParseResult.AssignmentStmt)
|
|
|
|
def generate_incrdecr(self, stmt: ParseResult.IncrDecrStmt) -> None:
|
|
if stmt.howmuch in (-1, 1):
|
|
if isinstance(stmt.what, ParseResult.RegisterValue):
|
|
if stmt.howmuch == 1:
|
|
if stmt.what.register == 'A':
|
|
self.p("\t\tadc #1")
|
|
else:
|
|
self.p("\t\tin{:s}".format(stmt.what.register.lower()))
|
|
else:
|
|
if stmt.what.register == 'A':
|
|
self.p("\t\tsbc #1")
|
|
else:
|
|
self.p("\t\tde{:s}".format(stmt.what.register.lower()))
|
|
elif isinstance(stmt.what, (ParseResult.MemMappedValue, ParseResult.IndirectValue)):
|
|
what = stmt.what
|
|
if isinstance(what, ParseResult.IndirectValue):
|
|
if isinstance(what.value, ParseResult.IntegerValue):
|
|
r_str = what.value.name or Parser.to_hex(what.value.value)
|
|
else:
|
|
raise CodeError("invalid incr indirect type", what.value)
|
|
else:
|
|
r_str = what.name or Parser.to_hex(what.address)
|
|
if what.datatype == DataType.BYTE:
|
|
if stmt.howmuch == 1:
|
|
self.p("\t\tinc " + r_str)
|
|
else:
|
|
self.p("\t\tdec " + r_str)
|
|
elif what.datatype == DataType.WORD:
|
|
# @todo verify this asm code
|
|
if stmt.howmuch == 1:
|
|
self.p("\t\tinc " + r_str)
|
|
self.p("\t\tbne +")
|
|
self.p("\t\tinc {:s}+1".format(r_str))
|
|
self.p("+")
|
|
else:
|
|
self.p("\t\tdec " + r_str)
|
|
self.p("\t\tbne +")
|
|
self.p("\t\tdec {:s}+1".format(r_str))
|
|
self.p("+")
|
|
else:
|
|
raise CodeError("cannot in/decrement memory of type " + str(what.datatype))
|
|
else:
|
|
raise CodeError("cannot in/decrement " + str(stmt.what))
|
|
elif stmt.howmuch > 0:
|
|
raise NotImplementedError("incr by > 1") # XXX
|
|
elif stmt.howmuch < 0:
|
|
raise NotImplementedError("decr by > 1") # XXX
|
|
|
|
def generate_call(self, stmt: ParseResult.CallStmt) -> None:
|
|
def generate_param_assignments() -> None:
|
|
for assign_stmt in stmt.desugared_call_arguments:
|
|
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, ParseResult.RegisterValue):
|
|
if lv.register == 'A':
|
|
return True
|
|
return False
|
|
|
|
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, ParseResult.MemMappedValue):
|
|
targetstr = stmt.target.name or Parser.to_hex(stmt.address)
|
|
else:
|
|
raise TypeError("call sub target should be mmapped")
|
|
if stmt.is_goto:
|
|
generate_param_assignments()
|
|
self.p("\t\tjmp " + targetstr)
|
|
return
|
|
clobbered = set() # type: Set[str]
|
|
if targetdef.clobbered_registers:
|
|
if stmt.preserve_regs:
|
|
clobbered = targetdef.clobbered_registers
|
|
with self.preserving_registers(clobbered, loads_a_within=params_load_a()):
|
|
generate_param_assignments()
|
|
self.p("\t\tjsr " + targetstr)
|
|
return
|
|
if isinstance(stmt.target, ParseResult.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, ParseResult.RegisterValue):
|
|
targetstr = stmt.target.value.register
|
|
elif isinstance(stmt.target.value, ParseResult.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)))
|
|
self.p("\t\tjmp ({:s})".format(Parser.to_hex(Zeropage.SCRATCH_B1)))
|
|
else:
|
|
self.p("\t\tjmp ({:s})".format(targetstr))
|
|
else:
|
|
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
|
|
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a()):
|
|
generate_param_assignments()
|
|
if targetstr in REGISTER_WORDS:
|
|
if stmt.preserve_regs:
|
|
# cannot use zp scratch. This is very inefficient code!
|
|
print("warning: {:s}:{:d}: indirect register pair call, this is very inefficient"
|
|
.format(self.cur_block.sourceref.file, stmt.lineno))
|
|
self.p("\t\tst{:s} ++".format(targetstr[0].lower()))
|
|
self.p("\t\tst{:s} +++".format(targetstr[1].lower()))
|
|
self.p("\t\tjsr +")
|
|
self.p("\t\tjmp ++++")
|
|
self.p("+\t\tjmp (+)")
|
|
self.p("+\t\t.byte 0\t; lo")
|
|
self.p("+\t\t.byte 0\t; hi")
|
|
self.p("+")
|
|
else:
|
|
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)))
|
|
self.p("\t\tjsr +")
|
|
self.p("\t\tjmp ++")
|
|
self.p("+\t\tjmp ({:s})".format(Parser.to_hex(Zeropage.SCRATCH_B1)))
|
|
self.p("+")
|
|
else:
|
|
self.p("\t\tjsr +")
|
|
self.p("\t\tjmp ++")
|
|
self.p("+\t\tjmp ({:s})".format(targetstr))
|
|
self.p("+")
|
|
else:
|
|
if stmt.target.name:
|
|
targetstr = stmt.target.name
|
|
elif stmt.address is not None:
|
|
targetstr = Parser.to_hex(stmt.address)
|
|
elif isinstance(stmt.target, ParseResult.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()
|
|
self.p("\t\tjmp " + targetstr)
|
|
else:
|
|
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
|
|
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a()):
|
|
generate_param_assignments()
|
|
self.p("\t\tjsr " + targetstr)
|
|
|
|
def generate_assignment(self, stmt: ParseResult.AssignmentStmt) -> None:
|
|
def unwrap_indirect(iv: ParseResult.IndirectValue) -> ParseResult.MemMappedValue:
|
|
if isinstance(iv.value, ParseResult.MemMappedValue):
|
|
return iv.value
|
|
elif iv.value.constant and isinstance(iv.value, ParseResult.IntegerValue):
|
|
return ParseResult.MemMappedValue(iv.value.value, iv.datatype, 1, iv.name)
|
|
else:
|
|
raise CodeError("cannot yet generate code for assignment: non-constant and non-memmapped indirect") # XXX
|
|
|
|
rvalue = stmt.right
|
|
if isinstance(rvalue, ParseResult.IndirectValue):
|
|
rvalue = unwrap_indirect(rvalue)
|
|
self.p("\t\t\t\t\t; src l. {:d}".format(stmt.lineno))
|
|
if isinstance(rvalue, ParseResult.IntegerValue):
|
|
for lv in stmt.leftvalues:
|
|
if isinstance(lv, ParseResult.RegisterValue):
|
|
self.generate_assign_integer_to_reg(lv.register, rvalue)
|
|
elif isinstance(lv, ParseResult.MemMappedValue):
|
|
self.generate_assign_integer_to_mem(lv, rvalue)
|
|
elif isinstance(lv, ParseResult.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, ParseResult.RegisterValue):
|
|
for lv in stmt.leftvalues:
|
|
if isinstance(lv, ParseResult.RegisterValue):
|
|
self.generate_assign_reg_to_reg(lv, rvalue.register)
|
|
elif isinstance(lv, ParseResult.MemMappedValue):
|
|
self.generate_assign_reg_to_memory(lv, rvalue.register)
|
|
elif isinstance(lv, ParseResult.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, ParseResult.StringValue):
|
|
r_str = self.output_string(rvalue.value, True)
|
|
for lv in stmt.leftvalues:
|
|
if isinstance(lv, ParseResult.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, ParseResult.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, ParseResult.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, ParseResult.MemMappedValue):
|
|
for lv in stmt.leftvalues:
|
|
if isinstance(lv, ParseResult.RegisterValue):
|
|
self.generate_assign_mem_to_reg(lv.register, rvalue)
|
|
elif isinstance(lv, ParseResult.MemMappedValue):
|
|
self.generate_assign_mem_to_mem(lv, rvalue)
|
|
elif isinstance(lv, ParseResult.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, ParseResult.FloatValue):
|
|
for lv in stmt.leftvalues:
|
|
if isinstance(lv, ParseResult.MemMappedValue) and lv.datatype == DataType.FLOAT:
|
|
self.generate_assign_float_to_mem(lv, rvalue)
|
|
elif isinstance(lv, ParseResult.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: ParseResult.MemMappedValue,
|
|
rvalue: Union[ParseResult.FloatValue, ParseResult.IntegerValue], save_reg: bool=True) -> None:
|
|
floatvalue = float(rvalue.value)
|
|
mflpt = self.to_mflpt5(floatvalue)
|
|
target = mmv.name or Parser.to_hex(mmv.address)
|
|
if save_reg:
|
|
self.p("\t\tpha\t\t\t; {:s} = {}".format(target, rvalue.name or floatvalue))
|
|
else:
|
|
self.p("\t\t\t\t\t; {:s} = {}".format(target, rvalue.name or floatvalue))
|
|
for num in range(5):
|
|
self.p("\t\tlda #${:02x}".format(mflpt[num]))
|
|
self.p("\t\tsta {:s}+{:d}".format(target, num))
|
|
if save_reg:
|
|
self.p("\t\tpla")
|
|
|
|
def generate_assign_reg_to_memory(self, lv: ParseResult.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 c64util.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: ParseResult.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":
|
|
self.p("\t\ttxa")
|
|
# 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 == "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":
|
|
self.p("\t\ttxa")
|
|
elif lv.register == "XY" and r_register == "AX":
|
|
self.p("\t\ttax")
|
|
# 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 == "XY" and r_register == "AY":
|
|
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):
|
|
# this clobbers a ZP scratch register and is therefore NOT safe to use in interrupts
|
|
# see http://6502.org/tutorials/register_preservation.html
|
|
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 'Y' in registers:
|
|
self.p("\t\tpla\n\t\ttay")
|
|
if 'X' in registers:
|
|
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: ParseResult.MemMappedValue, rvalue: ParseResult.IntegerValue) -> None:
|
|
if lv.name:
|
|
symblock, sym = self.cur_block.lookup(lv.name)
|
|
if not isinstance(sym, VariableDef):
|
|
raise TypeError("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 ValueError("value cannot be assigned to a float")
|
|
self.generate_assign_float_to_mem(lv, rvalue, False)
|
|
else:
|
|
raise TypeError("invalid lvalue type " + str(lvdatatype))
|
|
|
|
def generate_assign_mem_to_reg(self, l_register: str, rvalue: ParseResult.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: ParseResult.MemMappedValue, rvalue: ParseResult.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'}, loads_a_within=True):
|
|
self.p("\t\tlda " + r_str)
|
|
self.p("\t\tsta " + l_str)
|
|
self.p("\t\tlda {:s}+1".format(r_str))
|
|
self.p("\t\tsta {:s}+1".format(l_str))
|
|
self.p("\t\tlda {:s}+2".format(r_str))
|
|
self.p("\t\tsta {:s}+2".format(l_str))
|
|
self.p("\t\tlda {:s}+3".format(r_str))
|
|
self.p("\t\tsta {:s}+3".format(l_str))
|
|
self.p("\t\tlda {:s}+4".format(r_str))
|
|
self.p("\t\tsta {:s}+4".format(l_str))
|
|
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 c64util.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: ParseResult.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 TypeError("invalid lvalue type " + str(sym))
|
|
else:
|
|
raise TypeError("invalid lvalue type " + str(sym))
|
|
|
|
def generate_assign_integer_to_reg(self, l_register: str, rvalue: ParseResult.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")
|
|
else:
|
|
raise CodeError("invalid register in immediate integer assignment", l_register, rvalue.value)
|
|
|
|
def generate_assign_char_to_reg(self, lv: ParseResult.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: ParseResult.RegisterValue, rvalue: ParseResult.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 should be a string variable")
|
|
|
|
def generate_assign_string_to_memory(self, lv: ParseResult.MemMappedValue, rvalue: ParseResult.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 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:
|
|
if char == "\f":
|
|
result += "{clear}"
|
|
elif char == "\b":
|
|
result += "{delete}"
|
|
elif char == "\n":
|
|
result += "{lf}"
|
|
elif char == "\r":
|
|
result += "{cr}"
|
|
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",
|
|
"--labels", outputfilename+".labels.txt", "--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:
|
|
print("assembler failed with returncode", x.returncode)
|