augmented assignments

This commit is contained in:
Irmen de Jong 2017-12-28 04:20:59 +01:00
parent ff39d15a01
commit 4e4baff9e0
7 changed files with 543 additions and 95 deletions

View File

@ -16,7 +16,7 @@ from functools import partial
from typing import TextIO, Set, Union, List from typing import TextIO, Set, Union, List
from .parse import ProgramFormat, ParseResult, Parser from .parse import ProgramFormat, ParseResult, Parser
from .symbols import Zeropage, DataType, ConstantDef, VariableDef, SubroutineDef, \ 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): class CodeError(Exception):
@ -216,8 +216,8 @@ class CodeGenerator:
if isinstance(stmt, ParseResult.Label) and stmt.name == "start": if isinstance(stmt, ParseResult.Label) and stmt.name == "start":
asmlines = [ asmlines = [
"\t\tcld\t\t\t; clear decimal flag", "\t\tcld\t\t\t; clear decimal flag",
"\t\tclc\t\t\t; clear carry flag" "\t\tclc\t\t\t; clear carry flag",
"\t\tclv\t\t\t; clear overflow flag" "\t\tclv\t\t\t; clear overflow flag",
] ]
statements.insert(index+1, ParseResult.InlineAsm(asmlines, 0)) statements.insert(index+1, ParseResult.InlineAsm(asmlines, 0))
break break
@ -371,12 +371,14 @@ class CodeGenerator:
else: else:
raise CodeError("can only return immediate values for now") # XXX raise CodeError("can only return immediate values for now") # XXX
self.p("\t\trts") self.p("\t\trts")
elif isinstance(stmt, ParseResult.AugmentedAssignmentStmt):
self.generate_augmented_assignment(stmt)
elif isinstance(stmt, ParseResult.AssignmentStmt): elif isinstance(stmt, ParseResult.AssignmentStmt):
self.generate_assignment(stmt) self.generate_assignment(stmt)
elif isinstance(stmt, ParseResult.Label): elif isinstance(stmt, ParseResult.Label):
self.p("\n{:s}\t\t\t\t; src l. {:d}".format(stmt.name, stmt.lineno)) self.p("\n{:s}\t\t\t\t; src l. {:d}".format(stmt.name, stmt.lineno))
elif isinstance(stmt, ParseResult.IncrDecrStmt): elif isinstance(stmt, (ParseResult.InplaceIncrStmt, ParseResult.InplaceDecrStmt)):
self.generate_incrdecr(stmt) self.generate_incr_or_decr(stmt)
elif isinstance(stmt, ParseResult.CallStmt): elif isinstance(stmt, ParseResult.CallStmt):
self.generate_call(stmt) self.generate_call(stmt)
elif isinstance(stmt, ParseResult.InlineAsm): elif isinstance(stmt, ParseResult.InlineAsm):
@ -393,36 +395,72 @@ class CodeGenerator:
raise CodeError("unknown statement " + repr(stmt)) raise CodeError("unknown statement " + repr(stmt))
self.previous_stmt_was_assignment = isinstance(stmt, ParseResult.AssignmentStmt) self.previous_stmt_was_assignment = isinstance(stmt, ParseResult.AssignmentStmt)
def generate_incrdecr(self, stmt: ParseResult.IncrDecrStmt) -> None: def generate_incr_or_decr(self, stmt: Union[ParseResult.InplaceIncrStmt, ParseResult.InplaceDecrStmt]) -> None:
if stmt.howmuch in (-1, 1): if stmt.what.datatype == DataType.FLOAT:
if isinstance(stmt.what, ParseResult.RegisterValue): raise CodeError("incr/decr on float not yet supported") # @todo support incr/decr on float
if stmt.howmuch == 1: else:
if stmt.what.register == 'A': assert type(stmt.howmuch) is int
self.p("\t\tadc #1") assert stmt.howmuch > 0
else: 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())) self.p("\t\tin{:s}".format(stmt.what.register.lower()))
else:
if stmt.what.register == 'A':
self.p("\t\tsbc #1")
else: 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())) 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: 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: else:
r_str = what.name or Parser.to_hex(what.address) raise CodeError("invalid incr/decr register")
if what.datatype == DataType.BYTE: elif isinstance(stmt.what, (ParseResult.MemMappedValue, ParseResult.IndirectValue)):
if stmt.howmuch == 1: what = stmt.what
self.p("\t\tinc " + r_str) 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: else:
self.p("\t\tdec " + r_str) self.p("\t\tsbc #{:d}".format(stmt.howmuch))
elif what.datatype == DataType.WORD: self.p("\t\tsta " + r_str)
# @todo verify this asm code self.p("\t\tpla")
if stmt.howmuch == 1: 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\tinc " + r_str)
self.p("\t\tbne +") self.p("\t\tbne +")
self.p("\t\tinc {:s}+1".format(r_str)) 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("\t\tdec {:s}+1".format(r_str))
self.p("+") self.p("+")
else: 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: else:
raise CodeError("cannot in/decrement " + str(stmt.what)) raise CodeError("cannot in/decrement memory of type " + str(what.datatype))
elif stmt.howmuch > 0: else:
raise NotImplementedError("incr by > 1") # XXX raise CodeError("cannot in/decrement " + str(stmt.what))
elif stmt.howmuch < 0:
raise NotImplementedError("decr by > 1") # XXX
def generate_call(self, stmt: ParseResult.CallStmt) -> None: def generate_call(self, stmt: ParseResult.CallStmt) -> None:
self.p("\t\t\t\t\t; src l. {:d}".format(stmt.lineno)) 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) unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a()): with self.preserving_registers(preserve_regs, loads_a_within=params_load_a()):
generate_param_assignments() 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 targetstr in REGISTER_WORDS:
if stmt.preserve_regs: if stmt.preserve_regs:
# cannot use zp scratch. This is very inefficient code! # cannot use zp scratch. This is very inefficient code!
@ -733,6 +770,305 @@ class CodeGenerator:
self.p("\t\tjsr " + targetstr) self.p("\t\tjsr " + targetstr)
generate_result_assignments() 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 generate_assignment(self, stmt: ParseResult.AssignmentStmt) -> None:
def unwrap_indirect(iv: ParseResult.IndirectValue) -> ParseResult.MemMappedValue: def unwrap_indirect(iv: ParseResult.IndirectValue) -> ParseResult.MemMappedValue:
if isinstance(iv.value, ParseResult.MemMappedValue): if isinstance(iv.value, ParseResult.MemMappedValue):

View File

@ -43,7 +43,7 @@ def main() -> None:
parsed = p.parse() parsed = p.parse()
if parsed: if parsed:
if args.nooptimize: if args.nooptimize:
print("not optimizing the parse tree!") p.print_warning("not optimizing the parse tree!")
else: else:
opt = Optimizer(parsed) opt = Optimizer(parsed)
parsed = opt.optimize() parsed = opt.optimize()
@ -57,11 +57,11 @@ def main() -> None:
mon_command_file = assembler.generate_breakpoint_list(program_filename) mon_command_file = assembler.generate_breakpoint_list(program_filename)
duration_total = time.perf_counter() - start duration_total = time.perf_counter() - start
print("Compile duration: {:.2f} seconds".format(duration_total)) print("Compile duration: {:.2f} seconds".format(duration_total))
print("Output file: ", program_filename) p.print_warning("Output file: " + program_filename)
print() print()
if args.startvice: if args.startvice:
print("Autostart vice emulator...") print("Autostart vice emulator...")
args = ["x64", "-remotemonitor", "-moncommands", mon_command_file, cmdline = ["x64", "-remotemonitor", "-moncommands", mon_command_file,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", program_filename] "-autostartprgmode", "1", "-autostart-warp", "-autostart", program_filename]
with open(os.devnull, "wb") as shutup: with open(os.devnull, "wb") as shutup:
subprocess.call(args, stdout=shutup) subprocess.call(cmdline, stdout=shutup)

View File

@ -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, \ from .symbols import SourceRef, SymbolTable, DataType, SymbolDefinition, SubroutineDef, LabelDef, \
zeropage, check_value_in_range, char_to_bytevalue, \ zeropage, check_value_in_range, char_to_bytevalue, \
PrimitiveType, VariableDef, ConstantDef, SymbolError, STRING_DATATYPES, \ 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): class ProgramFormat(enum.Enum):
@ -259,7 +259,7 @@ class ParseResult:
return False, "cannot explicitly assign from a status bit register alias" return False, "cannot explicitly assign from a status bit register alias"
if len(self.register) < len(other.register): if len(self.register) < len(other.register):
return False, "register size mismatch" 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" return False, "string address requires 16 bits combined register"
if isinstance(other, ParseResult.IntegerValue): if isinstance(other, ParseResult.IntegerValue):
if other.value is not None: if other.value is not None:
@ -399,6 +399,18 @@ class ParseResult:
def is_identity(self) -> bool: def is_identity(self) -> bool:
return all(lv == self.right for lv in self.leftvalues) 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 "<AugAssign {:s} {:s} {:s}>".format(str(self.leftvalues[0]), self.operator, str(self.right))
class ReturnStmt(_AstNode): class ReturnStmt(_AstNode):
def __init__(self, lineno: int, a: Optional['ParseResult.Value']=None, def __init__(self, lineno: int, a: Optional['ParseResult.Value']=None,
x: Optional['ParseResult.Value']=None, x: Optional['ParseResult.Value']=None,
@ -408,9 +420,17 @@ class ParseResult:
self.x = x self.x = x
self.y = y self.y = y
class IncrDecrStmt(_AstNode): class InplaceIncrStmt(_AstNode):
def __init__(self, what: 'ParseResult.Value', howmuch: int, lineno: int) -> None: def __init__(self, what: 'ParseResult.Value', howmuch: Union[int, float], lineno: int) -> None:
super().__init__(lineno) 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.what = what
self.howmuch = howmuch self.howmuch = howmuch
@ -437,7 +457,7 @@ class ParseResult:
self.desugared_output_assignments.clear() self.desugared_output_assignments.clear()
for name, value in self.arguments or []: 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" 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(): if not assignment.is_identity():
assignment.lineno = self.lineno assignment.lineno = self.lineno
self.desugared_call_arguments.append(assignment) self.desugared_call_arguments.append(assignment)
@ -1144,12 +1164,18 @@ class Parser:
what = self.parse_expression(line[:-2].rstrip()) what = self.parse_expression(line[:-2].rstrip())
if isinstance(what, ParseResult.IntegerValue): if isinstance(what, ParseResult.IntegerValue):
raise self.PError("cannot in/decrement a constant value") 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: else:
# perhaps it is an assignment statment # perhaps it is an augmented assignment statement
lhs, sep, rhs = line.partition("=") match = re.fullmatch(r"(?P<left>\S+)\s*(?P<assignment>\+=|-=|\*=|/=|%=|//=|\*\*=|&=|\|=|\^=|>>=|<<=)\s*(?P<right>\S.*)", line)
if sep: if match:
return self.parse_assignment(line) 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") raise self.PError("invalid statement")
def parse_call_or_goto(self, targetstr: str, argumentstr: str, outputstr: str, 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[1:], 2)
return int(text) return int(text)
def parse_assignment(self, line: str, parsed_rvalue: ParseResult.Value = None) -> ParseResult.AssignmentStmt: def parse_assignment(self, *parts) -> ParseResult.AssignmentStmt:
# parses assigning a value to one or more targets # parses the assignment of one rvalue to one or more lvalues
parts = line.split("=") l_values = [self.parse_expression(p) for p in parts[:-1]]
if parsed_rvalue: r_value = self.parse_expression(parts[-1])
r_value = parsed_rvalue if any(lv.constant for lv in l_values):
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):
raise self.PError("can't have a constant as assignment target, perhaps you wanted indirection [...] instead?") raise self.PError("can't have a constant as assignment target, perhaps you wanted indirection [...] instead?")
for lv in l_values: for lv in l_values:
assignable, reason = lv.assignable_from(r_value) assignable, reason = lv.assignable_from(r_value)
if not assignable: if not assignable:
raise self.PError("cannot assign {0} to {1}; {2}".format(r_value, lv, reason)) raise self.PError("cannot assign {0} to {1}; {2}".format(r_value, lv, reason))
if lv.datatype in (DataType.BYTE, DataType.WORD, DataType.MATRIX): if lv.datatype in (DataType.BYTE, DataType.WORD, DataType.MATRIX):
# truncate the rvalue if needed
if isinstance(r_value, ParseResult.FloatValue): if isinstance(r_value, ParseResult.FloatValue):
truncated, value = self.coerce_value(self.sourceref, lv.datatype, r_value.value) truncated, value = self.coerce_value(self.sourceref, lv.datatype, r_value.value)
if truncated: if truncated:
r_value = ParseResult.IntegerValue(int(value), datatype=lv.datatype, name=r_value.name) r_value = ParseResult.IntegerValue(int(value), datatype=lv.datatype, name=r_value.name)
return ParseResult.AssignmentStmt(l_values, r_value, self.sourceref.line) 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: def parse_return(self, line: str) -> ParseResult.ReturnStmt:
parts = line.split(maxsplit=1) parts = line.split(maxsplit=1)
if parts[0] != "return": if parts[0] != "return":
@ -1376,7 +1430,7 @@ class Parser:
raise self.PError(str(ex)) raise self.PError(str(ex))
elif text in REGISTER_WORDS: elif text in REGISTER_WORDS:
return ParseResult.RegisterValue(text, DataType.WORD) return ParseResult.RegisterValue(text, DataType.WORD)
elif text in REGISTER_BYTES: elif text in REGISTER_BYTES | REGISTER_SBITS:
return ParseResult.RegisterValue(text, DataType.BYTE) return ParseResult.RegisterValue(text, DataType.BYTE)
elif (text.startswith("'") and text.endswith("'")) or (text.startswith('"') and text.endswith('"')): 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) 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(']'): elif text.startswith('[') and text.endswith(']'):
return self.parse_indirect_value(text)[1] return self.parse_indirect_value(text)[1]
else: 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]: def parse_indirect_value(self, text: str, allow_mmapped_for_call: bool=False) -> Tuple[str, ParseResult.IndirectValue]:
indirect = text[1:-1].strip() indirect = text[1:-1].strip()
@ -1581,10 +1635,10 @@ class Optimizer:
self.remove_unused_subroutines(block) self.remove_unused_subroutines(block)
self.optimize_compare_with_zero(block) self.optimize_compare_with_zero(block)
for sub in block.symbols.iter_subroutines(True): for sub in block.symbols.iter_subroutines(True):
self.remove_identity_assigns(sub.sub_block) self.remove_identity_assigns(sub.sub_block)
self.combine_assignments_into_multi(sub.sub_block) self.combine_assignments_into_multi(sub.sub_block)
self.optimize_multiassigns(sub.sub_block) self.optimize_multiassigns(sub.sub_block)
self.optimize_compare_with_zero(sub.sub_block) self.optimize_compare_with_zero(sub.sub_block)
return self.parsed return self.parsed
def optimize_compare_with_zero(self, block: ParseResult.Block) -> None: def optimize_compare_with_zero(self, block: ParseResult.Block) -> None:
@ -1618,13 +1672,12 @@ class Optimizer:
if simplified: if simplified:
print("{:s}:{:d}: simplified comparison with zero".format(block.sourceref.file, stmt.lineno)) print("{:s}:{:d}: simplified comparison with zero".format(block.sourceref.file, stmt.lineno))
def combine_assignments_into_multi(self, block: ParseResult.Block) -> None: def combine_assignments_into_multi(self, block: ParseResult.Block) -> None:
# fold multiple consecutive assignments with the same rvalue into one multi-assignment # fold multiple consecutive assignments with the same rvalue into one multi-assignment
statements = [] # type: List[ParseResult._AstNode] statements = [] # type: List[ParseResult._AstNode]
multi_assign_statement = None multi_assign_statement = None
for stmt in block.statements: 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: if multi_assign_statement and multi_assign_statement.right == stmt.right:
multi_assign_statement.leftvalues.extend(stmt.leftvalues) multi_assign_statement.leftvalues.extend(stmt.leftvalues)
print("{:s}:{:d}: joined with previous line into multi-assign statement".format(block.sourceref.file, stmt.lineno)) print("{:s}:{:d}: joined with previous line into multi-assign statement".format(block.sourceref.file, stmt.lineno))

View File

@ -18,7 +18,8 @@ PrimitiveType = Union[int, float, str]
REGISTER_SYMBOLS = {"A", "X", "Y", "AX", "AY", "XY", "SC", "SI"} REGISTER_SYMBOLS = {"A", "X", "Y", "AX", "AY", "XY", "SC", "SI"}
REGISTER_SYMBOLS_RETURNVALUES = REGISTER_SYMBOLS | {"SZ"} 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"} REGISTER_WORDS = {"AX", "AY", "XY"}
# 5-byte cbm MFLPT format limitations: # 5-byte cbm MFLPT format limitations:
@ -176,7 +177,7 @@ class SubroutineDef(SymbolDefinition):
self.clobbered_registers = set() # type: Set[str] self.clobbered_registers = set() # type: Set[str]
self.return_registers = [] # type: List[str] # ordered! self.return_registers = [] # type: List[str] # ordered!
for _, param in parameters: for _, param in parameters:
if param in REGISTER_BYTES: if param in REGISTER_BYTES | REGISTER_SBITS:
self.clobbered_registers.add(param) self.clobbered_registers.add(param)
elif param in REGISTER_WORDS: elif param in REGISTER_WORDS:
self.clobbered_registers.add(param[0]) 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 register in REGISTER_BYTES:
if value < 0 or value > 0xff: # type: ignore if value < 0 or value > 0xff: # type: ignore
return "value out of range, must be (unsigned) byte for a single register" 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: elif register in REGISTER_WORDS:
if value is None and datatype in (DataType.BYTE, DataType.WORD): if value is None and datatype in (DataType.BYTE, DataType.WORD):
return None return None

View File

@ -619,31 +619,32 @@ sub input_chars (buffer: AX) -> (A?, Y) {
} }
} }
sub memcopy_basic () -> (A?, X?, Y?) { ;sub memcopy_basic () -> (A?, X?, Y?) {
; ---- copy a memory block by using a BASIC ROM routine @todo fix code ; ; ---- copy a memory block by using a BASIC ROM routine @todo fix code
; it calls a function from the basic interpreter, so: ; ; it calls a function from the basic interpreter, so:
; - BASIC ROM must be banked in ; ; - BASIC ROM must be banked in
; - the source block must be readable (so no RAM hidden under BASIC, Kernal, or I/O) ; ; - 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) ; ; - the target block must be writable (so no RAM hidden under I/O)
; higher addresses are copied first, so: ; ; higher addresses are copied first, so:
; - moving data to higher addresses works even if areas overlap ; ; - moving data to higher addresses works even if areas overlap
; - moving data to lower addresses only works if areas do not overlap ; ; - moving data to lower addresses only works if areas do not overlap
asm { ; ; @todo fix this
lda #<src_start ; asm {
ldx #>src_start ; lda #<src_start
sta $5f ; ldx #>src_start
stx $60 ; sta $5f
lda #<src_end ; stx $60
ldx #>src_end ; lda #<src_end
sta $5a ; ldx #>src_end
stx $5b ; sta $5a
lda #<(target_start + src_end - src_start) ; stx $5b
ldx #>(target_start + src_end - src_start) ; lda #<(target_start + src_end - src_start)
sta $58 ; ldx #>(target_start + src_end - src_start)
stx $59 ; sta $58
jmp $a3bf ; stx $59
} ; jmp $a3bf
} ; }
;}
; macro version of the above memcopy_basic routine: @todo macro support? ; macro version of the above memcopy_basic routine: @todo macro support?
; MACRO PARAMS src_start, src_end, target_start ; MACRO PARAMS src_start, src_end, target_start

View File

@ -228,6 +228,11 @@ tries to detect this situation however and optimize it itself if it finds the ca
target1 = target2 = target3 [,...] = value-expression 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 ### Expressions

49
testsource/numbergame.ill Normal file
View File

@ -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
}