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 .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):

View File

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

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, \
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 "<AugAssign {:s} {:s} {:s}>".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<left>\S+)\s*(?P<assignment>\+=|-=|\*=|/=|%=|//=|\*\*=|&=|\|=|\^=|>>=|<<=)\s*(?P<right>\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))

View File

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

View File

@ -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
ldx #>src_start
sta $5f
stx $60
lda #<src_end
ldx #>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
; ldx #>src_start
; sta $5f
; stx $60
; lda #<src_end
; ldx #>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

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
### Augmented Assignments
A special assignment is the *augmented assignment* where the value is modified in-place.
Several assignment operators are available: ``+=``, ``-=``, ``&=``, ``|=``, ``^=``, ``<<=``, ``>>=``
### 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
}