prog8/il65/oldstuff/codegen.py
Irmen de Jong 938c541cc2 codegen
2018-02-04 22:47:50 +01:00

827 lines
44 KiB
Python

# old deprecated code, in the process of moving this to the new emit/... modules
class CodeGenerator:
BREAKPOINT_COMMENT_SIGNATURE = "~~~BREAKPOINT~~~"
BREAKPOINT_COMMENT_DETECTOR = r".(?P<address>\w+)\s+ea\s+nop\s+;\s+{:s}.*".format(BREAKPOINT_COMMENT_SIGNATURE)
def generate_call(self, stmt: CallStmt) -> None:
self.p("\t\t\t\t\t; " + stmt.lineref)
if stmt.condition:
assert stmt.is_goto
if stmt.condition.lvalue:
if stmt.condition.comparison_op:
self._generate_goto_conditional_comparison(stmt)
else:
self._generate_goto_conditional_truthvalue(stmt)
else:
self._generate_goto_conditional_if(stmt)
else:
# unconditional goto or subroutine call.
def branch_emitter(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
if is_goto:
if target_indirect:
self.p("\t\tjmp ({:s})".format(targetstr))
else:
self.p("\t\tjmp {:s}".format(targetstr))
else:
assert not target_indirect
self.p("\t\tjsr " + targetstr)
self._generate_call_or_goto(stmt, branch_emitter)
def _generate_goto_conditional_if(self, stmt):
# a goto with just an if-condition, no condition expression
def branch_emitter(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
assert is_goto and not stmt.condition.comparison_op
ifs = stmt.condition.ifstatus
if target_indirect:
if ifs == "true":
self.p("\t\tbeq +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs in ("not", "zero"):
self.p("\t\tbne +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs in ("cc", "cs", "vc", "vs", "eq", "ne"):
if ifs == "cc":
self.p("\t\tbcs +")
elif ifs == "cs":
self.p("\t\tbcc +")
elif ifs == "vc":
self.p("\t\tbvs +")
elif ifs == "vs":
self.p("\t\tbvc +")
elif ifs == "eq":
self.p("\t\tbne +")
elif ifs == "ne":
self.p("\t\tbeq +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs == "lt":
self.p("\t\tbcs +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs == "gt":
self.p("\t\tbcc +")
self.p("\t\tbeq +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs == "ge":
self.p("\t\tbcc +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs == "le":
self.p("\t\tbeq +")
self.p("\t\tbcs ++")
self.p("+\t\tjmp ({:s})".format(targetstr))
self.p("+")
else:
raise CodeError("invalid if status " + ifs)
else:
if ifs == "true":
self.p("\t\tbne " + targetstr)
elif ifs in ("not", "zero"):
self.p("\t\tbeq " + targetstr)
elif ifs in ("cc", "cs", "vc", "vs", "eq", "ne"):
self.p("\t\tb{:s} {:s}".format(ifs, targetstr))
elif ifs == "pos":
self.p("\t\tbpl " + targetstr)
elif ifs == "neg":
self.p("\t\tbmi " + targetstr)
elif ifs == "lt":
self.p("\t\tbcc " + targetstr)
elif ifs == "gt":
self.p("\t\tbeq +")
self.p("\t\tbcs " + targetstr)
self.p("+")
elif ifs == "ge":
self.p("\t\tbcs " + targetstr)
elif ifs == "le":
self.p("\t\tbcc " + targetstr)
self.p("\t\tbeq " + targetstr)
else:
raise CodeError("invalid if status " + ifs)
self._generate_call_or_goto(stmt, branch_emitter)
def _generate_goto_conditional_truthvalue(self, stmt: CallStmt) -> None:
# the condition is just the 'truth value' of the single value,
# this is translated into assembly by comparing the argument to zero.
def branch_emitter_mmap(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
assert is_goto and not stmt.condition.comparison_op
assert stmt.condition.lvalue and not stmt.condition.rvalue
assert not target_indirect
assert stmt.condition.ifstatus in ("true", "not", "zero")
branch, inverse_branch = ("bne", "beq") if stmt.condition.ifstatus == "true" else ("beq", "bne")
cv = stmt.condition.lvalue
assert isinstance(cv, MemMappedValue)
cv_str = cv.name or Parser.to_hex(cv.address)
if cv.datatype == DataType.BYTE:
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + cv_str)
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
elif cv.datatype == DataType.WORD:
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + cv_str)
if stmt.condition.ifstatus == "true":
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda {:s}+1".format(cv_str))
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
else:
self.p("\t\t{:s} +".format(inverse_branch, targetstr))
self.p("\t\tlda {:s}+1".format(cv_str))
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("+\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
else:
raise CodeError("conditions cannot yet use other types than byte or word", # @todo comparisons of other types
cv.datatype, str(cv), stmt.sourceref)
def branch_emitter_reg(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
assert is_goto and not stmt.condition.comparison_op
assert stmt.condition.lvalue and not stmt.condition.rvalue
assert not target_indirect
assert stmt.condition.ifstatus in ("true", "not", "zero")
branch, inverse_branch = ("bne", "beq") if stmt.condition.ifstatus == "true" else ("beq", "bne")
line_after_branch = ""
cv = stmt.condition.lvalue
assert isinstance(cv, RegisterValue)
if cv.register == 'A':
self.p("\t\tcmp #0")
elif cv.register == 'X':
self.p("\t\tcpx #0")
elif cv.register == 'Y':
self.p("\t\tcpy #0")
else:
if cv.register == 'AX':
line_after_branch = "+"
self.p("\t\tcmp #0")
self.p("\t\t{:s} {:s}".format(inverse_branch, line_after_branch))
self.p("\t\tcpx #0")
elif cv.register == 'AY':
line_after_branch = "+"
self.p("\t\tcmp #0")
self.p("\t\t{:s} {:s}".format(inverse_branch, line_after_branch))
self.p("\t\tcpy #0")
elif cv.register == 'XY':
line_after_branch = "+"
self.p("\t\tcpx #0")
self.p("\t\t{:s} {:s}".format(inverse_branch, line_after_branch))
self.p("\t\tcpy #0")
else:
raise CodeError("invalid register", cv.register)
self.p("\t\t{:s} {:s}".format(branch, targetstr))
if line_after_branch:
self.p(line_after_branch)
def branch_emitter_indirect_cond(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
assert is_goto and not stmt.condition.comparison_op
assert stmt.condition.lvalue and not stmt.condition.rvalue
assert stmt.condition.ifstatus in ("true", "not", "zero")
assert not target_indirect
cv = stmt.condition.lvalue.value # type: ignore
if isinstance(cv, RegisterValue):
branch = "bne" if stmt.condition.ifstatus == "true" else "beq"
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
if cv.register == 'Y':
self.p("\t\tlda ($00),y")
elif cv.register == 'X':
self.p("\t\tstx *+2\t; self-modify")
self.p("\t\tlda $ff")
elif cv.register == 'A':
self.p("\t\tsta *+2\t; self-modify")
self.p("\t\tlda $ff")
else:
self.p("\t\tst{:s} (+)+1\t; self-modify".format(cv.register[0].lower()))
self.p("\t\tst{:s} (+)+2\t; self-modify".format(cv.register[1].lower()))
self.p("+\t\tlda $ffff")
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
elif isinstance(cv, MemMappedValue):
raise CodeError("memmapped indirect should not occur, use the variable without indirection")
elif isinstance(cv, IntegerValue) and cv.constant:
branch, inverse_branch = ("bne", "beq") if stmt.condition.ifstatus == "true" else ("beq", "bne")
cv_str = cv.name or Parser.to_hex(cv.value)
if cv.datatype == DataType.BYTE:
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + cv_str)
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
elif cv.datatype == DataType.WORD:
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + cv_str)
if stmt.condition.ifstatus == "true":
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda {:s}+1".format(cv_str))
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
else:
self.p("\t\t{:s} +".format(inverse_branch, targetstr))
self.p("\t\tlda {:s}+1".format(cv_str))
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("+\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
else:
raise CodeError("conditions cannot yet use other types than byte or word", # @todo comparisons of other types
cv.datatype, str(cv), stmt.sourceref)
else:
raise CodeError("weird indirect type", str(cv))
cv = stmt.condition.lvalue
if isinstance(cv, RegisterValue):
self._generate_call_or_goto(stmt, branch_emitter_reg)
elif isinstance(cv, MemMappedValue):
self._generate_call_or_goto(stmt, branch_emitter_mmap)
elif isinstance(cv, IndirectValue):
if isinstance(cv.value, RegisterValue):
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
elif isinstance(cv.value, MemMappedValue):
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
elif isinstance(cv.value, IntegerValue) and cv.value.constant:
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
else:
raise CodeError("weird indirect type", str(cv))
else:
raise CodeError("need register, memmapped or indirect value", str(cv))
def _generate_goto_conditional_comparison(self, stmt: CallStmt) -> None:
# the condition is lvalue operator rvalue
raise NotImplementedError("no comparisons yet") # XXX comparisons
assert stmt.condition.ifstatus in ("true", "not", "zero")
assert stmt.condition.lvalue != stmt.condition.rvalue # so we know we actually have to compare different things
lv, compare_operator, rv = stmt.condition.lvalue, stmt.condition.comparison_op, stmt.condition.rvalue
if lv.constant and not rv.constant:
# if lv is a constant, swap the whole thing around so the constant is on the right
lv, compare_operator, rv = stmt.condition.swap()
if isinstance(rv, RegisterValue):
# if rv is a register, make sure it comes first instead
lv, compare_operator, rv = stmt.condition.swap()
if lv.datatype != DataType.BYTE or rv.datatype != DataType.BYTE:
raise CodeError("can only generate comparison code for byte values for now") # @todo compare non-bytes
if isinstance(lv, RegisterValue):
if isinstance(rv, RegisterValue):
self.p("\t\tst{:s} {:s}".format(rv.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
if lv.register == "A":
self.p("\t\tcmp " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lv.register == "X":
self.p("\t\tcpx " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lv.register == "Y":
self.p("\t\tcpy " + Parser.to_hex(Zeropage.SCRATCH_B1))
else:
raise CodeError("wrong lvalue register")
elif isinstance(rv, IntegerValue):
rvstr = rv.name or Parser.to_hex(rv.value)
if lv.register == "A":
self.p("\t\tcmp #" + rvstr)
elif lv.register == "X":
self.p("\t\tcpx #" + rvstr)
elif lv.register == "Y":
self.p("\t\tcpy #" + rvstr)
else:
raise CodeError("wrong lvalue register")
elif isinstance(rv, MemMappedValue):
rvstr = rv.name or Parser.to_hex(rv.address)
if lv.register == "A":
self.p("\t\tcmp " + rvstr)
elif lv.register == "X":
self.p("\t\tcpx #" + rvstr)
elif lv.register == "Y":
self.p("\t\tcpy #" + rvstr)
else:
raise CodeError("wrong lvalue register")
else:
raise CodeError("invalid rvalue type in comparison", rv)
elif isinstance(lv, MemMappedValue):
assert not isinstance(rv, RegisterValue), "registers as rvalue should have been swapped with lvalue"
if isinstance(rv, IntegerValue):
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + (lv.name or Parser.to_hex(lv.address)))
self.p("\t\tcmp #" + (rv.name or Parser.to_hex(rv.value)))
line_after_goto = "\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
elif isinstance(rv, MemMappedValue):
rvstr = rv.name or Parser.to_hex(rv.address)
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + (lv.name or Parser.to_hex(lv.address)))
self.p("\t\tcmp " + rvstr)
line_after_goto = "\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
else:
raise CodeError("invalid rvalue type in comparison", rv)
else:
raise CodeError("invalid lvalue type in comparison", lv)
def _generate_call_or_goto(self, stmt: CallStmt, branch_emitter: Callable[[str, bool, bool], None]) -> None:
def generate_param_assignments() -> None:
for assign_stmt in stmt.desugared_call_arguments:
self.generate_assignment(assign_stmt)
def generate_result_assignments() -> None:
for assign_stmt in stmt.desugared_output_assignments:
self.generate_assignment(assign_stmt)
def params_load_a() -> bool:
for assign_stmt in stmt.desugared_call_arguments:
for lv in assign_stmt.leftvalues:
if isinstance(lv, RegisterValue):
if lv.register == 'A':
return True
return False
def unclobber_result_registers(registers: Set[str], output_assignments: List[AssignmentStmt]) -> Set[str]:
result = registers.copy()
for a in output_assignments:
for lv in a.leftvalues:
if isinstance(lv, RegisterValue):
if len(lv.register) == 1:
result.discard(lv.register)
else:
for r in lv.register:
result.discard(r)
return result
if stmt.target.name:
symblock, targetdef = self.cur_block.lookup(stmt.target.name)
else:
symblock = None
targetdef = None
if isinstance(targetdef, SubroutineDef):
if isinstance(stmt.target, MemMappedValue):
targetstr = stmt.target.name or Parser.to_hex(stmt.address)
else:
raise CodeError("call sub target must be mmapped")
if stmt.is_goto:
generate_param_assignments()
branch_emitter(targetstr, True, False)
# no result assignments because it's a goto
return
clobbered = set() # type: Set[str]
if targetdef.clobbered_registers:
if stmt.preserve_regs is not None:
clobbered = targetdef.clobbered_registers & stmt.preserve_regs
clobbered = unclobber_result_registers(clobbered, stmt.desugared_output_assignments)
with self.preserving_registers(clobbered, loads_a_within=params_load_a(), always_preserve=stmt.preserve_regs is not None):
generate_param_assignments()
branch_emitter(targetstr, False, False)
generate_result_assignments()
return
if isinstance(stmt.target, IndirectValue):
if stmt.target.name:
targetstr = stmt.target.name
elif stmt.address is not None:
targetstr = Parser.to_hex(stmt.address)
elif stmt.target.value.name:
targetstr = stmt.target.value.name
elif isinstance(stmt.target.value, RegisterValue):
targetstr = stmt.target.value.register
elif isinstance(stmt.target.value, IntegerValue):
targetstr = stmt.target.value.name or Parser.to_hex(stmt.target.value.value)
else:
raise CodeError("missing name", stmt.target.value)
if stmt.is_goto:
# no need to preserve registers for a goto
generate_param_assignments()
if targetstr in REGISTER_WORDS:
self.p("\t\tst{:s} {:s}".format(targetstr[0].lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tst{:s} {:s}".format(targetstr[1].lower(), Parser.to_hex(Zeropage.SCRATCH_B2)))
branch_emitter(Parser.to_hex(Zeropage.SCRATCH_B1), True, True)
else:
branch_emitter(targetstr, True, True)
# no result assignments because it's a goto
else:
# indirect call to subroutine
preserve_regs = unclobber_result_registers(stmt.preserve_regs or set(), stmt.desugared_output_assignments)
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a(),
always_preserve=stmt.preserve_regs is not None):
generate_param_assignments()
if targetstr in REGISTER_WORDS:
print("warning: {}: indirect register pair call is quite inefficient, use a jump table in memory instead?"
.format(stmt.sourceref))
if stmt.preserve_regs is not None:
# cannot use zp scratch because it may be used by the register backup. This is very inefficient code!
self.p("\t\tjsr il65_lib.jsr_indirect_nozpuse_"+targetstr)
else:
self.p("\t\tjsr il65_lib.jsr_indirect_"+targetstr)
else:
self.p("\t\tjsr +")
self.p("\t\tjmp ++")
self.p("+\t\tjmp ({:s})".format(targetstr))
self.p("+")
generate_result_assignments()
else:
# call to a label or immediate address
if stmt.target.name:
targetstr = stmt.target.name
elif stmt.address is not None:
targetstr = Parser.to_hex(stmt.address)
elif isinstance(stmt.target, IntegerValue):
targetstr = stmt.target.name or Parser.to_hex(stmt.target.value)
else:
raise CodeError("missing name", stmt.target)
if stmt.is_goto:
# no need to preserve registers for a goto
generate_param_assignments()
branch_emitter(targetstr, True, False)
# no result assignments because it's a goto
else:
preserve_regs = unclobber_result_registers(stmt.preserve_regs or set(), stmt.desugared_output_assignments)
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a(),
always_preserve=stmt.preserve_regs is not None):
generate_param_assignments()
branch_emitter(targetstr, False, False)
generate_result_assignments()
def generate_assignment(self, stmt: AssignmentStmt) -> None:
def unwrap_indirect(iv: IndirectValue) -> MemMappedValue:
if isinstance(iv.value, MemMappedValue):
return iv.value
elif iv.value.constant and isinstance(iv.value, IntegerValue):
return MemMappedValue(iv.value.value, iv.datatype, 1, stmt.sourceref, iv.name)
else:
raise CodeError("cannot yet generate code for assignment: non-constant and non-memmapped indirect") # XXX
rvalue = stmt.right
if isinstance(rvalue, IndirectValue):
rvalue = unwrap_indirect(rvalue)
self.p("\t\t\t\t\t; " + stmt.lineref)
if isinstance(rvalue, IntegerValue):
for lv in stmt.leftvalues:
if isinstance(lv, RegisterValue):
self.generate_assign_integer_to_reg(lv.register, rvalue)
elif isinstance(lv, MemMappedValue):
self.generate_assign_integer_to_mem(lv, rvalue)
elif isinstance(lv, IndirectValue):
lv = unwrap_indirect(lv)
self.generate_assign_integer_to_mem(lv, rvalue)
else:
raise CodeError("invalid assignment target (1)", str(stmt))
elif isinstance(rvalue, RegisterValue):
for lv in stmt.leftvalues:
if isinstance(lv, RegisterValue):
self.generate_assign_reg_to_reg(lv, rvalue.register)
elif isinstance(lv, MemMappedValue):
self.generate_assign_reg_to_memory(lv, rvalue.register)
elif isinstance(lv, IndirectValue):
lv = unwrap_indirect(lv)
self.generate_assign_reg_to_memory(lv, rvalue.register)
else:
raise CodeError("invalid assignment target (2)", str(stmt))
elif isinstance(rvalue, StringValue):
r_str = self.output_string(rvalue.value, True)
for lv in stmt.leftvalues:
if isinstance(lv, RegisterValue):
if len(rvalue.value) == 1:
self.generate_assign_char_to_reg(lv, r_str)
else:
self.generate_assign_string_to_reg(lv, rvalue)
elif isinstance(lv, MemMappedValue):
if len(rvalue.value) == 1:
self.generate_assign_char_to_memory(lv, r_str)
else:
self.generate_assign_string_to_memory(lv, rvalue)
elif isinstance(lv, IndirectValue):
lv = unwrap_indirect(lv)
if len(rvalue.value) == 1:
self.generate_assign_char_to_memory(lv, r_str)
else:
self.generate_assign_string_to_memory(lv, rvalue)
else:
raise CodeError("invalid assignment target (2)", str(stmt))
elif isinstance(rvalue, MemMappedValue):
for lv in stmt.leftvalues:
if isinstance(lv, RegisterValue):
self.generate_assign_mem_to_reg(lv.register, rvalue)
elif isinstance(lv, MemMappedValue):
self.generate_assign_mem_to_mem(lv, rvalue)
elif isinstance(lv, IndirectValue):
lv = unwrap_indirect(lv)
self.generate_assign_mem_to_mem(lv, rvalue)
else:
raise CodeError("invalid assignment target (4)", str(stmt))
elif isinstance(rvalue, FloatValue):
for lv in stmt.leftvalues:
if isinstance(lv, MemMappedValue) and lv.datatype == DataType.FLOAT:
self.generate_assign_float_to_mem(lv, rvalue)
elif isinstance(lv, IndirectValue):
lv = unwrap_indirect(lv)
assert lv.datatype == DataType.FLOAT
self.generate_assign_float_to_mem(lv, rvalue)
else:
raise CodeError("cannot assign float to ", str(lv))
else:
raise CodeError("invalid assignment value type", str(stmt))
def generate_assign_float_to_mem(self, mmv: MemMappedValue,
rvalue: Union[FloatValue, IntegerValue]) -> None:
floatvalue = float(rvalue.value)
mflpt = self.to_mflpt5(floatvalue)
target = mmv.name or Parser.to_hex(mmv.address)
with self.preserving_registers({'A'}):
self.p("\t\t\t\t\t; {:s} = {}".format(target, rvalue.name or floatvalue))
a_reg_value = None
for i, byte in enumerate(mflpt):
if byte != a_reg_value:
self.p("\t\tlda #${:02x}".format(byte))
a_reg_value = byte
self.p("\t\tsta {:s}+{:d}".format(target, i))
def generate_assign_reg_to_memory(self, lv: MemMappedValue, r_register: str) -> None:
# Memory = Register
lv_string = lv.name or Parser.to_hex(lv.address)
if lv.datatype == DataType.BYTE:
if len(r_register) > 1:
raise CodeError("cannot assign register pair to single byte memory")
self.p("\t\tst{:s} {}".format(r_register.lower(), lv_string))
elif lv.datatype == DataType.WORD:
if len(r_register) == 1:
self.p("\t\tst{:s} {}".format(r_register.lower(), lv_string)) # lsb
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda #0")
self.p("\t\tsta {:s}+1".format(lv_string)) # msb
else:
self.p("\t\tst{:s} {}".format(r_register[0].lower(), lv_string))
self.p("\t\tst{:s} {}+1".format(r_register[1].lower(), lv_string))
elif lv.datatype == DataType.FLOAT:
# assigning a register to a float requires c64 ROM routines
if r_register in REGISTER_WORDS:
def do_rom_calls():
self.p("\t\tjsr c64flt.GIVUAYF") # uword AY -> fac1
self.p("\t\tldx #<" + lv_string)
self.p("\t\tldy #>" + lv_string)
self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY
if r_register == "AY":
with self.preserving_registers({'A', 'X', 'Y'}):
do_rom_calls()
elif r_register == "AX":
with self.preserving_registers({'A', 'X', 'Y'}):
self.p("\t\tpha\n\t\ttxa\n\t\ttay\n\t\tpla") # X->Y (so we have AY now)
do_rom_calls()
else: # XY
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\ttxa") # X->A (so we have AY now)
do_rom_calls()
elif r_register in "AXY":
def do_rom_calls():
self.p("\t\tjsr c64.FREADUY") # ubyte Y -> fac1
self.p("\t\tldx #<" + lv_string)
self.p("\t\tldy #>" + lv_string)
self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY
if r_register == "A":
with self.preserving_registers({'A', 'X', 'Y'}):
self.p("\t\ttay")
do_rom_calls()
elif r_register == "X":
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\ttxa")
self.p("\t\ttay")
do_rom_calls()
elif r_register == "Y":
with self.preserving_registers({'A', 'X', 'Y'}):
do_rom_calls()
else:
raise CodeError("invalid register to assign to float", r_register)
else:
raise CodeError("invalid lvalue type", lv.datatype)
def generate_assign_reg_to_reg(self, lv: RegisterValue, r_register: str) -> None:
if lv.register != r_register:
if lv.register == 'A': # x/y -> a
self.p("\t\tt{:s}a".format(r_register.lower()))
elif lv.register == 'Y':
if r_register == 'A':
# a -> y
self.p("\t\ttay")
else:
# x -> y, 6502 doesn't have txy
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == 'X':
if r_register == 'A':
# a -> x
self.p("\t\ttax")
else:
# y -> x, 6502 doesn't have tyx
self.p("\t\tsty ${0:02x}\n\t\tldx ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register in REGISTER_WORDS:
if len(r_register) == 1:
# assign one register to a pair, so the hi byte is zero.
if lv.register == "AX" and r_register == "A":
self.p("\t\tldx #0")
elif lv.register == "AX" and r_register == "X":
self.p("\t\ttxa\n\t\tldx #0")
elif lv.register == "AX" and r_register == "Y":
self.p("\t\ttya\n\t\tldx #0")
elif lv.register == "AY" and r_register == "A":
self.p("\t\tldy #0")
elif lv.register == "AY" and r_register == "X":
self.p("\t\ttxa\n\t\tldy #0")
elif lv.register == "AY" and r_register == "Y":
self.p("\t\ttya\n\t\tldy #0")
elif lv.register == "XY" and r_register == "A":
self.p("\t\ttax\n\t\tldy #0")
elif lv.register == "XY" and r_register == "X":
self.p("\t\tldy #0")
elif lv.register == "XY" and r_register == "Y":
self.p("\t\ttyx\n\t\tldy #0")
else:
raise CodeError("invalid register combination", lv.register, r_register)
elif lv.register == "AX" and r_register == "AY":
# y -> x, 6502 doesn't have tyx
self.p("\t\tsty ${0:02x}\n\t\tldx ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == "AX" and r_register == "XY":
# x -> a, y -> x, 6502 doesn't have tyx
self.p("\t\ttxa")
self.p("\t\tsty ${0:02x}\n\t\tldx ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == "AY" and r_register == "AX":
# x -> y, 6502 doesn't have txy
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == "AY" and r_register == "XY":
# x -> a
self.p("\t\ttxa")
elif lv.register == "XY" and r_register == "AX":
# x -> y, a -> x, 6502 doesn't have txy
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lv.register == "XY" and r_register == "AY":
# a -> x
self.p("\t\ttax")
else:
raise CodeError("invalid register combination", lv.register, r_register)
else:
raise CodeError("invalid register " + lv.register)
def generate_assign_integer_to_mem(self, lv: MemMappedValue, rvalue: IntegerValue) -> None:
if lv.name:
symblock, sym = self.cur_block.lookup(lv.name)
if not isinstance(sym, VariableDef):
raise CodeError("invalid lvalue type " + str(sym))
assign_target = symblock.label + '.' + sym.name if symblock is not self.cur_block else lv.name
lvdatatype = sym.type
else:
assign_target = Parser.to_hex(lv.address)
lvdatatype = lv.datatype
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.value)
if lvdatatype == DataType.BYTE:
if rvalue.value is not None and not lv.assignable_from(rvalue) or rvalue.datatype != DataType.BYTE:
raise OverflowError("value doesn't fit in a byte")
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda #" + r_str)
self.p("\t\tsta " + assign_target)
elif lvdatatype == DataType.WORD:
if rvalue.value is not None and not lv.assignable_from(rvalue):
raise OverflowError("value doesn't fit in a word")
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda #<" + r_str)
self.p("\t\tsta " + assign_target)
self.p("\t\tlda #>" + r_str)
self.p("\t\tsta {}+1".format(assign_target))
elif lvdatatype == DataType.FLOAT:
if rvalue.value is not None and not DataType.FLOAT.assignable_from_value(rvalue.value):
raise CodeError("value cannot be assigned to a float")
self.generate_assign_float_to_mem(lv, rvalue)
else:
raise CodeError("invalid lvalue type " + str(lvdatatype))
def generate_assign_mem_to_reg(self, l_register: str, rvalue: MemMappedValue) -> None:
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.address)
if len(l_register) == 1:
if rvalue.datatype != DataType.BYTE:
raise CodeError("can only assign a byte to a register")
self.p("\t\tld{:s} {:s}".format(l_register.lower(), r_str))
else:
if rvalue.datatype == DataType.BYTE:
self.p("\t\tld{:s} {:s}".format(l_register[0].lower(), r_str))
self.p("\t\tld{:s} #0".format(l_register[1].lower()))
elif rvalue.datatype == DataType.WORD:
self.p("\t\tld{:s} {:s}".format(l_register[0].lower(), r_str))
self.p("\t\tld{:s} {:s}+1".format(l_register[1].lower(), r_str))
else:
raise CodeError("can only assign a byte or word to a register pair")
def generate_assign_mem_to_mem(self, lv: MemMappedValue, rvalue: MemMappedValue) -> None:
r_str = rvalue.name or Parser.to_hex(rvalue.address)
l_str = lv.name or Parser.to_hex(lv.address)
if lv.datatype == DataType.BYTE:
if rvalue.datatype != DataType.BYTE:
raise CodeError("can only assign a byte to a byte", str(rvalue))
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda " + r_str)
self.p("\t\tsta " + l_str)
elif lv.datatype == DataType.WORD:
if rvalue.datatype == DataType.BYTE:
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda " + r_str)
self.p("\t\tsta " + l_str)
self.p("\t\tlda #0")
self.p("\t\tsta {:s}+1".format(l_str))
elif rvalue.datatype == DataType.WORD:
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda {:s}".format(r_str))
self.p("\t\tsta {:s}".format(l_str))
self.p("\t\tlda {:s}+1".format(r_str))
self.p("\t\tsta {:s}+1".format(l_str))
else:
raise CodeError("can only assign a byte or word to a word", str(rvalue))
elif lv.datatype == DataType.FLOAT:
if rvalue.datatype == DataType.FLOAT:
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\tlda #<" + r_str)
self.p("\t\tsta c64.SCRATCH_ZPWORD1")
self.p("\t\tlda #>" + r_str)
self.p("\t\tsta c64.SCRATCH_ZPWORD1+1")
self.p("\t\tldx #<" + l_str)
self.p("\t\tldy #>" + l_str)
self.p("\t\tjsr c64flt.copy_mflt")
elif rvalue.datatype == DataType.BYTE:
with self.preserving_registers({'A', 'X', 'Y'}):
self.p("\t\tldy " + r_str)
self.p("\t\tjsr c64.FREADUY") # ubyte Y -> fac1
self.p("\t\tldx #<" + l_str)
self.p("\t\tldy #>" + l_str)
self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY
elif rvalue.datatype == DataType.WORD:
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\tlda " + r_str)
self.p("\t\tldy {:s}+1".format(r_str))
self.p("\t\tjsr c64flt.GIVUAYF") # uword AY -> fac1
self.p("\t\tldx #<" + l_str)
self.p("\t\tldy #>" + l_str)
self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY
else:
raise CodeError("unsupported rvalue to memfloat", str(rvalue))
else:
raise CodeError("invalid lvalue memmapped datatype", str(lv))
def generate_assign_char_to_memory(self, lv: MemMappedValue, char_str: str) -> None:
# Memory = Character
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda #" + char_str)
if not lv.name:
self.p("\t\tsta " + Parser.to_hex(lv.address))
return
# assign char value to a memory location by symbol name
symblock, sym = self.cur_block.lookup(lv.name)
if isinstance(sym, VariableDef):
assign_target = lv.name
if symblock is not self.cur_block:
assign_target = symblock.label + '.' + sym.name
if sym.type == DataType.BYTE:
self.p("\t\tsta " + assign_target)
elif sym.type == DataType.WORD:
self.p("\t\tsta " + assign_target)
self.p("\t\tlda #0")
self.p("\t\tsta {}+1".format(assign_target))
else:
raise CodeError("invalid lvalue type " + str(sym))
else:
raise CodeError("invalid lvalue type " + str(sym))
def generate_assign_integer_to_reg(self, l_register: str, rvalue: IntegerValue) -> None:
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.value)
if l_register in ('A', 'X', 'Y'):
self.p("\t\tld{:s} #{:s}".format(l_register.lower(), r_str))
elif l_register in REGISTER_WORDS:
self.p("\t\tld{:s} #<{:s}".format(l_register[0].lower(), r_str))
self.p("\t\tld{:s} #>{:s}".format(l_register[1].lower(), r_str))
elif l_register == "SC":
# set/clear S carry bit
if rvalue.value:
self.p("\t\tsec")
else:
self.p("\t\tclc")
elif l_register == "SI":
# interrupt disable bit
if rvalue.value:
self.p("\t\tsei")
else:
self.p("\t\tcli")
else:
raise CodeError("invalid register in immediate integer assignment", l_register, rvalue.value)
def generate_assign_char_to_reg(self, lv: RegisterValue, char_str: str) -> None:
# Register = Char (string of length 1)
if lv.register not in ('A', 'X', 'Y'):
raise CodeError("invalid register for char assignment", lv.register)
self.p("\t\tld{:s} #{:s}".format(lv.register.lower(), char_str))
def generate_assign_string_to_reg(self, lv: RegisterValue, rvalue: StringValue) -> None:
if lv.register not in ("AX", "AY", "XY"):
raise CodeError("need register pair AX, AY or XY for string address assignment", lv.register)
if rvalue.name:
self.p("\t\tld{:s} #<{:s}".format(lv.register[0].lower(), rvalue.name))
self.p("\t\tld{:s} #>{:s}".format(lv.register[1].lower(), rvalue.name))
else:
raise CodeError("cannot assign immediate string, it must be a string variable")
def generate_assign_string_to_memory(self, lv: MemMappedValue, rvalue: StringValue) -> None:
if lv.datatype != DataType.WORD:
raise CodeError("need word memory type for string address assignment")
if rvalue.name:
assign_target = lv.name if lv.name else Parser.to_hex(lv.address)
self.p("\t\tlda #<{:s}".format(rvalue.name))
self.p("\t\tsta " + assign_target)
self.p("\t\tlda #>{:s}".format(rvalue.name))
self.p("\t\tsta {}+1".format(assign_target))
else:
raise CodeError("cannot assign immediate string, it must be a string variable")