diff --git a/il65/compile.py b/il65/compile.py index ebfe89c1c..4b9ac17ba 100644 --- a/il65/compile.py +++ b/il65/compile.py @@ -102,7 +102,7 @@ class PlyParser: if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST: raise ParseError("cannot modify a constant", target.sourceref) elif isinstance(node, AugAssignment): - # the assignment target must be a variable + # the assignment target must not be a constant if isinstance(node.left, SymbolName): symdef = node.my_scope().lookup(node.left.name) if isinstance(symdef, VarDef): @@ -111,6 +111,10 @@ class PlyParser: elif symdef.datatype not in {DataType.BYTE, DataType.WORD, DataType.FLOAT}: raise ParseError("cannot modify that datatype ({:s}) in this way" .format(symdef.datatype.name.lower()), node.sourceref) + # check for divide by (constant) zero + if node.operator in ("/=", "//="): + if isinstance(node.right, LiteralValue) and node.right.value == 0: + raise ParseError("division by zero", node.right.sourceref) previous_stmt = node def check_subroutine_arguments(self, call: SubCall, subdef: Subroutine) -> None: diff --git a/il65/datatypes.py b/il65/datatypes.py index 9b8457557..eaf8e33f7 100644 --- a/il65/datatypes.py +++ b/il65/datatypes.py @@ -46,6 +46,9 @@ class DataType(enum.Enum): def isnumeric(self) -> bool: return self.value in (1, 2, 3) + def isinteger(self) -> bool: + return self.value in (1, 2) + def isarray(self) -> bool: return self.value in (4, 5, 6) diff --git a/il65/emit/assignment.py b/il65/emit/assignment.py index 021885b0f..891aee961 100644 --- a/il65/emit/assignment.py +++ b/il65/emit/assignment.py @@ -6,18 +6,320 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 """ from typing import Callable -from ..plyparse import AstNode, Scope, VarDef, Dereference, Register, TargetRegisters,\ - LiteralValue, Assignment, AugAssignment +from ..plyparse import Scope, Assignment, AugAssignment, Register, LiteralValue, SymbolName, VarDef +from . import CodeError, preserving_registers, to_hex +from ..datatypes import REGISTER_BYTES, REGISTER_WORDS, VarType, DataType +from ..compile import Zeropage -def generate_assignment(out: Callable, stmt: Assignment) -> None: - assert stmt.right is not None +def generate_assignment(out: Callable, stmt: Assignment, scope: Scope) -> None: + pass + out("\v\t\t\t; " + stmt.lineref) + out("\v; @todo assignment") + # @todo assignment + + +def generate_aug_assignment(out: Callable, stmt: AugAssignment, scope: Scope) -> None: + # for instance: value += 3 + # left: Register, SymbolName, or Dereference. right: Expression/LiteralValue + out("\v\t\t\t; " + stmt.lineref) + out("\v; @todo aug-assignment") + lvalue = stmt.left rvalue = stmt.right - if isinstance(stmt.right, LiteralValue): - rvalue = stmt.right.value - # @todo + if isinstance(lvalue, Register): + if isinstance(rvalue, LiteralValue): + if type(rvalue.value) is int: + _generate_aug_reg_constant_int(out, lvalue, stmt.operator, rvalue.value, "", scope) + else: + raise CodeError("integer literal or variable required for now", rvalue) # XXX + elif isinstance(rvalue, SymbolName): + symdef = scope.lookup(rvalue.name) + if isinstance(symdef, VarDef) and symdef.datatype.isinteger(): + _generate_aug_reg_constant_int(out, lvalue, stmt.operator, 0, symdef.name, scope) + else: + raise CodeError("integer literal or variable required for now", rvalue) # XXX + elif isinstance(rvalue, Register): + _generate_aug_reg_reg(out, lvalue, stmt.operator, rvalue, scope) + else: + # @todo Register += symbolname / dereference , _generate_aug_reg_mem? + raise CodeError("invalid rvalue for augmented assignment on register", rvalue) + else: + raise CodeError("augmented assignment only implemented for registers for now", stmt.sourceref) # XXX -def generate_aug_assignment(out: Callable, stmt: AugAssignment) -> None: - assert stmt.right is not None - pass # @todo +def _generate_aug_reg_constant_int(out: Callable, lvalue: Register, operator: str, rvalue: int, rname: str, scope: Scope) -> None: + r_str = rname or to_hex(rvalue) + if operator == "+=": + if lvalue.register == "A": + out("\vclc") + out("\vadc #" + r_str) + elif lvalue.register == "X": + with preserving_registers({'A'}, scope, out): + out("\vtxa") + out("\vclc") + out("\vadc #" + r_str) + out("\vtax") + elif lvalue.register == "Y": + with preserving_registers({'A'}, scope, out): + out("\vtya") + out("\vclc") + out("\vadc #" + r_str) + out("\vtay") + else: + raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word + elif operator == "-=": + if lvalue.register == "A": + out("\vsec") + out("\vsbc #" + r_str) + elif lvalue.register == "X": + with preserving_registers({'A'}, scope, out): + out("\vtxa") + out("\vsec") + out("\vsbc #" + r_str) + out("\vtax") + elif lvalue.register == "Y": + with preserving_registers({'A'}, scope, out): + out("\vtya") + out("\vsec") + out("\vsbc #" + r_str) + out("\vtay") + else: + raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo -=.word + elif operator == "&=": + if lvalue.register == "A": + out("\vand #" + r_str) + elif lvalue.register == "X": + with preserving_registers({'A'}, scope, out): + out("\vtxa") + out("\vand #" + r_str) + out("\vtax") + elif lvalue.register == "Y": + with preserving_registers({'A'}, scope, out): + out("\vtya") + out("\vand #" + r_str) + out("\vtay") + else: + raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word + elif operator == "|=": + if lvalue.register == "A": + out("\vora #" + r_str) + elif lvalue.register == "X": + with preserving_registers({'A'}, scope, out): + out("\vtxa") + out("\vora #" + r_str) + out("\vtax") + elif lvalue.register == "Y": + with preserving_registers({'A'}, scope, out): + out("\vtya") + out("\vora #" + r_str) + out("\vtay") + else: + raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word + elif operator == "^=": + if lvalue.register == "A": + out("\veor #" + r_str) + elif lvalue.register == "X": + with preserving_registers({'A'}, scope, out): + out("\vtxa") + out("\veor #" + r_str) + out("\vtax") + elif lvalue.register == "Y": + with preserving_registers({'A'}, scope, out): + out("\vtya") + out("\veor #" + r_str) + out("\vtay") + else: + raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word + elif operator == ">>=": + if rvalue.value > 0: + def shifts_A(times: int) -> None: + if times >= 8: + out("\vlda #0") + else: + for _ in range(min(8, times)): + out("\vlsr a") + if lvalue.register == "A": + shifts_A(rvalue.value) + elif lvalue.register == "X": + with preserving_registers({'A'}, scope, out): + out("\vtxa") + shifts_A(rvalue.value) + out("\vtax") + elif lvalue.register == "Y": + with preserving_registers({'A'}, scope, out): + out("\vtya") + shifts_A(rvalue.value) + out("\vtay") + else: + raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo >>=.word + elif operator == "<<=": + if rvalue.value > 0: + def shifts_A(times: int) -> None: + if times >= 8: + out("\vlda #0") + else: + for _ in range(min(8, times)): + out("\vasl a") + if lvalue.register == "A": + shifts_A(rvalue.value) + elif lvalue.register == "X": + with preserving_registers({'A'}, scope, out): + out("\vtxa") + shifts_A(rvalue.value) + out("\vtax") + elif lvalue.register == "Y": + with preserving_registers({'A'}, scope, out): + out("\vtya") + shifts_A(rvalue.value) + out("\vtay") + else: + raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo <<=.word + + +def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue: Register, scope: Scope) -> None: + if operator == "+=": + if rvalue.register not in REGISTER_BYTES: + raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo +=.word + if lvalue.register == "A": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + out("\vclc") + out("\vadc " + to_hex(Zeropage.SCRATCH_B1)) + elif lvalue.register == "X": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtxa") + out("\vclc") + out("\vadc " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtax") + elif lvalue.register == "Y": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtya") + out("\vclc") + out("\vadc " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtay") + 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": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + out("\vsec") + out("\vsbc " + to_hex(Zeropage.SCRATCH_B1)) + elif lvalue.register == "X": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtxa") + out("\vsec") + out("\vsbc " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtax") + elif lvalue.register == "Y": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtya") + out("\vsec") + out("\vsbc " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtay") + 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": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + out("\vand " + to_hex(Zeropage.SCRATCH_B1)) + elif lvalue.register == "X": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtxa") + out("\vand " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtax") + elif lvalue.register == "Y": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtya") + out("\vand " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtay") + 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": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + out("\vora " + to_hex(Zeropage.SCRATCH_B1)) + elif lvalue.register == "X": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtxa") + out("\vora " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtax") + elif lvalue.register == "Y": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtya") + out("\vora " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtay") + 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": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + out("\veor " + to_hex(Zeropage.SCRATCH_B1)) + elif lvalue.register == "X": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtxa") + out("\veor " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtax") + elif lvalue.register == "Y": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtya") + out("\veor " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtay") + 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": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + out("\vlsr " + to_hex(Zeropage.SCRATCH_B1)) + elif lvalue.register == "X": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtxa") + out("\vlsr " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtax") + elif lvalue.register == "Y": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtya") + out("\vlsr " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtay") + 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": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + out("\vasl " + to_hex(Zeropage.SCRATCH_B1)) + elif lvalue.register == "X": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtxa") + out("\vasl " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtax") + elif lvalue.register == "Y": + out("\vst{:s} {:s}".format(rvalue.register.lower(), to_hex(Zeropage.SCRATCH_B1))) + with preserving_registers({'A'}, scope, out): + out("\vtya") + out("\vasl " + to_hex(Zeropage.SCRATCH_B1)) + out("\vtay") + else: + raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo <<=.word diff --git a/il65/emit/generate.py b/il65/emit/generate.py index 5e583f030..be196c8ca 100644 --- a/il65/emit/generate.py +++ b/il65/emit/generate.py @@ -211,9 +211,9 @@ class AssemblyGenerator: elif isinstance(stmt, SubCall): generate_subcall(out, stmt) elif isinstance(stmt, Assignment): - generate_assignment(out, stmt) + generate_assignment(out, stmt, scope) elif isinstance(stmt, AugAssignment): - generate_aug_assignment(out, stmt) + generate_aug_assignment(out, stmt, scope) elif isinstance(stmt, Directive): if stmt.name == "breakpoint": # put a marker in the source so that we can generate a list of breakpoints later diff --git a/il65/oldstuff/codegen.py b/il65/oldstuff/codegen.py index 50e9207ea..c02037e72 100644 --- a/il65/oldstuff/codegen.py +++ b/il65/oldstuff/codegen.py @@ -1,203 +1,9 @@ -# old deprecated code +# 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
\w+)\s+ea\s+nop\s+;\s+{:s}.*".format(BREAKPOINT_COMMENT_SIGNATURE) - def generate_incr_or_decr(self, stmt: Union[InplaceIncrStmt, InplaceDecrStmt]) -> None: - assert stmt.value.constant - assert (stmt.value.value is None and stmt.value.name) or stmt.value.value > 0 - if stmt.what.datatype != DataType.FLOAT and not stmt.value.name and stmt.value.value > 0xff: - raise CodeError("only supports integer incr/decr by up to 255 for now") # XXX - is_incr = isinstance(stmt, InplaceIncrStmt) - howmuch = stmt.value.value - value_str = stmt.value.name or str(howmuch) - if isinstance(stmt.what, RegisterValue): - reg = stmt.what.register - # note: these operations below are all checked to be ok - if is_incr: - if reg == 'A': - # a += 1..255 - self.p("\t\tclc") - self.p("\t\tadc #" + value_str) - elif reg in REGISTER_BYTES: - if howmuch == 1: - # x/y += 1 - self.p("\t\tin{:s}".format(reg.lower())) - else: - # x/y += 2..255 - with self.preserving_registers({'A'}): - self.p("\t\tt{:s}a".format(reg.lower())) - self.p("\t\tclc") - self.p("\t\tadc #" + value_str) - self.p("\t\tta{:s}".format(reg.lower())) - elif reg == "AX": - # AX += 1..255 - self.p("\t\tclc") - self.p("\t\tadc #" + value_str) - self.p("\t\tbcc +") - self.p("\t\tinx") - self.p("+") - elif reg == "AY": - # AY += 1..255 - self.p("\t\tclc") - self.p("\t\tadc # " + value_str) - self.p("\t\tbcc +") - self.p("\t\tiny") - self.p("+") - elif reg == "XY": - if howmuch == 1: - # XY += 1 - self.p("\t\tinx") - self.p("\t\tbne +") - self.p("\t\tiny") - self.p("+") - else: - # XY += 2..255 - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\tclc") - self.p("\t\tadc #" + value_str) - self.p("\t\ttax") - self.p("\t\tbcc +") - self.p("\t\tiny") - self.p("+") - else: - raise CodeError("invalid incr register: " + reg) - else: - if reg == 'A': - # a -= 1..255 - self.p("\t\tsec") - self.p("\t\tsbc #" + value_str) - elif reg in REGISTER_BYTES: - if howmuch == 1: - # x/y -= 1 - self.p("\t\tde{:s}".format(reg.lower())) - else: - # x/y -= 2..255 - with self.preserving_registers({'A'}): - self.p("\t\tt{:s}a".format(reg.lower())) - self.p("\t\tsec") - self.p("\t\tsbc #" + value_str) - self.p("\t\tta{:s}".format(reg.lower())) - elif reg == "AX": - # AX -= 1..255 - self.p("\t\tsec") - self.p("\t\tsbc #" + value_str) - self.p("\t\tbcs +") - self.p("\t\tdex") - self.p("+") - elif reg == "AY": - # AY -= 1..255 - self.p("\t\tsec") - self.p("\t\tsbc #" + value_str) - self.p("\t\tbcs +") - self.p("\t\tdey") - self.p("+") - elif reg == "XY": - if howmuch == 1: - # XY -= 1 - self.p("\t\tcpx #0") - self.p("\t\tbne +") - self.p("\t\tdey") - self.p("+\t\tdex") - else: - # XY -= 2..255 - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\tsec") - self.p("\t\tsbc #" + value_str) - self.p("\t\ttax") - self.p("\t\tbcs +") - self.p("\t\tdey") - self.p("+") - else: - raise CodeError("invalid decr register: " + reg) - elif isinstance(stmt.what, (MemMappedValue, IndirectValue)): - what = stmt.what - if isinstance(what, IndirectValue): - if isinstance(what.value, IntegerValue): - what_str = what.value.name or Parser.to_hex(what.value.value) - else: - raise CodeError("invalid incr indirect type", what.value) - else: - what_str = what.name or Parser.to_hex(what.address) - if what.datatype == DataType.BYTE: - if howmuch == 1: - self.p("\t\t{:s} {:s}".format("inc" if is_incr else "dec", what_str)) - else: - with self.preserving_registers({'A'}): - self.p("\t\tlda " + what_str) - if is_incr: - self.p("\t\tclc") - self.p("\t\tadc #" + value_str) - else: - self.p("\t\tsec") - self.p("\t\tsbc #" + value_str) - self.p("\t\tsta " + what_str) - elif what.datatype == DataType.WORD: - if howmuch == 1: - # mem.word +=/-= 1 - if is_incr: - self.p("\t\tinc " + what_str) - self.p("\t\tbne +") - self.p("\t\tinc {:s}+1".format(what_str)) - self.p("+") - else: - with self.preserving_registers({'A'}): - self.p("\t\tlda " + what_str) - self.p("\t\tbne +") - self.p("\t\tdec {:s}+1".format(what_str)) - self.p("+\t\tdec " + what_str) - else: - # mem.word +=/-= 2..255 - if is_incr: - with self.preserving_registers({'A'}): - self.p("\t\tclc") - self.p("\t\tlda " + what_str) - self.p("\t\tadc #" + value_str) - self.p("\t\tsta " + what_str) - self.p("\t\tbcc +") - self.p("\t\tinc {:s}+1".format(what_str)) - self.p("+") - else: - with self.preserving_registers({'A'}): - self.p("\t\tsec") - self.p("\t\tlda " + what_str) - self.p("\t\tsbc #" + value_str) - self.p("\t\tsta " + what_str) - self.p("\t\tbcs +") - self.p("\t\tdec {:s}+1".format(what_str)) - self.p("+") - elif what.datatype == DataType.FLOAT: - if howmuch == 1.0: - # special case for +/-1 - with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True): - self.p("\t\tldx #<" + what_str) - self.p("\t\tldy #>" + what_str) - if is_incr: - self.p("\t\tjsr c64flt.float_add_one") - else: - self.p("\t\tjsr c64flt.float_sub_one") - elif stmt.value.name: - with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True): - self.p("\t\tlda #<" + stmt.value.name) - self.p("\t\tsta c64.SCRATCH_ZPWORD1") - self.p("\t\tlda #>" + stmt.value.name) - self.p("\t\tsta c64.SCRATCH_ZPWORD1+1") - self.p("\t\tldx #<" + what_str) - self.p("\t\tldy #>" + what_str) - if is_incr: - self.p("\t\tjsr c64flt.float_add_SW1_to_XY") - else: - self.p("\t\tjsr c64flt.float_sub_SW1_from_XY") - else: - raise CodeError("incr/decr missing float constant definition") - else: - raise CodeError("cannot in/decrement memory of type " + str(what.datatype), howmuch) - else: - raise CodeError("cannot in/decrement " + str(stmt.what)) - def generate_call(self, stmt: CallStmt) -> None: self.p("\t\t\t\t\t; " + stmt.lineref) if stmt.condition: @@ -623,22 +429,7 @@ class CodeGenerator: branch_emitter(targetstr, False, False) generate_result_assignments() - def generate_augmented_assignment(self, stmt: AugmentedAssignmentStmt) -> None: - # for instance: value += 3 - lvalue = stmt.leftvalues[0] - rvalue = stmt.right - self.p("\t\t\t\t\t; " + stmt.lineref) - if isinstance(lvalue, RegisterValue): - if isinstance(rvalue, IntegerValue): - self._generate_aug_reg_int(lvalue, stmt.operator, rvalue) - elif isinstance(rvalue, RegisterValue): - self._generate_aug_reg_reg(lvalue, stmt.operator, rvalue) - elif isinstance(rvalue, MemMappedValue): - self._generate_aug_reg_mem(lvalue, stmt.operator, rvalue) - else: - raise CodeError("invalid rvalue for augmented assignment on register", str(rvalue)) - else: - raise CodeError("augmented assignment only implemented for registers for now", str(rvalue)) # XXX + def _generate_aug_reg_mem(self, lvalue: RegisterValue, operator: str, rvalue: MemMappedValue) -> None: r_str = rvalue.name or Parser.to_hex(rvalue.address) @@ -760,281 +551,7 @@ class CodeGenerator: else: raise CodeError("unsupported lvalue register for shift", str(lvalue)) # @todo >>=.word - def _generate_aug_reg_int(self, lvalue: RegisterValue, operator: str, rvalue: IntegerValue) -> None: - r_str = rvalue.name or Parser.to_hex(rvalue.value) - if operator == "+=": - if lvalue.register == "A": - self.p("\t\tclc") - self.p("\t\tadc #" + r_str) - elif lvalue.register == "X": - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\tclc") - self.p("\t\tadc #" + r_str) - self.p("\t\ttax") - elif lvalue.register == "Y": - with self.preserving_registers({'A'}): - self.p("\t\ttya") - self.p("\t\tclc") - self.p("\t\tadc #" + r_str) - self.p("\t\ttay") - else: - raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word - elif operator == "-=": - if lvalue.register == "A": - self.p("\t\tsec") - self.p("\t\tsbc #" + r_str) - elif lvalue.register == "X": - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\tsec") - self.p("\t\tsbc #" + r_str) - self.p("\t\ttax") - elif lvalue.register == "Y": - with self.preserving_registers({'A'}): - self.p("\t\ttya") - self.p("\t\tsec") - self.p("\t\tsbc #" + r_str) - self.p("\t\ttay") - else: - raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo -=.word - elif operator == "&=": - if lvalue.register == "A": - self.p("\t\tand #" + r_str) - elif lvalue.register == "X": - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\tand #" + r_str) - self.p("\t\ttax") - elif lvalue.register == "Y": - with self.preserving_registers({'A'}): - self.p("\t\ttya") - self.p("\t\tand #" + r_str) - self.p("\t\ttay") - else: - raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word - elif operator == "|=": - if lvalue.register == "A": - self.p("\t\tora #" + r_str) - elif lvalue.register == "X": - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\tora #" + r_str) - self.p("\t\ttax") - elif lvalue.register == "Y": - with self.preserving_registers({'A'}): - self.p("\t\ttya") - self.p("\t\tora #" + r_str) - self.p("\t\ttay") - else: - raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word - elif operator == "^=": - if lvalue.register == "A": - self.p("\t\teor #" + r_str) - elif lvalue.register == "X": - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\teor #" + r_str) - self.p("\t\ttax") - elif lvalue.register == "Y": - with self.preserving_registers({'A'}): - self.p("\t\ttya") - self.p("\t\teor #" + r_str) - self.p("\t\ttay") - else: - raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word - elif operator == ">>=": - if rvalue.value > 0: - def shifts_A(times: int) -> None: - if times >= 8: - self.p("\t\tlda #0") - else: - for _ in range(min(8, times)): - self.p("\t\tlsr a") - if lvalue.register == "A": - shifts_A(rvalue.value) - elif lvalue.register == "X": - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - shifts_A(rvalue.value) - self.p("\t\ttax") - elif lvalue.register == "Y": - with self.preserving_registers({'A'}): - self.p("\t\ttya") - shifts_A(rvalue.value) - self.p("\t\ttay") - else: - raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo >>=.word - elif operator == "<<=": - if rvalue.value > 0: - def shifts_A(times: int) -> None: - if times >= 8: - self.p("\t\tlda #0") - else: - for _ in range(min(8, times)): - self.p("\t\tasl a") - if lvalue.register == "A": - shifts_A(rvalue.value) - elif lvalue.register == "X": - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - shifts_A(rvalue.value) - self.p("\t\ttax") - elif lvalue.register == "Y": - with self.preserving_registers({'A'}): - self.p("\t\ttya") - shifts_A(rvalue.value) - self.p("\t\ttay") - else: - raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo <<=.word - def _generate_aug_reg_reg(self, lvalue: RegisterValue, operator: str, rvalue: RegisterValue) -> None: - if operator == "+=": - if rvalue.register not in REGISTER_BYTES: - raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo +=.word - if lvalue.register == "A": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - self.p("\t\tclc") - self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\tclc") - self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttax") - elif lvalue.register == "Y": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttya") - self.p("\t\tclc") - self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttay") - else: - raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo +=.word - elif operator == "-=": - if rvalue.register not in REGISTER_BYTES: - raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo -=.word - if lvalue.register == "A": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - self.p("\t\tsec") - self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\tsec") - self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttax") - elif lvalue.register == "Y": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttya") - self.p("\t\tsec") - self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttay") - else: - raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo -=.word - elif operator == "&=": - if rvalue.register not in REGISTER_BYTES: - raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo &=.word - if lvalue.register == "A": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttax") - elif lvalue.register == "Y": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttya") - self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttay") - else: - raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo &=.word - elif operator == "|=": - if rvalue.register not in REGISTER_BYTES: - raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo |=.word - if lvalue.register == "A": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttax") - elif lvalue.register == "Y": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttya") - self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttay") - else: - raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo |=.word - elif operator == "^=": - if rvalue.register not in REGISTER_BYTES: - raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo ^=.word - if lvalue.register == "A": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttax") - elif lvalue.register == "Y": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttya") - self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttay") - else: - raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo ^=.word - elif operator == ">>=": - if rvalue.register not in REGISTER_BYTES: - raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo >>=.word - if lvalue.register == "A": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttax") - elif lvalue.register == "Y": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttya") - self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttay") - else: - raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo >>=.word - elif operator == "<<=": - if rvalue.register not in REGISTER_BYTES: - raise CodeError("unsupported rvalue register for aug assign", str(rvalue)) # @todo <<=.word - if lvalue.register == "A": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1)) - elif lvalue.register == "X": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttxa") - self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttax") - elif lvalue.register == "Y": - self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) - with self.preserving_registers({'A'}): - self.p("\t\ttya") - self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1)) - self.p("\t\ttay") - else: - raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo <<=.word def generate_assignment(self, stmt: AssignmentStmt) -> None: def unwrap_indirect(iv: IndirectValue) -> MemMappedValue: @@ -1256,58 +773,6 @@ class CodeGenerator: else: raise CodeError("invalid register " + lv.register) - @contextlib.contextmanager - def preserving_registers(self, registers: Set[str], loads_a_within: bool=False, always_preserve: bool=False): - # this sometimes clobbers a ZP scratch register and is therefore NOT safe to use in interrupts - # see http://6502.org/tutorials/register_preservation.html - if not self.cur_block.preserve_registers and not always_preserve: - yield - return - if registers == {'A'}: - self.p("\t\tpha") - yield - self.p("\t\tpla") - elif registers: - if not loads_a_within: - self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2)) - if 'A' in registers: - self.p("\t\tpha") - if 'X' in registers: - self.p("\t\ttxa\n\t\tpha") - if 'Y' in registers: - self.p("\t\ttya\n\t\tpha") - if not loads_a_within: - self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2)) - yield - if 'X' in registers and 'Y' in registers: - if 'A' not in registers: - self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2)) - self.p("\t\tpla\n\t\ttay") - self.p("\t\tpla\n\t\ttax") - self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2)) - else: - self.p("\t\tpla\n\t\ttay") - self.p("\t\tpla\n\t\ttax") - else: - if 'Y' in registers: - if 'A' not in registers: - self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2)) - self.p("\t\tpla\n\t\ttay") - self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2)) - else: - self.p("\t\tpla\n\t\ttay") - if 'X' in registers: - if 'A' not in registers: - self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2)) - self.p("\t\tpla\n\t\ttax") - self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2)) - else: - self.p("\t\tpla\n\t\ttax") - if 'A' in registers: - self.p("\t\tpla") - else: - yield - def generate_assign_integer_to_mem(self, lv: MemMappedValue, rvalue: IntegerValue) -> None: if lv.name: symblock, sym = self.cur_block.lookup(lv.name) diff --git a/il65/optimize.py b/il65/optimize.py index 269763f62..ddf1629a2 100644 --- a/il65/optimize.py +++ b/il65/optimize.py @@ -43,15 +43,26 @@ class Optimizer: print_warning("{}: removed statement that has no effect".format(assignment.sourceref)) if isinstance(assignment, AugAssignment): if isinstance(assignment.right, LiteralValue) and isinstance(assignment.right.value, (int, float)): - if assignment.right.value == 0 and assignment.operator in ("+=", "-=", "|=", "<<=", ">>=", "^="): - self.num_warnings += 1 - print_warning("{}: removed statement that has no effect".format(assignment.sourceref)) - assignment.my_scope().remove_node(assignment) + if assignment.right.value == 0: + if assignment.operator in ("+=", "-=", "|=", "<<=", ">>=", "^="): + self.num_warnings += 1 + print_warning("{}: removed statement that has no effect".format(assignment.sourceref)) + assignment.my_scope().remove_node(assignment) + elif assignment.operator == "*=": + self.num_warnings += 1 + print_warning("{}: statement replaced by = 0".format(assignment.sourceref)) + new_assignment = self._make_new_assignment(assignment, 0) + assignment.my_scope().replace_node(assignment, new_assignment) + elif assignment.operator == "**=": + self.num_warnings += 1 + print_warning("{}: statement replaced by = 1".format(assignment.sourceref)) + new_assignment = self._make_new_assignment(assignment, 1) + assignment.my_scope().replace_node(assignment, new_assignment) if assignment.right.value >= 8 and assignment.operator in ("<<=", ">>="): print("{}: shifting result is always zero".format(assignment.sourceref)) new_stmt = Assignment(sourceref=assignment.sourceref) new_stmt.nodes.append(AssignmentTargets(nodes=[assignment.left], sourceref=assignment.sourceref)) - new_stmt.nodes.append(0) + new_stmt.nodes.append(0) # XXX literalvalue? assignment.my_scope().replace_node(assignment, new_stmt) if assignment.operator in ("+=", "-=") and 0 < assignment.right.value < 256: howmuch = assignment.right @@ -62,6 +73,22 @@ class Optimizer: howmuch=howmuch.value, sourceref=assignment.sourceref) new_stmt.target = assignment.left assignment.my_scope().replace_node(assignment, new_stmt) + if assignment.operator in ("/=", "//=", "*=") and assignment.right.value == 1: + self.num_warnings += 1 + print_warning("{}: removed statement that has no effect".format(assignment.sourceref)) + assignment.my_scope().remove_node(assignment) + + @no_type_check + def _make_new_assignment(self, old_aug_assignment: AugAssignment, constantvalue: int) -> Assignment: + new_assignment = Assignment(sourceref=old_aug_assignment.sourceref) + new_assignment.parent = old_aug_assignment.parent + left = AssignmentTargets(nodes=[old_aug_assignment.left], sourceref=old_aug_assignment.sourceref) + left.parent = new_assignment + new_assignment.nodes.append(left) + value = LiteralValue(value=constantvalue, sourceref=old_aug_assignment.sourceref) + value.parent = new_assignment + new_assignment.nodes.append(value) + return new_assignment def combine_assignments_into_multi(self) -> None: # fold multiple consecutive assignments with the same rvalue into one multi-assignment diff --git a/il65/plylex.py b/il65/plylex.py index 13240a26b..dcba4fced 100644 --- a/il65/plylex.py +++ b/il65/plylex.py @@ -86,7 +86,7 @@ t_BITOR = r"\|" t_BITXOR = r"\^" t_BITINVERT = r"~" t_IS = r"=" -t_AUGASSIGN = r"\+=|-=|/=|\*=|<<=|>>=|&=|\|=|\^=" +t_AUGASSIGN = r"\+=|-=|/=|//=|\*=|\*\*=|<<=|>>=|&=|\|=|\^=" t_DECR = r"--" t_INCR = r"\+\+" t_EQUALS = r"==" diff --git a/il65/plyparse.py b/il65/plyparse.py index 3476469dc..277388c0f 100644 --- a/il65/plyparse.py +++ b/il65/plyparse.py @@ -482,6 +482,8 @@ class Expression(AstNode): estr = "{} {} {}".format(repr(self.left.value), self.operator, repr(self.right.value)) try: return LiteralValue(value=eval(estr, {}, {}), sourceref=sourceref) # type: ignore # safe because of checks above + except ZeroDivisionError: + raise ParseError("division by zero", sourceref) except Exception as x: raise ExpressionEvaluationError("expression error: " + str(x), self.sourceref) from None diff --git a/todo.ill b/todo.ill index 12c082947..095e6f992 100644 --- a/todo.ill +++ b/todo.ill @@ -38,8 +38,8 @@ start: v3t++ v3t+=1 v3t+=1 ; @todo? (optimize) join with previous - v3t+=0 - v3t+=1 + v3t+=0 ; is removed. + v3t+=1 ; @todo? (optimize) join with previous v3t+=1 ; @todo? (optimize) join with previous v3t=2.23424 ; @todo store as constant float with generated name, replace value node v3t=2.23411 ; @todo store as constant float with generated name, replace value node @@ -49,7 +49,13 @@ start: ; v3t+=2.23411 ; @todo store as constant float with generated name, replace value node ; v3t+=2.23411 ; @todo store as constant float with generated name, replace value node v3t=2.23424 * v3t ; @todo store as constant float with generated name, replace value node - v3t*=2.23424 * v3t ; @todo store as constant float with generated name, replace value node + XY*=2 + XY*=3 + XY=XY/0 ; @todo zerodiv (during expression to code generation) + XY=XY//0 ; @todo zerodiv (during expression to code generation) + XY*=2.23424 ; @todo store as constant float with generated name, replace value node + XY*=2.23424 * v3t ; @todo store as constant float with generated name, replace value node + ;v3t*=2.23424 * v3t ; @todo store as constant float with generated name, replace value node ; A++ ; X-- ; A+=1