diff --git a/il65/codegen.py b/il65/codegen.py index 9d488840a..f93d4504e 100644 --- a/il65/codegen.py +++ b/il65/codegen.py @@ -16,7 +16,7 @@ from functools import partial from typing import TextIO, Set, Union, List from .parse import ProgramFormat, ParseResult, Parser from .symbols import Zeropage, DataType, ConstantDef, VariableDef, SubroutineDef, \ - STRING_DATATYPES, REGISTER_WORDS, FLOAT_MAX_NEGATIVE, FLOAT_MAX_POSITIVE + STRING_DATATYPES, REGISTER_WORDS, REGISTER_BYTES, FLOAT_MAX_NEGATIVE, FLOAT_MAX_POSITIVE class CodeError(Exception): @@ -216,8 +216,8 @@ class CodeGenerator: 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" - "\t\tclv\t\t\t; clear overflow flag" + "\t\tclc\t\t\t; clear carry flag", + "\t\tclv\t\t\t; clear overflow flag", ] statements.insert(index+1, ParseResult.InlineAsm(asmlines, 0)) break @@ -371,12 +371,14 @@ class CodeGenerator: else: raise CodeError("can only return immediate values for now") # XXX self.p("\t\trts") + elif isinstance(stmt, ParseResult.AugmentedAssignmentStmt): + self.generate_augmented_assignment(stmt) 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.InplaceIncrStmt, ParseResult.InplaceDecrStmt)): + self.generate_incr_or_decr(stmt) elif isinstance(stmt, ParseResult.CallStmt): self.generate_call(stmt) elif isinstance(stmt, ParseResult.InlineAsm): @@ -393,36 +395,72 @@ class CodeGenerator: 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: + def generate_incr_or_decr(self, stmt: Union[ParseResult.InplaceIncrStmt, ParseResult.InplaceDecrStmt]) -> None: + if stmt.what.datatype == DataType.FLOAT: + raise CodeError("incr/decr on float not yet supported") # @todo support incr/decr on float + else: + assert type(stmt.howmuch) is int + assert stmt.howmuch > 0 + if stmt.howmuch > 0xff: + raise CodeError("only supports incr/decr by up to 255 for now") # XXX + is_incr = isinstance(stmt, ParseResult.InplaceIncrStmt) + if isinstance(stmt.what, ParseResult.RegisterValue): + if is_incr: + if stmt.what.register == 'A': + self.p("\t\tclc") + self.p("\t\tadc #{:d}".format(stmt.howmuch)) + elif stmt.what.register in REGISTER_BYTES: + if stmt.howmuch == 1: 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\tpha") + self.p("\t\tt{:s}a".format(stmt.what.register.lower())) + self.p("\t\tclc") + self.p("\t\tadc #{:d}".format(stmt.howmuch)) + self.p("\t\tta{:s}".format(stmt.what.register.lower())) + self.p("\t\tpla") + else: + raise CodeError("invalid incr/decr register") + else: + if stmt.what.register == 'A': + self.p("\t\tadc #{:d}".format(stmt.howmuch)) + elif stmt.what.register in REGISTER_BYTES: + if stmt.howmuch == 1: 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) + self.p("\t\tpha") + self.p("\t\tt{:s}a".format(stmt.what.register.lower())) + self.p("\t\tsbc #{:d}".format(stmt.howmuch)) + self.p("\t\tta{:s}".format(stmt.what.register.lower())) + self.p("\t\tpla") 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) + raise CodeError("invalid incr/decr register") + 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\t{:s} {:s}".format("inc" if is_incr else "dec", r_str)) + else: + self.p("\t\tpha") + self.p("\t\tlda " + r_str) + if is_incr: + self.p("\t\tclc") + self.p("\t\tadc #{:d}".format(stmt.howmuch)) else: - self.p("\t\tdec " + r_str) - elif what.datatype == DataType.WORD: - # @todo verify this asm code - if stmt.howmuch == 1: + self.p("\t\tsbc #{:d}".format(stmt.howmuch)) + self.p("\t\tsta " + r_str) + self.p("\t\tpla") + elif what.datatype == DataType.WORD: + # @todo verify this 16-bit incr/decr asm code + if stmt.howmuch == 1: + if is_incr: self.p("\t\tinc " + r_str) self.p("\t\tbne +") self.p("\t\tinc {:s}+1".format(r_str)) @@ -433,13 +471,11 @@ class CodeGenerator: self.p("\t\tdec {:s}+1".format(r_str)) self.p("+") else: - raise CodeError("cannot in/decrement memory of type " + str(what.datatype)) + raise CodeError("cannot yet incr/decr 16 bit memory by more than 1") # @todo 16-bit incr/decr 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 + raise CodeError("cannot in/decrement memory of type " + str(what.datatype)) + else: + raise CodeError("cannot in/decrement " + str(stmt.what)) def generate_call(self, stmt: ParseResult.CallStmt) -> None: self.p("\t\t\t\t\t; src l. {:d}".format(stmt.lineno)) @@ -683,6 +719,7 @@ class CodeGenerator: unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments) with self.preserving_registers(preserve_regs, loads_a_within=params_load_a()): generate_param_assignments() + # @todo optimize this with RTS_trick? https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations#Use_Jump_tables_with_RTS_instruction_instead_of_JMP_indirect_instruction if targetstr in REGISTER_WORDS: if stmt.preserve_regs: # cannot use zp scratch. This is very inefficient code! @@ -733,6 +770,305 @@ class CodeGenerator: self.p("\t\tjsr " + targetstr) generate_result_assignments() + def generate_augmented_assignment(self, stmt: ParseResult.AugmentedAssignmentStmt) -> None: + # for instance: value += 3 + lvalue = stmt.leftvalues[0] + rvalue = stmt.right + self.p("\t\t\t\t\t; src l. {:d}".format(stmt.lineno)) + if isinstance(lvalue, ParseResult.RegisterValue): + if isinstance(rvalue, ParseResult.IntegerValue): + self._generate_aug_reg_int(lvalue, stmt.operator, rvalue) + elif isinstance(rvalue, ParseResult.RegisterValue): + self._generate_aug_reg_reg(lvalue, stmt.operator, rvalue) + else: + raise CodeError("incr/decr on register only takes int or register for now") # XXX + else: + raise CodeError("incr/decr only implemented for registers for now") # XXX + + def _generate_aug_reg_int(self, lvalue: ParseResult.RegisterValue, operator: str, rvalue: ParseResult.IntegerValue) -> None: + r_str = rvalue.name or Parser.to_hex(rvalue.value) + if operator == "+=": + self.p("\t\tclc") + if lvalue.register == "A": + self.p("\t\tadc #" + r_str) + elif lvalue.register == "X": + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\tadc #" + r_str) + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\tadc #" + r_str) + self.p("\t\ttay") + self.p("\t\tpla") + else: + raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word + elif operator == "-=": + if lvalue.register == "A": + self.p("\t\tsbc #" + r_str) + elif lvalue.register == "X": + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\tsbc #" + r_str) + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\tsbc #" + r_str) + self.p("\t\ttay") + self.p("\t\tpla") + 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": + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\tand #" + r_str) + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\tand #" + r_str) + self.p("\t\ttay") + self.p("\t\tpla") + 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": + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\tora #" + r_str) + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\tora #" + r_str) + self.p("\t\ttay") + self.p("\t\tpla") + 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": + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\teor #" + r_str) + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\teor #" + r_str) + self.p("\t\ttay") + self.p("\t\tpla") + else: + raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word + elif operator == ">>=": + if rvalue.value != 1: + raise CodeError("can only shift 1 bit for now") # XXX + if lvalue.register == "A": + self.p("\t\tlsr a") + elif lvalue.register == "X": + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\tlsr a") + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\tlsr a") + self.p("\t\ttay") + self.p("\t\tpla") + else: + raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo >>=.word + elif operator == "<<=": + if rvalue.value != 1: + raise CodeError("can only shift 1 bit for now") # XXX + if lvalue.register == "A": + self.p("\t\tasl a") + elif lvalue.register == "X": + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\tasl a") + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\tasl a") + self.p("\t\ttay") + self.p("\t\tpla") + else: + raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo <<=.word + + def _generate_aug_reg_reg(self, lvalue: ParseResult.RegisterValue, operator: str, rvalue: ParseResult.RegisterValue) -> None: + if operator == "+=": + self.p("\t\tclc") + 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\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))) + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttay") + self.p("\t\tpla") + 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\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))) + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttay") + self.p("\t\tpla") + 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))) + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttay") + self.p("\t\tpla") + 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))) + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttay") + self.p("\t\tpla") + 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))) + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttay") + self.p("\t\tpla") + 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))) + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttay") + self.p("\t\tpla") + 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))) + self.p("\t\tpha") + self.p("\t\ttxa") + self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttax") + self.p("\t\tpla") + elif lvalue.register == "Y": + self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1))) + self.p("\t\tpha") + self.p("\t\ttya") + self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1)) + self.p("\t\ttay") + self.p("\t\tpla") + else: + raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo <<=.word + def generate_assignment(self, stmt: ParseResult.AssignmentStmt) -> None: def unwrap_indirect(iv: ParseResult.IndirectValue) -> ParseResult.MemMappedValue: if isinstance(iv.value, ParseResult.MemMappedValue): diff --git a/il65/main.py b/il65/main.py index 1aa0083db..38e2ca1ac 100644 --- a/il65/main.py +++ b/il65/main.py @@ -43,7 +43,7 @@ def main() -> None: parsed = p.parse() if parsed: if args.nooptimize: - print("not optimizing the parse tree!") + p.print_warning("not optimizing the parse tree!") else: opt = Optimizer(parsed) parsed = opt.optimize() @@ -57,11 +57,11 @@ def main() -> None: mon_command_file = assembler.generate_breakpoint_list(program_filename) duration_total = time.perf_counter() - start print("Compile duration: {:.2f} seconds".format(duration_total)) - print("Output file: ", program_filename) + p.print_warning("Output file: " + program_filename) print() if args.startvice: print("Autostart vice emulator...") - args = ["x64", "-remotemonitor", "-moncommands", mon_command_file, - "-autostartprgmode", "1", "-autostart-warp", "-autostart", program_filename] + cmdline = ["x64", "-remotemonitor", "-moncommands", mon_command_file, + "-autostartprgmode", "1", "-autostart-warp", "-autostart", program_filename] with open(os.devnull, "wb") as shutup: - subprocess.call(args, stdout=shutup) + subprocess.call(cmdline, stdout=shutup) diff --git a/il65/parse.py b/il65/parse.py index 0808fc897..161cad12f 100644 --- a/il65/parse.py +++ b/il65/parse.py @@ -19,7 +19,7 @@ from .astparse import ParseError, parse_expr_as_int, parse_expr_as_number, parse from .symbols import SourceRef, SymbolTable, DataType, SymbolDefinition, SubroutineDef, LabelDef, \ zeropage, check_value_in_range, char_to_bytevalue, \ PrimitiveType, VariableDef, ConstantDef, SymbolError, STRING_DATATYPES, \ - REGISTER_SYMBOLS, REGISTER_WORDS, REGISTER_BYTES, RESERVED_NAMES + REGISTER_SYMBOLS, REGISTER_WORDS, REGISTER_BYTES, REGISTER_SBITS, RESERVED_NAMES class ProgramFormat(enum.Enum): @@ -259,7 +259,7 @@ class ParseResult: return False, "cannot explicitly assign from a status bit register alias" if len(self.register) < len(other.register): return False, "register size mismatch" - if isinstance(other, ParseResult.StringValue) and self.register in REGISTER_BYTES: + if isinstance(other, ParseResult.StringValue) and self.register in REGISTER_BYTES | REGISTER_SBITS: return False, "string address requires 16 bits combined register" if isinstance(other, ParseResult.IntegerValue): if other.value is not None: @@ -399,6 +399,18 @@ class ParseResult: def is_identity(self) -> bool: return all(lv == self.right for lv in self.leftvalues) + class AugmentedAssignmentStmt(AssignmentStmt): + SUPPORTED_OPERATORS = {"+=", "-=", "&=", "|=", "^=", ">>=", "<<="} + # full set: {"+=", "-=", "*=", "/=", "%=", "//=", "**=", "&=", "|=", "^=", ">>=", "<<="} + + def __init__(self, left: 'ParseResult.Value', operator: str, right: 'ParseResult.Value', lineno: int) -> None: + assert operator in self.SUPPORTED_OPERATORS + super().__init__([left], right, lineno) + self.operator = operator + + def __str__(self): + return "".format(str(self.leftvalues[0]), self.operator, str(self.right)) + class ReturnStmt(_AstNode): def __init__(self, lineno: int, a: Optional['ParseResult.Value']=None, x: Optional['ParseResult.Value']=None, @@ -408,9 +420,17 @@ class ParseResult: self.x = x self.y = y - class IncrDecrStmt(_AstNode): - def __init__(self, what: 'ParseResult.Value', howmuch: int, lineno: int) -> None: + class InplaceIncrStmt(_AstNode): + def __init__(self, what: 'ParseResult.Value', howmuch: Union[int, float], lineno: int) -> None: super().__init__(lineno) + assert howmuch > 0 + self.what = what + self.howmuch = howmuch + + class InplaceDecrStmt(_AstNode): + def __init__(self, what: 'ParseResult.Value', howmuch: Union[int, float], lineno: int) -> None: + super().__init__(lineno) + assert howmuch > 0 self.what = what self.howmuch = howmuch @@ -437,7 +457,7 @@ class ParseResult: self.desugared_output_assignments.clear() for name, value in self.arguments or []: assert name is not None, "all call arguments should have a name or be matched on a named parameter" - assignment = parser.parse_assignment("{:s}={:s}".format(name, value)) + assignment = parser.parse_assignment(name, value) if not assignment.is_identity(): assignment.lineno = self.lineno self.desugared_call_arguments.append(assignment) @@ -1144,12 +1164,18 @@ class Parser: what = self.parse_expression(line[:-2].rstrip()) if isinstance(what, ParseResult.IntegerValue): raise self.PError("cannot in/decrement a constant value") - return ParseResult.IncrDecrStmt(what, 1 if incr else -1, self.sourceref.line) + if incr: + return ParseResult.InplaceIncrStmt(what, 1, self.sourceref.line) + return ParseResult.InplaceDecrStmt(what, 1, self.sourceref.line) else: - # perhaps it is an assignment statment - lhs, sep, rhs = line.partition("=") - if sep: - return self.parse_assignment(line) + # perhaps it is an augmented assignment statement + match = re.fullmatch(r"(?P\S+)\s*(?P\+=|-=|\*=|/=|%=|//=|\*\*=|&=|\|=|\^=|>>=|<<=)\s*(?P\S.*)", line) + if match: + return self.parse_augmented_assignment(match.group("left"), match.group("assignment"), match.group("right")) + # a normal assignment perhaps? + splits = [s.strip() for s in line.split('=')] + if all(splits): + return self.parse_assignment(*splits) raise self.PError("invalid statement") def parse_call_or_goto(self, targetstr: str, argumentstr: str, outputstr: str, @@ -1237,28 +1263,56 @@ class Parser: return int(text[1:], 2) return int(text) - def parse_assignment(self, line: str, parsed_rvalue: ParseResult.Value = None) -> ParseResult.AssignmentStmt: - # parses assigning a value to one or more targets - parts = line.split("=") - if parsed_rvalue: - r_value = parsed_rvalue - else: - rhs = parts.pop() - r_value = self.parse_expression(rhs) - l_values = [self.parse_expression(part) for part in parts] - if any(isinstance(lv, ParseResult.IntegerValue) for lv in l_values): + def parse_assignment(self, *parts) -> ParseResult.AssignmentStmt: + # parses the assignment of one rvalue to one or more lvalues + l_values = [self.parse_expression(p) for p in parts[:-1]] + r_value = self.parse_expression(parts[-1]) + if any(lv.constant for lv in l_values): raise self.PError("can't have a constant as assignment target, perhaps you wanted indirection [...] instead?") for lv in l_values: assignable, reason = lv.assignable_from(r_value) if not assignable: raise self.PError("cannot assign {0} to {1}; {2}".format(r_value, lv, reason)) if lv.datatype in (DataType.BYTE, DataType.WORD, DataType.MATRIX): + # truncate the rvalue if needed if isinstance(r_value, ParseResult.FloatValue): truncated, value = self.coerce_value(self.sourceref, lv.datatype, r_value.value) if truncated: r_value = ParseResult.IntegerValue(int(value), datatype=lv.datatype, name=r_value.name) return ParseResult.AssignmentStmt(l_values, r_value, self.sourceref.line) + def parse_augmented_assignment(self, leftstr: str, operator: str, rightstr: str) \ + -> Union[ParseResult.AssignmentStmt, ParseResult.InplaceDecrStmt, ParseResult.InplaceIncrStmt]: + # parses an augmented assignment (for instance: value += 3) + if operator not in ParseResult.AugmentedAssignmentStmt.SUPPORTED_OPERATORS: + raise self.PError("augmented assignment operator '{:s}' not supported".format(operator)) + l_value = self.parse_expression(leftstr) + r_value = self.parse_expression(rightstr) + if l_value.constant: + raise self.PError("can't have a constant as assignment target, perhaps you wanted indirection [...] instead?") + if l_value.datatype in (DataType.BYTE, DataType.WORD, DataType.MATRIX): + # truncate the rvalue if needed + if isinstance(r_value, ParseResult.FloatValue): + truncated, value = self.coerce_value(self.sourceref, l_value.datatype, r_value.value) + if truncated: + r_value = ParseResult.IntegerValue(int(value), datatype=l_value.datatype, name=r_value.name) + if r_value.constant and operator in ("+=", "-="): + if operator == "+=": + if r_value.value > 0: # type: ignore + return ParseResult.InplaceIncrStmt(l_value, r_value.value, self.sourceref.line) # type: ignore + elif r_value.value < 0: # type: ignore + return ParseResult.InplaceDecrStmt(l_value, -r_value.value, self.sourceref.line) # type: ignore + else: + self.print_warning("warning: {}: incr with zero, ignored".format(self.sourceref)) + else: + if r_value.value > 0: # type: ignore + return ParseResult.InplaceDecrStmt(l_value, r_value.value, self.sourceref.line) # type: ignore + elif r_value.value < 0: # type: ignore + return ParseResult.InplaceIncrStmt(l_value, -r_value.value, self.sourceref.line) # type: ignore + else: + self.print_warning("warning: {}: decr with zero, ignored".format(self.sourceref)) + return ParseResult.AugmentedAssignmentStmt(l_value, operator, r_value, self.sourceref.line) + def parse_return(self, line: str) -> ParseResult.ReturnStmt: parts = line.split(maxsplit=1) if parts[0] != "return": @@ -1376,7 +1430,7 @@ class Parser: raise self.PError(str(ex)) elif text in REGISTER_WORDS: return ParseResult.RegisterValue(text, DataType.WORD) - elif text in REGISTER_BYTES: + elif text in REGISTER_BYTES | REGISTER_SBITS: return ParseResult.RegisterValue(text, DataType.BYTE) elif (text.startswith("'") and text.endswith("'")) or (text.startswith('"') and text.endswith('"')): strvalue = parse_expr_as_string(text, self.cur_block.symbols, self.ppsymbols, self.sourceref) @@ -1430,7 +1484,7 @@ class Parser: elif text.startswith('[') and text.endswith(']'): return self.parse_indirect_value(text)[1] else: - raise self.PError("invalid value '" + text + "'") + raise self.PError("invalid single value '" + text + "'") # @todo understand complex expressions def parse_indirect_value(self, text: str, allow_mmapped_for_call: bool=False) -> Tuple[str, ParseResult.IndirectValue]: indirect = text[1:-1].strip() @@ -1581,10 +1635,10 @@ class Optimizer: self.remove_unused_subroutines(block) self.optimize_compare_with_zero(block) for sub in block.symbols.iter_subroutines(True): - self.remove_identity_assigns(sub.sub_block) - self.combine_assignments_into_multi(sub.sub_block) - self.optimize_multiassigns(sub.sub_block) - self.optimize_compare_with_zero(sub.sub_block) + self.remove_identity_assigns(sub.sub_block) + self.combine_assignments_into_multi(sub.sub_block) + self.optimize_multiassigns(sub.sub_block) + self.optimize_compare_with_zero(sub.sub_block) return self.parsed def optimize_compare_with_zero(self, block: ParseResult.Block) -> None: @@ -1618,13 +1672,12 @@ class Optimizer: if simplified: print("{:s}:{:d}: simplified comparison with zero".format(block.sourceref.file, stmt.lineno)) - def combine_assignments_into_multi(self, block: ParseResult.Block) -> None: # fold multiple consecutive assignments with the same rvalue into one multi-assignment statements = [] # type: List[ParseResult._AstNode] multi_assign_statement = None for stmt in block.statements: - if isinstance(stmt, ParseResult.AssignmentStmt): + if isinstance(stmt, ParseResult.AssignmentStmt) and not isinstance(stmt, ParseResult.AugmentedAssignmentStmt): if multi_assign_statement and multi_assign_statement.right == stmt.right: multi_assign_statement.leftvalues.extend(stmt.leftvalues) print("{:s}:{:d}: joined with previous line into multi-assign statement".format(block.sourceref.file, stmt.lineno)) diff --git a/il65/symbols.py b/il65/symbols.py index f04b8874f..b5b985783 100644 --- a/il65/symbols.py +++ b/il65/symbols.py @@ -18,7 +18,8 @@ PrimitiveType = Union[int, float, str] REGISTER_SYMBOLS = {"A", "X", "Y", "AX", "AY", "XY", "SC", "SI"} REGISTER_SYMBOLS_RETURNVALUES = REGISTER_SYMBOLS | {"SZ"} -REGISTER_BYTES = {"A", "X", "Y", "SC", "SI"} +REGISTER_BYTES = {"A", "X", "Y"} +REGISTER_SBITS = {"SC", "SI", "SZ"} REGISTER_WORDS = {"AX", "AY", "XY"} # 5-byte cbm MFLPT format limitations: @@ -176,7 +177,7 @@ class SubroutineDef(SymbolDefinition): self.clobbered_registers = set() # type: Set[str] self.return_registers = [] # type: List[str] # ordered! for _, param in parameters: - if param in REGISTER_BYTES: + if param in REGISTER_BYTES | REGISTER_SBITS: self.clobbered_registers.add(param) elif param in REGISTER_WORDS: self.clobbered_registers.add(param[0]) @@ -514,6 +515,9 @@ def check_value_in_range(datatype: DataType, register: str, length: int, value: if register in REGISTER_BYTES: if value < 0 or value > 0xff: # type: ignore return "value out of range, must be (unsigned) byte for a single register" + elif register in REGISTER_SBITS: + if value not in (0, 1): + return "value out of range, must be 0 or 1 for a status bit register" elif register in REGISTER_WORDS: if value is None and datatype in (DataType.BYTE, DataType.WORD): return None diff --git a/lib/c64lib.ill b/lib/c64lib.ill index 81cfaacdd..a388061ac 100644 --- a/lib/c64lib.ill +++ b/lib/c64lib.ill @@ -619,31 +619,32 @@ sub input_chars (buffer: AX) -> (A?, Y) { } } -sub memcopy_basic () -> (A?, X?, Y?) { - ; ---- copy a memory block by using a BASIC ROM routine @todo fix code - ; it calls a function from the basic interpreter, so: - ; - BASIC ROM must be banked in - ; - the source block must be readable (so no RAM hidden under BASIC, Kernal, or I/O) - ; - the target block must be writable (so no RAM hidden under I/O) - ; higher addresses are copied first, so: - ; - moving data to higher addresses works even if areas overlap - ; - moving data to lower addresses only works if areas do not overlap - asm { - lda #src_start - sta $5f - stx $60 - lda #src_end - sta $5a - stx $5b - lda #<(target_start + src_end - src_start) - ldx #>(target_start + src_end - src_start) - sta $58 - stx $59 - jmp $a3bf - } -} +;sub memcopy_basic () -> (A?, X?, Y?) { +; ; ---- copy a memory block by using a BASIC ROM routine @todo fix code +; ; it calls a function from the basic interpreter, so: +; ; - BASIC ROM must be banked in +; ; - the source block must be readable (so no RAM hidden under BASIC, Kernal, or I/O) +; ; - the target block must be writable (so no RAM hidden under I/O) +; ; higher addresses are copied first, so: +; ; - moving data to higher addresses works even if areas overlap +; ; - moving data to lower addresses only works if areas do not overlap +; ; @todo fix this +; asm { +; lda #src_start +; sta $5f +; stx $60 +; lda #src_end +; sta $5a +; stx $5b +; lda #<(target_start + src_end - src_start) +; ldx #>(target_start + src_end - src_start) +; sta $58 +; stx $59 +; jmp $a3bf +; } +;} ; macro version of the above memcopy_basic routine: @todo macro support? ; MACRO PARAMS src_start, src_end, target_start diff --git a/reference.md b/reference.md index d39e57d61..2167210b5 100644 --- a/reference.md +++ b/reference.md @@ -228,6 +228,11 @@ tries to detect this situation however and optimize it itself if it finds the ca target1 = target2 = target3 [,...] = value-expression +### Augmented Assignments + +A special assignment is the *augmented assignment* where the value is modified in-place. +Several assignment operators are available: ``+=``, ``-=``, ``&=``, ``|=``, ``^=``, ``<<=``, ``>>=`` + ### Expressions diff --git a/testsource/numbergame.ill b/testsource/numbergame.ill new file mode 100644 index 000000000..e36515dd2 --- /dev/null +++ b/testsource/numbergame.ill @@ -0,0 +1,49 @@ +output prg,sys + +import "c64lib" + + +~ main { + var .text name = '?' * 80 + var .float myfloat = 2.33 + +start + A += 1 + A -= 1 + A &= %10011100 + A |= %10011100 + A ^= %10011100 + A <<= 1 + A >>= 1 + A += 2 + A -= 2 + + X += 1 + X -= 1 + X += 2 + X -= 2 + + Y += 1 + Y -= 1 + Y += 2 + Y -= 2 + + c64util.init_system() + A = c64.VMCSB + ;A |= 2 ; @todo A = A | 2 (complex expressions) + c64.VMCSB = A + ;c64.VMCSB |= 2 ; @todo c64.VMCSB |= 2 + + c64util.print_string("Enter your name: ") + c64util.input_chars(name) + c64.CHROUT('\n') + + A = 0 +printloop + c64util.print_byte_decimal(A) + c64.CHROUT('\n') + A++ + if A <20 goto printloop + return + +} \ No newline at end of file