mirror of
https://github.com/irmen/prog8.git
synced 2024-12-23 09:32:43 +00:00
augmented assignments
This commit is contained in:
parent
ff39d15a01
commit
4e4baff9e0
406
il65/codegen.py
406
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):
|
||||
|
10
il65/main.py
10
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)
|
||||
|
109
il65/parse.py
109
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 "<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))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
49
testsource/numbergame.ill
Normal 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
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user