mirror of
https://github.com/irmen/prog8.git
synced 2025-01-10 20:30:23 +00:00
various
This commit is contained in:
parent
a5b4849058
commit
a7465f480a
@ -126,14 +126,7 @@ class CodeGenerator:
|
||||
def initialize_variables(self) -> None:
|
||||
must_save_zp = self.parsed.clobberzp and self.parsed.restorezp
|
||||
if must_save_zp:
|
||||
self.p("; save zp")
|
||||
self.p("\t\tsei")
|
||||
self.p("\t\tldx #2")
|
||||
self.p("-\t\tlda $00,x")
|
||||
self.p("\t\tsta _il65_zp_backup-2,x")
|
||||
self.p("\t\tinx")
|
||||
self.p("\t\tbne -")
|
||||
|
||||
self.p("\t\tjsr il65_lib_zp.save_zeropage")
|
||||
# Only the vars from the ZeroPage need to be initialized here,
|
||||
# the vars in all other blocks are just defined and pre-filled there.
|
||||
zpblocks = [b for b in self.parsed.blocks if b.name == "ZP"]
|
||||
@ -173,17 +166,8 @@ class CodeGenerator:
|
||||
main_block_label = [b.label for b in self.parsed.blocks if b.name == "main"][0]
|
||||
if must_save_zp:
|
||||
self.p("\t\tjsr {:s}.start\t\t; call user code".format(main_block_label))
|
||||
self.p("; restore zp")
|
||||
self.p("\t\tcld")
|
||||
self.p("\t\tphp\n\t\tpha\n\t\ttxa\n\t\tpha\n\t\tsei")
|
||||
self.p("\t\tldx #2")
|
||||
self.p("-\t\tlda _il65_zp_backup-2,x")
|
||||
self.p("\t\tsta $00,x")
|
||||
self.p("\t\tinx")
|
||||
self.p("\t\tbne -")
|
||||
self.p("\t\tcli\n\t\tpla\n\t\ttax\n\t\tpla\n\t\tplp")
|
||||
self.p("\t\trts")
|
||||
self.p("_il65_zp_backup\t\t.fill 254, 0")
|
||||
self.p("\t\tjmp il65_lib_zp.restore_zeropage")
|
||||
else:
|
||||
self.p("\t\tjmp {:s}.start\t\t; call user code".format(main_block_label))
|
||||
|
||||
@ -532,6 +516,7 @@ class CodeGenerator:
|
||||
self.p("\t\tpla")
|
||||
elif what.datatype == DataType.WORD:
|
||||
if stmt.howmuch == 1:
|
||||
# mem.word +=/-= 1
|
||||
if is_incr:
|
||||
self.p("\t\tinc " + r_str)
|
||||
self.p("\t\tbne +")
|
||||
@ -545,7 +530,25 @@ class CodeGenerator:
|
||||
self.p("+\t\tdec " + r_str)
|
||||
self.p("\t\tpla")
|
||||
else:
|
||||
raise CodeError("cannot yet incr/decr 16 bit memory by more than 1") # @todo 16-bit incr/decr
|
||||
# mem.word +=/-= 2..255
|
||||
if is_incr:
|
||||
self.p("\t\tpha")
|
||||
self.p("\t\tclc")
|
||||
self.p("\t\tlda " + r_str)
|
||||
self.p("\t\tadc #{:d}".format(stmt.howmuch))
|
||||
self.p("\t\tsta " + r_str)
|
||||
self.p("\t\tbcc +")
|
||||
self.p("\t\tinc {:s}+1".format(r_str))
|
||||
self.p("+\t\tpla")
|
||||
else:
|
||||
self.p("\t\tpha")
|
||||
self.p("\t\tsec")
|
||||
self.p("\t\tlda " + r_str)
|
||||
self.p("\t\tsbc #{:d}".format(stmt.howmuch))
|
||||
self.p("\t\tsta " + r_str)
|
||||
self.p("\t\tbcs +")
|
||||
self.p("\t\tdec {:s}+1".format(r_str))
|
||||
self.p("+\t\tpla")
|
||||
else:
|
||||
raise CodeError("cannot in/decrement memory of type " + str(what.datatype))
|
||||
else:
|
||||
@ -718,13 +721,23 @@ class CodeGenerator:
|
||||
self.p(line_after_branch)
|
||||
|
||||
def branch_emitter_indirect_cond(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
|
||||
print("EMIT", stmt.target, stmt.condition, is_goto, target_indirect)
|
||||
assert is_goto and not stmt.condition.comparison_op
|
||||
assert stmt.condition.lvalue and not stmt.condition.rvalue
|
||||
assert stmt.condition.ifstatus in ("true", "not", "zero")
|
||||
assert not target_indirect
|
||||
cv = stmt.condition.lvalue.value # type: ignore
|
||||
if isinstance(cv, ParseResult.RegisterValue):
|
||||
raise CodeError("indirect registers not yet supported") # @todo indirect reg
|
||||
branch = "bne" if stmt.condition.ifstatus == "true" else "beq"
|
||||
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
|
||||
if cv.register in REGISTER_BYTES:
|
||||
self.p("\t\tst{:s} *+2\t; self-modify".format(cv.register.lower()))
|
||||
self.p("\t\tlda $ff")
|
||||
else:
|
||||
self.p("\t\tst{:s} (+)+1\t; self-modify".format(cv.register[0].lower()))
|
||||
self.p("\t\tst{:s} (+)+2\t; self-modify".format(cv.register[1].lower()))
|
||||
self.p("+\t\tlda $ffff")
|
||||
self.p("\t\t{:s} {:s}".format(branch, targetstr))
|
||||
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
|
||||
elif isinstance(cv, ParseResult.MemMappedValue):
|
||||
raise CodeError("memmapped indirect should not occur, use the variable without indirection")
|
||||
elif isinstance(cv, ParseResult.IntegerValue) and cv.constant:
|
||||
@ -917,27 +930,15 @@ 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:
|
||||
print("warning: {:s}:{:d}: indirect register pair call is quite inefficient, use a jump table in memory instead?"
|
||||
.format(self.cur_block.sourceref.file, stmt.lineno))
|
||||
if stmt.preserve_regs:
|
||||
# cannot use zp scratch. This is very inefficient code!
|
||||
print("warning: {:s}:{:d}: indirect register pair call, this is very inefficient"
|
||||
.format(self.cur_block.sourceref.file, stmt.lineno))
|
||||
self.p("\t\tst{:s} ++".format(targetstr[0].lower()))
|
||||
self.p("\t\tst{:s} +++".format(targetstr[1].lower()))
|
||||
self.p("\t\tjsr +")
|
||||
self.p("\t\tjmp ++++")
|
||||
self.p("+\t\tjmp (+)")
|
||||
self.p("+\t\t.byte 0\t; lo")
|
||||
self.p("+\t\t.byte 0\t; hi")
|
||||
self.p("+")
|
||||
# cannot use zp scratch because it may be used by the register backup. This is very inefficient code!
|
||||
self.p("\t\tjsr il65_lib.jsr_indirect_nozpuse_"+targetstr)
|
||||
|
||||
else:
|
||||
self.p("\t\tst{:s} {:s}".format(targetstr[0].lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
|
||||
self.p("\t\tst{:s} {:s}".format(targetstr[1].lower(), Parser.to_hex(Zeropage.SCRATCH_B2)))
|
||||
self.p("\t\tjsr +")
|
||||
self.p("\t\tjmp ++")
|
||||
self.p("+\t\tjmp ({:s})".format(Parser.to_hex(Zeropage.SCRATCH_B1)))
|
||||
self.p("+")
|
||||
self.p("\t\tjsr il65_lib.jsr_indirect_"+targetstr)
|
||||
else:
|
||||
self.p("\t\tjsr +")
|
||||
self.p("\t\tjmp ++")
|
||||
@ -1720,17 +1721,14 @@ class CodeGenerator:
|
||||
raise CodeError("can only assign a byte or word to a word", str(rvalue))
|
||||
elif lv.datatype == DataType.FLOAT:
|
||||
if rvalue.datatype == DataType.FLOAT:
|
||||
with self.preserving_registers({'A'}, loads_a_within=True):
|
||||
self.p("\t\tlda " + r_str)
|
||||
self.p("\t\tsta " + l_str)
|
||||
self.p("\t\tlda {:s}+1".format(r_str))
|
||||
self.p("\t\tsta {:s}+1".format(l_str))
|
||||
self.p("\t\tlda {:s}+2".format(r_str))
|
||||
self.p("\t\tsta {:s}+2".format(l_str))
|
||||
self.p("\t\tlda {:s}+3".format(r_str))
|
||||
self.p("\t\tsta {:s}+3".format(l_str))
|
||||
self.p("\t\tlda {:s}+4".format(r_str))
|
||||
self.p("\t\tsta {:s}+4".format(l_str))
|
||||
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
|
||||
self.p("\t\tlda #<" + r_str)
|
||||
self.p("\t\tsta c64.SCRATCH_ZPWORD")
|
||||
self.p("\t\tlda #>" + r_str)
|
||||
self.p("\t\tsta c64.SCRATCH_ZPWORD+1")
|
||||
self.p("\t\tldx #<" + l_str)
|
||||
self.p("\t\tldy #>" + l_str)
|
||||
self.p("\t\tjsr c64_lib.copy_mflt")
|
||||
elif rvalue.datatype == DataType.BYTE:
|
||||
with self.preserving_registers({'A', 'X', 'Y'}):
|
||||
self.p("\t\tldy " + r_str)
|
||||
|
@ -35,11 +35,11 @@ def main() -> None:
|
||||
print("\n" + description)
|
||||
|
||||
start = time.perf_counter()
|
||||
pp = PreprocessingParser(args.sourcefile, )
|
||||
pp = PreprocessingParser(args.sourcefile, set())
|
||||
sourcelines, symbols = pp.preprocess()
|
||||
# symbols.print_table()
|
||||
|
||||
p = Parser(args.sourcefile, args.output, sourcelines, ppsymbols=symbols, sub_usage=pp.result.subroutine_usage)
|
||||
p = Parser(args.sourcefile, args.output, set(), sourcelines=sourcelines, ppsymbols=symbols, sub_usage=pp.result.subroutine_usage)
|
||||
parsed = p.parse()
|
||||
if parsed:
|
||||
if args.nooptimize:
|
||||
|
@ -472,12 +472,29 @@ class ParseResult:
|
||||
if not assignment.is_identity():
|
||||
assignment.lineno = self.lineno
|
||||
self.desugared_call_arguments.append(assignment)
|
||||
for register, value in self.outputvars or []:
|
||||
rvalue = parser.parse_expression(register)
|
||||
assignment = ParseResult.AssignmentStmt([value], rvalue, self.lineno)
|
||||
# note: we need the identity assignment here or the output register handling generates buggy code
|
||||
assignment.lineno = self.lineno
|
||||
self.desugared_output_assignments.append(assignment)
|
||||
if all(not isinstance(v, ParseResult.RegisterValue) for r, v in self.outputvars or []):
|
||||
# if none of the output variables are registers, we can simply generate the assignments without issues
|
||||
for register, value in self.outputvars or []:
|
||||
rvalue = parser.parse_expression(register)
|
||||
assignment = ParseResult.AssignmentStmt([value], rvalue, self.lineno)
|
||||
assignment.lineno = self.lineno
|
||||
self.desugared_output_assignments.append(assignment)
|
||||
else:
|
||||
result_reg_mapping = [(register, value.register, value) for register, value in self.outputvars or []
|
||||
if isinstance(value, ParseResult.RegisterValue)]
|
||||
if any(r[0] != r[1] for r in result_reg_mapping):
|
||||
# not all result parameter registers line up with the correct order of registers in the statement,
|
||||
# reshuffling call results is not supported yet.
|
||||
raise parser.PError("result registers and/or their ordering is not the same as in the "
|
||||
"subroutine definition, this isn't supported yet")
|
||||
else:
|
||||
# no register alignment issues, just generate the assignments
|
||||
# note: do not remove the identity assignment here or the output register handling generates buggy code
|
||||
for register, value in self.outputvars or []:
|
||||
rvalue = parser.parse_expression(register)
|
||||
assignment = ParseResult.AssignmentStmt([value], rvalue, self.lineno)
|
||||
assignment.lineno = self.lineno
|
||||
self.desugared_output_assignments.append(assignment)
|
||||
|
||||
class InlineAsm(_AstNode):
|
||||
def __init__(self, asmlines: List[str], lineno: int) -> None:
|
||||
@ -552,8 +569,8 @@ class ParseResult:
|
||||
|
||||
|
||||
class Parser:
|
||||
def __init__(self, filename: str, outputdir: str, sourcelines: List[Tuple[int, str]] = None, parsing_import: bool = False,
|
||||
ppsymbols: SymbolTable = None, sub_usage: Dict=None) -> None:
|
||||
def __init__(self, filename: str, outputdir: str, existing_imports: Set[str], parsing_import: bool = False,
|
||||
sourcelines: List[Tuple[int, str]] = None, ppsymbols: SymbolTable = None, sub_usage: Dict=None) -> None:
|
||||
self.result = ParseResult(filename)
|
||||
if sub_usage is not None:
|
||||
# re-use the (global) subroutine usage tracking
|
||||
@ -571,6 +588,7 @@ class Parser:
|
||||
self.root_scope.set_zeropage(self.result.zeropage)
|
||||
self.ppsymbols = ppsymbols # symboltable from preprocess phase
|
||||
self.print_block_parsing = True
|
||||
self.existing_imports = existing_imports
|
||||
|
||||
def load_source(self, filename: str) -> List[Tuple[int, str]]:
|
||||
with open(filename, "rU") as source:
|
||||
@ -624,6 +642,7 @@ class Parser:
|
||||
def parse_file(self) -> ParseResult:
|
||||
print("\nparsing", self.sourceref.file)
|
||||
self._parse_1()
|
||||
self._parse_import_file("il65lib") # compiler support library is always imported.
|
||||
self._parse_2()
|
||||
return self.result
|
||||
|
||||
@ -658,7 +677,7 @@ class Parser:
|
||||
block = self.parse_block()
|
||||
if block:
|
||||
self.result.add_block(block)
|
||||
elif next_line.startswith("import"):
|
||||
elif next_line.startswith(("import ", "import\t")):
|
||||
self.parse_import()
|
||||
else:
|
||||
break
|
||||
@ -861,7 +880,7 @@ class Parser:
|
||||
def parse_import(self) -> None:
|
||||
line = self.next_line()
|
||||
line = line.lstrip()
|
||||
if not line.startswith("import"):
|
||||
if not line.startswith(("import ", "import\t")):
|
||||
raise self.PError("expected import")
|
||||
try:
|
||||
_, arg = line.split(maxsplit=1)
|
||||
@ -872,6 +891,9 @@ class Parser:
|
||||
filename = arg[1:-1]
|
||||
if not filename:
|
||||
raise self.PError("invalid filename")
|
||||
self._parse_import_file(filename)
|
||||
|
||||
def _parse_import_file(self, filename: str) -> None:
|
||||
filename_at_source_location = os.path.join(os.path.split(self.sourceref.file)[0], filename)
|
||||
filename_at_libs_location = os.path.join(os.getcwd(), "lib", filename)
|
||||
candidates = [filename,
|
||||
@ -882,10 +904,12 @@ class Parser:
|
||||
filename_at_libs_location+".ill"]
|
||||
for filename in candidates:
|
||||
if os.path.isfile(filename):
|
||||
print("importing", filename)
|
||||
if not self.check_import_okay(filename):
|
||||
return
|
||||
self.print_import_progress("importing", filename)
|
||||
parser = self.create_import_parser(filename, self.outputdir)
|
||||
result = parser.parse()
|
||||
print("\ncontinuing", self.sourceref.file)
|
||||
self.print_import_progress("\ncontinuing", self.sourceref.file)
|
||||
if result:
|
||||
# merge the symbol table of the imported file into our own
|
||||
try:
|
||||
@ -898,8 +922,11 @@ class Parser:
|
||||
raise self.PError("Error while parsing imported file")
|
||||
raise self.PError("imported file not found")
|
||||
|
||||
def print_import_progress(self, message: str, *args: str) -> None:
|
||||
print(message, *args)
|
||||
|
||||
def create_import_parser(self, filename: str, outputdir: str) -> 'Parser':
|
||||
return Parser(filename, outputdir, parsing_import=True, ppsymbols=self.ppsymbols, sub_usage=self.result.subroutine_usage)
|
||||
return Parser(filename, outputdir, self.existing_imports, True, ppsymbols=self.ppsymbols, sub_usage=self.result.subroutine_usage)
|
||||
|
||||
def parse_block(self) -> Optional[ParseResult.Block]:
|
||||
# first line contains block header "~ [name] [addr]" followed by a '{'
|
||||
@ -1276,7 +1303,7 @@ class Parser:
|
||||
raise self.PError("all call arguments should have a name or be matched on a named parameter")
|
||||
if isinstance(target, (type(None), ParseResult.Value)):
|
||||
# special case for the C-64 lib's print function, to be able to use it with a single character argument
|
||||
if target.name == "c64util.print_string" and len(arguments) == 1 and len(arguments[0][0]) > 1:
|
||||
if target.name == "c64util.print_string" and len(arguments) == 1 and isinstance(arguments[0], str):
|
||||
if arguments[0][1].startswith("'") and arguments[0][1].endswith("'"):
|
||||
target = self.parse_expression("c64.CHROUT")
|
||||
address = target.address
|
||||
@ -1662,6 +1689,14 @@ class Parser:
|
||||
self.print_warning("if_not condition inverted to if")
|
||||
return result
|
||||
|
||||
def check_import_okay(self, filename: str) -> bool:
|
||||
if filename == self.sourceref.file and not filename.endswith("il65lib.ill"):
|
||||
raise self.PError("can't import itself")
|
||||
if filename in self.existing_imports:
|
||||
return False
|
||||
self.existing_imports.add(filename)
|
||||
return True
|
||||
|
||||
|
||||
class Optimizer:
|
||||
def __init__(self, parseresult: ParseResult) -> None:
|
||||
@ -1765,8 +1800,7 @@ class Optimizer:
|
||||
block.symbols.discard_sub(sub.name)
|
||||
discarded.append(sub.name)
|
||||
if discarded:
|
||||
print("{}: discarded unused subroutines from block '{:s}': ".format(block.sourceref, block.name), end="")
|
||||
print(", ".join(discarded))
|
||||
print("{}: discarded {:d} unused subroutines from block '{:s}'".format(block.sourceref, len(discarded), block.name))
|
||||
|
||||
|
||||
def _value_sortkey(value: ParseResult.Value) -> int:
|
||||
|
@ -6,14 +6,14 @@ Written by Irmen de Jong (irmen@razorvine.net)
|
||||
License: GNU GPL 3.0, see LICENSE
|
||||
"""
|
||||
|
||||
from typing import List, Tuple
|
||||
from typing import List, Tuple, Set
|
||||
from .parse import Parser, ParseResult, SymbolTable, SymbolDefinition
|
||||
from .symbols import SourceRef
|
||||
|
||||
|
||||
class PreprocessingParser(Parser):
|
||||
def __init__(self, filename: str, parsing_import: bool=False) -> None:
|
||||
super().__init__(filename, "", parsing_import=parsing_import)
|
||||
def __init__(self, filename: str, existing_imports: Set[str], parsing_import: bool=False) -> None:
|
||||
super().__init__(filename, "", existing_imports=existing_imports, parsing_import=parsing_import)
|
||||
self.print_block_parsing = False
|
||||
|
||||
def preprocess(self) -> Tuple[List[Tuple[int, str]], SymbolTable]:
|
||||
@ -37,7 +37,7 @@ class PreprocessingParser(Parser):
|
||||
return lines
|
||||
|
||||
def parse_file(self) -> ParseResult:
|
||||
print("\npreprocessing", self.sourceref.file)
|
||||
print("preprocessing", self.sourceref.file)
|
||||
self._parse_1()
|
||||
return self.result
|
||||
|
||||
@ -63,4 +63,7 @@ class PreprocessingParser(Parser):
|
||||
super().parse_subroutine_def(line)
|
||||
|
||||
def create_import_parser(self, filename: str, outputdir: str) -> 'Parser':
|
||||
return PreprocessingParser(filename, parsing_import=True)
|
||||
return PreprocessingParser(filename, parsing_import=True, existing_imports=self.existing_imports)
|
||||
|
||||
def print_import_progress(self, message: str, *args: str) -> None:
|
||||
pass
|
||||
|
@ -208,6 +208,7 @@ class SubroutineDef(SymbolDefinition):
|
||||
class Zeropage:
|
||||
SCRATCH_B1 = 0x02
|
||||
SCRATCH_B2 = 0x03
|
||||
SCRATCH_W1 = 0xfd # $fd/$fe
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.unused_bytes = [] # type: List[int]
|
||||
@ -218,13 +219,13 @@ class Zeropage:
|
||||
if self._configured:
|
||||
raise SymbolError("cannot configure the ZP multiple times")
|
||||
if clobber_zp:
|
||||
self.unused_bytes = list(range(0x04, 0x80))
|
||||
self.unused_words = list(range(0x80, 0x100, 2))
|
||||
self.unused_bytes = list(range(0x04, 0x80)) + [0xfc, 0xff]
|
||||
self.unused_words = list(range(0x80, 0xfc, 2))
|
||||
else:
|
||||
# these are valid for the C-64:
|
||||
# ($02 and $03 are reserved as scratch addresses for various routines)
|
||||
self.unused_bytes = [0x06, 0x0a, 0x2a, 0x52, 0x93] # 5 zp variables (8 bits each)
|
||||
self.unused_words = [0x04, 0xf7, 0xf9, 0xfb, 0xfd] # 5 zp variables (16 bits each)
|
||||
# these are valid for the C-64 (when no RS232 I/O is performed):
|
||||
# ($02, $03, $fd-$fe are reserved as scratch addresses for various routines)
|
||||
self.unused_bytes = [0x04, 0x05, 0x06, 0x2a, 0x52] # 5 zp variables (1 byte each)
|
||||
self.unused_words = [0xf7, 0xf9, 0xfb] # 3 zp word variables (2 bytes each)
|
||||
assert self.SCRATCH_B1 not in self.unused_bytes and self.SCRATCH_B1 not in self.unused_words
|
||||
assert self.SCRATCH_B2 not in self.unused_bytes and self.SCRATCH_B2 not in self.unused_words
|
||||
self._configured = True
|
||||
@ -252,7 +253,7 @@ class SymbolTable:
|
||||
self.parent = parent
|
||||
self.owning_block = owning_block
|
||||
self.eval_dict = None
|
||||
self._zeropage = parent._zeropage if parent else None
|
||||
self._zeropage = parent._zeropage if parent else None # type: Zeropage
|
||||
|
||||
def set_zeropage(self, zp: Zeropage) -> None:
|
||||
if self._zeropage is None:
|
||||
|
@ -10,8 +10,9 @@
|
||||
output raw
|
||||
|
||||
~ c64 {
|
||||
memory SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
|
||||
memory SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
|
||||
memory .byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
|
||||
memory .byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
|
||||
memory .word SCRATCH_ZPWORD = $fd ; scratch word in ZP ($fd/$fe)
|
||||
|
||||
memory .byte COLOR = $0286 ; cursor color
|
||||
memory .word CINV = $0314 ; IRQ vector
|
||||
@ -335,6 +336,9 @@ sub GETADRAY () -> (AY, X?) {
|
||||
|
||||
sub print_string (address: XY) -> (A?, Y?) {
|
||||
; ---- print null terminated string from X/Y
|
||||
; note: the IL65 compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to c64.CHROUT of that single char.
|
||||
asm {
|
||||
stx c64.SCRATCH_ZP1
|
||||
sty c64.SCRATCH_ZP2
|
||||
@ -433,7 +437,7 @@ hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
|
||||
}
|
||||
|
||||
; var .array(4) word2hex_output_array @todo support to use array/matrix type by address
|
||||
var .text word2hex_output = "123" ; 0-terminated, 4 bytes total @todo remove if array works
|
||||
var .text word2hex_output = "123" ; 0-terminated, 4 bytes total @todo remove once array works
|
||||
sub word2hex (word: XY) -> (A?, X?, Y?) {
|
||||
; ---- convert 16 bit word in X/Y into hexadecimal string into memory 'word2hex_output'
|
||||
asm {
|
||||
@ -706,3 +710,33 @@ sub input_chars (buffer: AX) -> (A?, Y) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
~ c64_lib {
|
||||
|
||||
asm {
|
||||
|
||||
; ---- copy a 5 byte MFLT floating point variable to another place
|
||||
; input: X/Y = source address, SCRATCH_ZPWORD = destination address
|
||||
copy_mflt stx c64.SCRATCH_ZP1
|
||||
sty c64.SCRATCH_ZPWORD+1
|
||||
ldy #0
|
||||
lda (c64.SCRATCH_ZP1),y
|
||||
sta (c64.SCRATCH_ZPWORD),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZP1),y
|
||||
sta (c64.SCRATCH_ZPWORD),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZP1),y
|
||||
sta (c64.SCRATCH_ZPWORD),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZP1),y
|
||||
sta (c64.SCRATCH_ZPWORD),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZP1),y
|
||||
sta (c64.SCRATCH_ZPWORD),y
|
||||
ldy c64.SCRATCH_ZPWORD+1
|
||||
rts
|
||||
}
|
||||
|
||||
}
|
||||
|
88
lib/il65lib.ill
Normal file
88
lib/il65lib.ill
Normal file
@ -0,0 +1,88 @@
|
||||
; IL65 internal library routines
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net)
|
||||
; License: GNU GPL 3.0, see LICENSE
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
output raw
|
||||
|
||||
~ il65_lib_zp {
|
||||
; note: separate block so the 64tass assembler can remove this when no zp restore is required
|
||||
|
||||
asm {
|
||||
|
||||
; ---- store the Zeropage in a backup area
|
||||
save_zeropage
|
||||
sei
|
||||
ldx #2
|
||||
- lda $00,x
|
||||
sta zp_backup-2,x
|
||||
inx
|
||||
bne -
|
||||
rts
|
||||
|
||||
restore_zeropage
|
||||
php
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
sei
|
||||
ldx #2
|
||||
- lda zp_backup-2,x
|
||||
sta $00,x
|
||||
inx
|
||||
bne -
|
||||
cli
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
plp
|
||||
rts
|
||||
|
||||
zp_backup .fill 254, 0
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
~ il65_lib {
|
||||
; note: the following two ZP scratch registers must be the same as in c64lib
|
||||
memory SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
|
||||
memory SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
|
||||
|
||||
|
||||
asm {
|
||||
|
||||
; ---- jmp (indirect) routines for register pairs containing the indirect address
|
||||
jsr_indirect_nozpuse_AX
|
||||
sta jsr_indirect_tmp
|
||||
stx jsr_indirect_tmp+1
|
||||
jmp (jsr_indirect_tmp)
|
||||
jsr_indirect_nozpuse_AY
|
||||
sta jsr_indirect_tmp
|
||||
sty jsr_indirect_tmp+1
|
||||
jmp (jsr_indirect_tmp)
|
||||
jsr_indirect_nozpuse_XY
|
||||
stx jsr_indirect_tmp
|
||||
sty jsr_indirect_tmp+1
|
||||
jmp (jsr_indirect_tmp)
|
||||
jsr_indirect_tmp
|
||||
.byte 0, 0
|
||||
|
||||
|
||||
jsr_indirect_AX
|
||||
sta SCRATCH_ZP1
|
||||
stx SCRATCH_ZP2
|
||||
jmp (SCRATCH_ZP1)
|
||||
jsr_indirect_AY
|
||||
sta SCRATCH_ZP1
|
||||
sty SCRATCH_ZP2
|
||||
jmp (SCRATCH_ZP1)
|
||||
jsr_indirect_XY
|
||||
stx SCRATCH_ZP1
|
||||
sty SCRATCH_ZP2
|
||||
jmp (SCRATCH_ZP1)
|
||||
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
; IL65 integer math library for 6502
|
||||
; (floating point math is done via the C-64's BASIC ROM routines)
|
||||
;
|
||||
; some more interesting routines can be found here http://6502org.wikidot.com/software-math
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net)
|
||||
; License: GNU GPL 3.0, see LICENSE
|
||||
;
|
||||
@ -10,9 +12,79 @@
|
||||
output raw
|
||||
|
||||
~ math {
|
||||
; note: the following two ZP scratch registers must be the same as in c64lib
|
||||
memory SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
|
||||
memory SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
|
||||
|
||||
|
||||
; @todo some interesting routines can be found here http://6502org.wikidot.com/software-math
|
||||
|
||||
sub multiply_bytes (byte1: X, byte2: Y) -> (A, X?) {
|
||||
; ---- multiply 2 bytes, result as byte in A (signed or unsigned)
|
||||
asm {
|
||||
stx SCRATCH_ZP1
|
||||
sty SCRATCH_ZP2
|
||||
ldx #8
|
||||
- asl a
|
||||
asl SCRATCH_ZP1
|
||||
bcc +
|
||||
clc
|
||||
adc SCRATCH_ZP2
|
||||
+ dex
|
||||
bne -
|
||||
rts
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub multiply_bytes_16 (byte1: X, byte2: Y) -> (A?, XY) {
|
||||
; ---- multiply 2 bytes, result as word in X/Y (unsigned)
|
||||
asm {
|
||||
lda #0
|
||||
_m_with_add stx SCRATCH_ZP1
|
||||
sty SCRATCH_ZP2
|
||||
ldx #8
|
||||
lsr SCRATCH_ZP1
|
||||
- bcc +
|
||||
clc
|
||||
adc SCRATCH_ZP2
|
||||
+ ror a
|
||||
ror SCRATCH_ZP1
|
||||
dex
|
||||
bne -
|
||||
tay
|
||||
ldx SCRATCH_ZP1
|
||||
rts
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub multiply_bytes_addA_16 (byte1: X, byte2: Y, add: A) -> (A?, XY) {
|
||||
; ---- multiply 2 bytes and add A, result as word in X/Y (unsigned)
|
||||
asm {
|
||||
jmp multiply_bytes_16._m_with_add
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub divide_bytes (numerator: X, denominator: Y) -> (X, A) {
|
||||
; ---- divide X by Y, result quotient in X, remainder in A (unsigned)
|
||||
; division by zero will result in quotient = 255 and remainder = original number
|
||||
asm {
|
||||
stx SCRATCH_ZP1
|
||||
sty SCRATCH_ZP2
|
||||
lda #0
|
||||
ldx #8
|
||||
asl SCRATCH_ZP1
|
||||
- rol a
|
||||
cmp SCRATCH_ZP2
|
||||
bcc +
|
||||
sbc SCRATCH_ZP2
|
||||
+ rol SCRATCH_ZP1
|
||||
dex
|
||||
bne -
|
||||
ldx SCRATCH_ZP1
|
||||
rts
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
13
reference.md
13
reference.md
@ -86,7 +86,7 @@ The following 6502 hardware registers are directly accessible in your code (and
|
||||
The zero page locations ``$02`` - ``$ff`` can be regarded as 254 other registers because
|
||||
they take less clock cycles to access and need fewer instruction bytes than access to other memory locations.
|
||||
Theoretically you can use all of them in your program but there are a few limitations:
|
||||
- ``$02`` and ``$03`` are reserved for internal use as scratch registers by IL65
|
||||
- the four locations ``$02``, ``$03``, ``$fd - $fe`` are reserved for internal use as scratch registers by IL65
|
||||
- most other addresses often are in use by the machine's operating system or kernal,
|
||||
and overwriting them can crash the machine. Your program must take over the entire
|
||||
system to be able to safely use all zero page locations.
|
||||
@ -95,10 +95,11 @@ Theoretically you can use all of them in your program but there are a few limita
|
||||
|
||||
For the Commodore-64 here is a list of free-to-use zero page locations even when its BASIC and KERNAL are active:
|
||||
|
||||
``$02`` - ``$03`` (but see remark above); ``$04`` - ``$05``; ``$06``;
|
||||
``$0a``; ``$2a``; ``$52``; ``$93``;
|
||||
``$02``; ``$03``; ``$04``; ``$05``; ``$06``; ``$2a``; ``$52``;
|
||||
``$f7`` - ``$f8``; ``$f9`` - ``$fa``; ``$fb`` - ``$fc``; ``$fd`` - ``$fe``
|
||||
|
||||
The four reserved locations mentioned above are subtracted from this set, leaving you with
|
||||
five 1-byte and three 2-byte usable zero page registers.
|
||||
IL65 knows about all this: it will use the above zero page locations to place its ZP variables in,
|
||||
until they're all used up. You can instruct it to treat your program as taking over the entire
|
||||
machine, in which case all of the zero page locations are suddenly available for variables.
|
||||
@ -193,6 +194,9 @@ However you can specify some options globally in your program to change this beh
|
||||
- ``zp clobber, restore``
|
||||
Use the whole zeropage, but make a backup copy of the original values at program start.
|
||||
When your program exits, the original ZP is restored and you drop back to the BASIC prompt.
|
||||
Not that the interrupts are *disabled* when your program is entered!
|
||||
(you want/have to set your own IRQ routine because the default one will write to
|
||||
various locations in the zeropage)
|
||||
|
||||
If you use ``zp clobber``, you can no longer use BASIC or KERNAL routines,
|
||||
because these depend on most of the locations in the ZP. This includes most of the floating-point
|
||||
@ -231,7 +235,8 @@ want to work on later, because the contents of the ignored block are not syntact
|
||||
### Importing, Including and Binary-Including Files
|
||||
|
||||
import "filename[.ill]"
|
||||
Can only be used outside of a block (usually at the top of your file).
|
||||
Must be used *after* any global option statements such as ``output``,
|
||||
and can only be used outside of a block. But can otherwise occur between any blocks.
|
||||
Reads everything from the named IL65 file at this point and compile it as a normal part of the program.
|
||||
|
||||
asminclude "filename.txt", scopelabel
|
||||
|
@ -21,13 +21,13 @@ start
|
||||
if_ge goto label
|
||||
if_gt goto label
|
||||
if_cc goto value
|
||||
if_cc goto memvalue
|
||||
;if_cc goto memvalue
|
||||
if_cc goto #value
|
||||
if_cc goto #memvalue
|
||||
;if_cc goto #memvalue
|
||||
if_cc goto [value]
|
||||
if_cc goto [memvalue]
|
||||
if_cc goto $c000
|
||||
if_cc goto [$c000.word]
|
||||
;if_cc goto [memvalue]
|
||||
;if_cc goto $c000
|
||||
;if_cc goto [$c000.word]
|
||||
|
||||
label
|
||||
; conditional if with a single 'truth' value (register)
|
||||
@ -59,14 +59,20 @@ label2
|
||||
if_zero memvalue goto label3
|
||||
if memvalue goto label3
|
||||
|
||||
; conditional if with a single 'truth' value (indirect address)
|
||||
if_true [$c000] goto label
|
||||
if_not [$c000] goto label
|
||||
if_zero [$c000] goto label
|
||||
if [$c000] goto label
|
||||
; if_true [XY] goto label ; @todo support indirect reg
|
||||
|
||||
label3
|
||||
; conditional if with a single 'truth' value (indirect address)
|
||||
if_true [$c000] goto label4
|
||||
if_not [$c000] goto label4
|
||||
if_zero [$c000] goto label4
|
||||
if [$c000] goto label4
|
||||
if_true [XY] goto label4 ; @todo support indirect reg
|
||||
if_true [AY] goto label4 ; @todo support indirect reg
|
||||
if_true [AX] goto label4 ; @todo support indirect reg
|
||||
if_true [X] goto label4 ; @todo support indirect reg
|
||||
if_true [A] goto label4 ; @todo support indirect reg
|
||||
if_true [Y] goto label4 ; @todo support indirect reg
|
||||
|
||||
label4
|
||||
return
|
||||
}
|
||||
|
||||
@ -134,4 +140,4 @@ label1
|
||||
|
||||
label2
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
output prg,basic
|
||||
;reg_preserve off ; @todo global option
|
||||
;reg_preserve off ; @todo global option off/on default off?
|
||||
|
||||
import "c64lib"
|
||||
|
||||
@ -14,70 +14,6 @@ import "c64lib"
|
||||
start
|
||||
c64util.init_system()
|
||||
|
||||
|
||||
XY = $0401
|
||||
c64util.print_word_decimal(XY)
|
||||
c64.CHROUT('\n')
|
||||
XY--
|
||||
c64util.print_word_decimal(XY)
|
||||
c64.CHROUT('\n')
|
||||
XY--
|
||||
c64util.print_word_decimal(XY)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
AX = $0401
|
||||
c64util.print_word_decimal(AX)
|
||||
c64.CHROUT('\n')
|
||||
AX--
|
||||
c64util.print_word_decimal(AX)
|
||||
c64.CHROUT('\n')
|
||||
AX--
|
||||
c64util.print_word_decimal(AX)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
AY = $0401
|
||||
c64util.print_word_decimal(AY)
|
||||
c64.CHROUT('\n')
|
||||
AY--
|
||||
c64util.print_word_decimal(AY)
|
||||
c64.CHROUT('\n')
|
||||
AY--
|
||||
c64util.print_word_decimal(AY)
|
||||
c64.CHROUT('\n')
|
||||
c64.CHROUT('\n')
|
||||
|
||||
XY = $03ff
|
||||
c64util.print_word_decimal(XY)
|
||||
c64.CHROUT('\n')
|
||||
XY++
|
||||
c64util.print_word_decimal(XY)
|
||||
c64.CHROUT('\n')
|
||||
XY++
|
||||
c64util.print_word_decimal(XY)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
AX = $03ff
|
||||
c64util.print_word_decimal(AX)
|
||||
c64.CHROUT('\n')
|
||||
AX++
|
||||
c64util.print_word_decimal(AX)
|
||||
c64.CHROUT('\n')
|
||||
AX++
|
||||
c64util.print_word_decimal(AX)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
AY = $03ff
|
||||
c64util.print_word_decimal(AY)
|
||||
c64.CHROUT('\n')
|
||||
AY++
|
||||
c64util.print_word_decimal(AY)
|
||||
c64.CHROUT('\n')
|
||||
AY++
|
||||
c64util.print_word_decimal(AY)
|
||||
c64.CHROUT('\n')
|
||||
c64.CHROUT('\n')
|
||||
|
||||
|
||||
A = c64.VMCSB
|
||||
A |= 2 ; @todo c64.VMCSB |= 2
|
||||
c64.VMCSB = A
|
||||
@ -115,7 +51,7 @@ printloop
|
||||
c64util.print_string("es")
|
||||
ask_guess
|
||||
c64util.print_string(" left.\nWhat is your next guess? ")
|
||||
A = c64util.input_chars(guess)
|
||||
Y = c64util.input_chars(guess)
|
||||
c64.CHROUT('\n')
|
||||
[$22.word] = guess
|
||||
c64.FREADSTR(A)
|
||||
|
@ -32,14 +32,14 @@ start2
|
||||
c64util.print_string(greeting)
|
||||
c64util.print_pstring(p_greeting)
|
||||
c64util.print_byte_decimal(0)
|
||||
c64util.print_byte_hex(0)
|
||||
c64util.print_byte_hex(0, 0)
|
||||
c64.CHROUT(13)
|
||||
c64util.print_byte_decimal(13)
|
||||
c64util.print_byte_hex(13)
|
||||
c64util.print_byte_hex(0, 13)
|
||||
c64.CHROUT(13)
|
||||
c64util.print_byte_decimal(255)
|
||||
c64util.print_byte_hex(254)
|
||||
c64util.print_byte_hex(129)
|
||||
c64util.print_byte_hex(0, 254)
|
||||
c64util.print_byte_hex(0, 129)
|
||||
c64.CHROUT(13)
|
||||
|
||||
c64.CHROUT(13)
|
||||
|
27
todo.ill
Normal file
27
todo.ill
Normal file
@ -0,0 +1,27 @@
|
||||
output prg,basic
|
||||
|
||||
import "c64lib"
|
||||
import "mathlib"
|
||||
|
||||
|
||||
~ main {
|
||||
|
||||
; zpvar myvar @todo allow for zp vars like this
|
||||
var bytevar
|
||||
var bytevar2
|
||||
var .word wordvar
|
||||
var .float fl1
|
||||
var .float fl2
|
||||
|
||||
|
||||
start
|
||||
; XY() ; @todo better syntax error, need [XY]
|
||||
[AX]()
|
||||
[AY]()
|
||||
[XY]()
|
||||
[AX]!()
|
||||
[AY]!()
|
||||
[XY]!()
|
||||
|
||||
return
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user