This commit is contained in:
Irmen de Jong 2017-12-21 14:52:30 +01:00
parent 13e1d5c62c
commit a228bcd8fc
25 changed files with 5001 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
*.py[cod]
*.egg
*.egg-info
/MANIFEST
/.idea/
.tox/
/build/
/dist/
/output/
.cache/
.eggs/
*.directory
*.prg
*.asm
*.labels.txt
.mypy_cache/

6
README.md Normal file
View File

@ -0,0 +1,6 @@
# IL65 - codename 'Sick'
Intermediate Language for the 8-bit 6502/6510 microprocessors.
Mainly targeted at the Commodore-64, but should be system independent.
Work in progress.

6
il65/__init__.py Normal file
View File

@ -0,0 +1,6 @@
"""
Intermediate Language for 6502/6510 microprocessors
Written by Irmen de Jong (irmen@razorvine.net)
License: GNU GPL 3.0, see LICENSE
"""

9
il65/__main__.py Normal file
View File

@ -0,0 +1,9 @@
"""
Intermediate Language for 6502/6510 microprocessors
Written by Irmen de Jong (irmen@razorvine.net)
License: GNU GPL 3.0, see LICENSE
"""
from . import il65
il65.main()

206
il65/astparse.py Normal file
View File

@ -0,0 +1,206 @@
"""
Intermediate Language for 6502/6510 microprocessors
This is the expression parser/evaluator.
Written by Irmen de Jong (irmen@razorvine.net)
License: GNU GPL 3.0, see LICENSE
"""
import ast
from typing import Union, Optional
from .symbols import FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE, SourceRef, SymbolTable, SymbolError, DataType, PrimitiveType
class ParseError(Exception):
def __init__(self, message: str, text: str, sourceref: SourceRef) -> None:
self.sourceref = sourceref
self.msg = message
self.text = text
def __str__(self):
return "{} {:s}".format(self.sourceref, self.msg)
class SourceLine:
def __init__(self, text: str, sourceref: SourceRef) -> None:
self.sourceref = sourceref
self.text = text.strip()
def to_error(self, message: str) -> ParseError:
return ParseError(message, self.text, self.sourceref)
def preprocess(self) -> str:
# transforms the source text into valid Python syntax by bending some things, so ast can parse it.
# $d020 -> 0xd020
# %101001 -> 0xb101001
# #something -> __ptr@something (matmult operator)
text = ""
quotes_stack = ""
characters = enumerate(self.text + " ")
for i, c in characters:
if c in ("'", '"'):
if quotes_stack and quotes_stack[-1] == c:
quotes_stack = quotes_stack[:-1]
else:
quotes_stack += c
text += c
continue
if not quotes_stack:
if c == '%' and self.text[i + 1] in "01":
text += "0b"
continue
if c == '$' and self.text[i + 1] in "0123456789abcdefABCDEF":
text += "0x"
continue
if c == '#':
if i > 0:
text += " "
text += "__ptr@"
continue
text += c
return text
def parse_expr_as_int(text: str, context: Optional[SymbolTable], sourceref: SourceRef, *,
minimum: int=0, maximum: int=0xffff) -> int:
result = parse_expr_as_primitive(text, context, sourceref, minimum=minimum, maximum=maximum)
if isinstance(result, int):
return result
src = SourceLine(text, sourceref)
raise src.to_error("int expected, not " + type(result).__name__)
def parse_expr_as_number(text: str, context: Optional[SymbolTable], sourceref: SourceRef, *,
minimum: float=FLOAT_MAX_NEGATIVE, maximum: float=FLOAT_MAX_POSITIVE) -> Union[int, float]:
result = parse_expr_as_primitive(text, context, sourceref, minimum=minimum, maximum=maximum)
if isinstance(result, (int, float)):
return result
src = SourceLine(text, sourceref)
raise src.to_error("int or float expected, not " + type(result).__name__)
def parse_expr_as_string(text: str, context: Optional[SymbolTable], sourceref: SourceRef) -> str:
result = parse_expr_as_primitive(text, context, sourceref)
if isinstance(result, str):
return result
src = SourceLine(text, sourceref)
raise src.to_error("string expected, not " + type(result).__name__)
def parse_expr_as_primitive(text: str, context: Optional[SymbolTable], sourceref: SourceRef, *,
minimum: float = FLOAT_MAX_NEGATIVE, maximum: float = FLOAT_MAX_POSITIVE) -> PrimitiveType:
src = SourceLine(text, sourceref)
text = src.preprocess()
try:
node = ast.parse(text, sourceref.file, mode="eval")
except SyntaxError as x:
raise src.to_error(str(x))
if isinstance(node, ast.Expression):
result = ExpressionTransformer(src, context).evaluate(node)
else:
raise TypeError("ast.Expression expected")
if isinstance(result, bool):
return int(result)
if isinstance(result, (int, float)):
if minimum <= result <= maximum:
return result
raise src.to_error("number too large")
if isinstance(result, str):
return result
raise src.to_error("int or float or string expected, not " + type(result).__name__)
def parse_statement(text: str, sourceref: SourceRef) -> int: # @todo in progress...
src = SourceLine(text, sourceref)
text = src.preprocess()
node = ast.parse(text, sourceref.file, mode="single")
return node
class EvaluatingTransformer(ast.NodeTransformer):
def __init__(self, src: SourceLine, context: SymbolTable) -> None:
super().__init__()
self.src = src
self.context = context
def error(self, message: str, column: int=0) -> ParseError:
if column:
ref = self.src.sourceref.copy()
ref.column = column
else:
ref = self.src.sourceref
return ParseError(message, self.src.text, ref)
def evaluate(self, node: ast.Expression) -> PrimitiveType:
node = self.visit(node)
code = compile(node, self.src.sourceref.file, mode="eval")
if self.context:
globals = None
locals = self.context.as_eval_dict()
else:
globals = {"__builtins__": {}}
locals = None
try:
result = eval(code, globals, locals)
except Exception as x:
raise self.src.to_error(str(x))
else:
if type(result) is bool:
return int(result)
return result
class ExpressionTransformer(EvaluatingTransformer):
def _dotted_name_from_attr(self, node: ast.Attribute) -> str:
if isinstance(node.value, ast.Name):
return node.value.id + '.' + node.attr
if isinstance(node.value, ast.Attribute):
return self._dotted_name_from_attr(node.value) + '.' + node.attr
raise self.error("dotted name error")
def visit_Name(self, node: ast.Name):
# convert true/false names to True/False constants
if node.id == "true":
return ast.copy_location(ast.NameConstant(True), node)
if node.id == "false":
return ast.copy_location(ast.NameConstant(False), node)
return node
def visit_UnaryOp(self, node):
if isinstance(node.operand, ast.Num):
if isinstance(node.op, ast.USub):
node = self.generic_visit(node)
return ast.copy_location(ast.Num(-node.operand.n), node)
if isinstance(node.op, ast.UAdd):
node = self.generic_visit(node)
return ast.copy_location(ast.Num(node.operand.n), node)
raise self.error("expected unary + or -")
else:
raise self.error("expected numeric operand for unary operator")
def visit_BinOp(self, node):
node = self.generic_visit(node)
if isinstance(node.op, ast.MatMult):
if isinstance(node.left, ast.Name) and node.left.id == "__ptr":
if isinstance(node.right, ast.Attribute):
symbolname = self._dotted_name_from_attr(node.right)
elif isinstance(node.right, ast.Name):
symbolname = node.right.id
else:
raise self.error("can only take address of a named variable")
try:
address = self.context.get_address(symbolname)
except SymbolError as x:
raise self.error(str(x))
else:
return ast.copy_location(ast.Num(address), node)
else:
raise self.error("invalid MatMult/Pointer node in AST")
return node
if __name__ == "__main__":
symbols = SymbolTable("<root>", None, None)
symbols.define_variable("derp", SourceRef("<source>", 1), DataType.BYTE, address=2345)
result = parse_expr_as_primitive("2+#derp", symbols, SourceRef("<source>", 1))
print("EXPRESSION RESULT:", result)

858
il65/il65.py Normal file
View File

@ -0,0 +1,858 @@
#! /usr/bin/env python3
"""
Intermediate Language for 6502/6510 microprocessors, codename 'Sick'
This is the main program and assembly code generator (from the parse tree)
Written by Irmen de Jong (irmen@razorvine.net)
License: GNU GPL 3.0, see LICENSE
"""
import os
import io
import math
import datetime
import subprocess
import contextlib
import argparse
from functools import partial
from typing import TextIO, Set, Union
from .preprocess import PreprocessingParser
from .parse import ProgramFormat, Parser, ParseResult, Optimizer
from .symbols import Zeropage, DataType, VariableDef, REGISTER_WORDS, FLOAT_MAX_NEGATIVE, FLOAT_MAX_POSITIVE
class CodeError(Exception):
pass
class CodeGenerator:
def __init__(self, parsed: ParseResult) -> None:
self.parsed = parsed
self.generated_code = io.StringIO()
self.p = partial(print, file=self.generated_code)
self.previous_stmt_was_assignment = False
self.cur_block = None # type: ParseResult.Block
def generate(self) -> None:
self.sanitycheck()
self.header()
self.initialize_variables()
self.blocks()
self.footer()
def sanitycheck(self) -> None:
# duplicate block names?
all_blocknames = [b.name for b in self.parsed.blocks if b.name]
unique_blocknames = set(all_blocknames)
if len(all_blocknames) != len(unique_blocknames):
for name in unique_blocknames:
all_blocknames.remove(name)
raise CodeError("there are duplicate block names", all_blocknames)
# ZP block contains no code?
for zpblock in [b for b in self.parsed.blocks if b.name == "ZP"]:
if zpblock.label_names:
raise CodeError("ZP block cannot contain labels")
if zpblock.statements:
raise CodeError("ZP block cannot contain code statements")
def optimize(self) -> None:
# optimize the generated assembly code
pass
def write_assembly(self, out: TextIO) -> None:
out.write(self.generated_code.getvalue())
def header(self) -> None:
self.p("; code generated by il65.py - codename 'Sick'")
self.p("; source file:", self.parsed.sourcefile)
if self.parsed.with_sys:
self.p("; output format:", self.parsed.format.value, " (with basic program SYS)")
else:
self.p("; output format:", self.parsed.format.value)
self.p("; assembler syntax is for 64tasm")
self.p(".cpu '6502'\n.enc 'none'\n")
if self.parsed.format == ProgramFormat.PRG:
if self.parsed.with_sys:
self.p("; ---- basic program with sys call ----")
self.p("* = " + self.to_hex(self.parsed.start_address))
year = datetime.datetime.now().year
self.p("\t\t.word (+), {:d}".format(year))
self.p("\t\t.null $9e, format(' %d ', _il65_sysaddr), $3a, $8f, ' il65 by idj'")
self.p("+\t\t.word 0")
self.p("_il65_sysaddr\t\t; assembly code starts here\n")
else:
self.p("; ---- program without sys call ----")
self.p("* = " + self.to_hex(self.parsed.start_address) + "\n")
if self.parsed.format == ProgramFormat.RAW:
self.p("; ---- raw assembler program ----")
self.p("* = " + self.to_hex(self.parsed.start_address) + "\n")
@staticmethod
def to_hex(number: int) -> str:
# 0..255 -> "$00".."$ff"
# 256..65536 -> "$0100".."$ffff"
if 0 <= number < 0x100:
return "${:02x}".format(number)
if number < 0x10000:
return "${:04x}".format(number)
raise OverflowError(number)
@staticmethod
def to_mflpt5(number: float) -> bytearray:
# algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
number = float(number)
if number < FLOAT_MAX_NEGATIVE or number > FLOAT_MAX_POSITIVE:
raise OverflowError("floating point number out of 5-byte mflpt range", number)
if number == 0.0:
return bytearray([0, 0, 0, 0, 0])
if number < 0.0:
sign = 0x80000000
number = -number
else:
sign = 0x00000000
mant, exp = math.frexp(number)
exp += 128
if exp < 1:
# underflow, use zero instead
return bytearray([0, 0, 0, 0, 0])
if exp > 255:
raise OverflowError("floating point number out of 5-byte mflpt range", number)
mant = sign | int(mant * 0x100000000) & 0x7fffffff
return bytearray([exp]) + int.to_bytes(mant, 4, "big")
@staticmethod
def mflpt5_to_float(mflpt: bytearray) -> float:
if mflpt == bytearray([0, 0, 0, 0, 0]):
return 0.0
exp = mflpt[0] - 128
sign = mflpt[1] & 0x80
number = 0x80000000 | int.from_bytes(mflpt[1:], "big")
number = float(number) * 2**exp / 0x100000000
return -number if sign else number
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 -")
# 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"]
if zpblocks:
assert len(zpblocks) == 1
zpblock = zpblocks[0]
vars_to_init = [v for v in zpblock.symbols.iter_variables()
if v.allocate and v.type in (DataType.BYTE, DataType.WORD, DataType.FLOAT)]
# @todo optimize sort order (sort on value first, then type, then blockname, then address/name)
# (str(self.value) or "", self.blockname, self.name or "", self.address or 0, self.seq_nr)
prev_value = 0 # type: Union[str, int, float]
if vars_to_init:
self.p("; init zp vars")
self.p("\t\tlda #0\n\t\tldx #0")
for variable in vars_to_init:
vname = zpblock.label + "." + variable.name
vvalue = variable.value
if variable.type == DataType.BYTE:
if vvalue != prev_value:
self.p("\t\tlda #${:02x}".format(vvalue))
prev_value = vvalue
self.p("\t\tsta {:s}".format(vname))
elif variable.type == DataType.WORD:
if vvalue != prev_value:
self.p("\t\tlda #<${:04x}".format(vvalue))
self.p("\t\tldx #>${:04x}".format(vvalue))
prev_value = vvalue
self.p("\t\tsta {:s}".format(vname))
self.p("\t\tstx {:s}+1".format(vname))
elif variable.type == DataType.FLOAT:
raise TypeError("floats cannot be stored in the zp")
self.p("; end init zp vars")
else:
self.p("\t\t; there are no zp vars to initialize")
else:
self.p("\t\t; there is no zp block to initialize")
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")
else:
self.p("\t\tjmp {:s}.start\t\t; call user code".format(main_block_label))
def blocks(self) -> None:
# if there's a Zeropage block, it always goes first
for zpblock in [b for b in self.parsed.blocks if b.name == "ZP"]:
assert not zpblock.statements
self.cur_block = zpblock
self.p("\n; ---- zero page block: '{:s}' ----\t\t; src l. {:d}\n".format(zpblock.sourceref.file, zpblock.sourceref.line))
self.p("{:s}\t.proc\n".format(zpblock.label))
self.generate_block_vars(zpblock)
self.p("\t.pend\n")
# make sure the main.start routine clears the decimal and carry flags as first steps
for block in self.parsed.blocks:
if block.name == "main":
statements = list(block.statements)
for index, stmt in enumerate(statements):
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"
]
statements.insert(index+1, ParseResult.InlineAsm(0, asmlines))
break
block.statements = statements
# generate
for block in sorted(self.parsed.blocks, key=lambda b: b.address):
if block.name == "ZP":
continue # zeropage block is already processed
self.cur_block = block
self.p("\n; ---- next block: '{:s}' ----\t\t; src l. {:d}\n".format(block.sourceref.file, block.sourceref.line))
if block.address:
self.p(".cerror * > ${0:04x}, 'block address overlaps by ', *-${0:04x},' bytes'".format(block.address))
self.p("* = ${:04x}".format(block.address))
self.p("{:s}\t.proc\n".format(block.label))
self.generate_block_vars(block)
subroutines = list(block.symbols.iter_subroutines())
if subroutines:
self.p("\n; external subroutines")
for subdef in subroutines:
self.p("\t\t{:s} = {:s}".format(subdef.name, self.to_hex(subdef.address)))
self.p("; end external subroutines")
for stmt in block.statements:
self.generate_statement(stmt)
self.p("\t.pend\n")
def generate_block_vars(self, block: ParseResult.Block) -> None:
mem_vars = [vi for vi in block.symbols.iter_variables() if not vi.allocate and not vi.register]
if mem_vars:
self.p("; memory mapped variables")
for vardef in mem_vars:
# create a definition for variables at a specific place in memory (memory-mapped)
if vardef.type in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
self.p("\t\t{:s} = {:s}\t; {:s}".format(vardef.name, self.to_hex(vardef.address), vardef.type.name.lower()))
elif vardef.type == DataType.BYTEARRAY:
self.p("\t\t{:s} = {:s}\t; array of {:d} bytes".format(vardef.name, self.to_hex(vardef.address), vardef.length))
elif vardef.type == DataType.WORDARRAY:
self.p("\t\t{:s} = {:s}\t; array of {:d} words".format(vardef.name, self.to_hex(vardef.address), vardef.length))
elif vardef.type == DataType.MATRIX:
self.p("\t\t{:s} = {:s}\t; matrix {:d} by {:d} = {:d} bytes"
.format(vardef.name, self.to_hex(vardef.address), vardef.matrixsize[0], vardef.matrixsize[1], vardef.length))
else:
raise ValueError("invalid var type")
non_mem_vars = [vi for vi in block.symbols.iter_variables() if vi.allocate]
if non_mem_vars:
self.p("; normal variables")
for vardef in non_mem_vars:
# create a definition for a variable that takes up space and will be initialized at startup
if vardef.type in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
if vardef.address:
assert block.name == "ZP", "only ZP-variables can be put on an address"
self.p("\t\t{:s} = {:s}".format(vardef.name, self.to_hex(vardef.address)))
else:
if vardef.type == DataType.BYTE:
self.p("{:s}\t\t.byte {:s}".format(vardef.name, self.to_hex(int(vardef.value))))
elif vardef.type == DataType.WORD:
self.p("{:s}\t\t.word {:s}".format(vardef.name, self.to_hex(int(vardef.value))))
elif vardef.type == DataType.FLOAT:
self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}"
.format(vardef.name, *self.to_mflpt5(float(vardef.value))))
else:
raise TypeError("weird datatype")
elif vardef.type in (DataType.BYTEARRAY, DataType.WORDARRAY):
if vardef.address:
raise CodeError("array or wordarray vars must not have address; will be allocated by assembler")
if vardef.type == DataType.BYTEARRAY:
self.p("{:s}\t\t.fill {:d}, ${:02x}".format(vardef.name, vardef.length, vardef.value or 0))
elif vardef.type == DataType.WORDARRAY:
f_hi, f_lo = divmod(vardef.value or 0, 256) # type: ignore
self.p("{:s}\t\t.fill {:d}, [${:02x}, ${:02x}]\t; {:d} words of ${:04x}"
.format(vardef.name, vardef.length * 2, f_lo, f_hi, vardef.length, vardef.value or 0))
else:
raise TypeError("invalid datatype", vardef.type)
elif vardef.type == DataType.MATRIX:
if vardef.address:
raise CodeError("matrix vars must not have address; will be allocated by assembler")
self.p("{:s}\t\t.fill {:d}, ${:02x}\t\t; matrix {:d}*{:d} bytes"
.format(vardef.name,
vardef.matrixsize[0] * vardef.matrixsize[1],
vardef.value or 0,
vardef.matrixsize[0], vardef.matrixsize[1]))
elif vardef.type == DataType.STRING:
# 0-terminated string
self.p("{:s}\n\t\t.null {:s}".format(vardef.name, self.output_string(str(vardef.value))))
elif vardef.type == DataType.STRING_P:
# pascal string
self.p("{:s}\n\t\t.ptext {:s}".format(vardef.name, self.output_string(str(vardef.value))))
elif vardef.type == DataType.STRING_S:
# 0-terminated string in screencode encoding
self.p(".enc 'screen'")
self.p("{:s}\n\t\t.null {:s}".format(vardef.name, self.output_string(str(vardef.value), True)))
self.p(".enc 'none'")
elif vardef.type == DataType.STRING_PS:
# 0-terminated pascal string in screencode encoding
self.p(".enc 'screen'")
self.p("{:s}\n\t\t.ptext {:s}".format(vardef.name, self.output_string(str(vardef.value), True)))
self.p(".enc 'none'")
else:
raise CodeError("unknown variable type " + str(vardef.type))
def generate_statement(self, stmt: ParseResult._Stmt) -> None:
if isinstance(stmt, ParseResult.ReturnStmt):
if stmt.a:
if isinstance(stmt.a, ParseResult.IntegerValue):
self.p("\t\tlda #{:d}".format(stmt.a.value))
else:
raise CodeError("can only return immediate values for now") # XXX
if stmt.x:
if isinstance(stmt.x, ParseResult.IntegerValue):
self.p("\t\tldx #{:d}".format(stmt.x.value))
else:
raise CodeError("can only return immediate values for now") # XXX
if stmt.y:
if isinstance(stmt.y, ParseResult.IntegerValue):
self.p("\t\tldy #{:d}".format(stmt.y.value))
else:
raise CodeError("can only return immediate values for now") # XXX
self.p("\t\trts")
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):
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:
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\tde{:s}".format(stmt.what.register.lower()))
elif isinstance(stmt.what, ParseResult.MemMappedValue):
r_str = stmt.what.name or self.to_hex(stmt.what.address)
if stmt.what.datatype == DataType.BYTE:
if stmt.howmuch == 1:
self.p("\t\tinc " + r_str)
else:
self.p("\t\tdec " + r_str)
elif stmt.what.datatype == DataType.WORD:
# @todo verify this asm code
if stmt.howmuch == 1:
self.p("\t\tinc " + r_str)
self.p("\t\tbne +")
self.p("\t\tinc {:s}+1".format(r_str))
self.p("+")
else:
self.p("\t\tdec " + r_str)
self.p("\t\tbne +")
self.p("\t\tdec {:s}+1".format(r_str))
self.p("+")
else:
raise CodeError("cannot in/decrement memory of type " + str(stmt.what.datatype))
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
elif isinstance(stmt, ParseResult.CallStmt):
is_indirect = False
if stmt.call_label:
call_target = stmt.call_label
if stmt.call_module:
call_target = stmt.call_module + "." + stmt.call_label
elif stmt.address is not None:
call_target = self.to_hex(stmt.address)
else:
assert stmt.indirect_pointer is not None
if isinstance(stmt.indirect_pointer, int):
call_target = self.to_hex(stmt.indirect_pointer)
else:
call_target = stmt.indirect_pointer
is_indirect = True
if stmt.subroutine:
assert not is_indirect
if stmt.subroutine.clobbered_registers:
if stmt.preserve_regs: # @todo make this work with the separate assignment statements for the parameters.. :(
clobbered = stmt.subroutine.clobbered_registers
else:
clobbered = set()
with self.preserving_registers(clobbered):
self.p("\t\tjsr " + call_target)
if stmt.is_goto:
self.p("\t\trts")
return
if stmt.is_goto:
if is_indirect:
if call_target in REGISTER_WORDS:
self.p("\t\tst{:s} {:s}".format(call_target[0].lower(), self.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tst{:s} {:s}".format(call_target[1].lower(), self.to_hex(Zeropage.SCRATCH_B2)))
self.p("\t\tjmp ({:s})".format(self.to_hex(Zeropage.SCRATCH_B1)))
else:
self.p("\t\tjmp ({:s})".format(call_target))
else:
self.p("\t\tjmp " + call_target)
else:
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
with self.preserving_registers(preserve_regs):
if is_indirect:
if call_target in REGISTER_WORDS:
if stmt.preserve_regs:
# cannot use zp scratch
self.p("\t\tst{:s} ++".format(call_target[0].lower()))
self.p("\t\tst{:s} +++".format(call_target[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("+")
else:
self.p("\t\tst{:s} {:s}".format(call_target[0].lower(), self.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tst{:s} {:s}".format(call_target[1].lower(), self.to_hex(Zeropage.SCRATCH_B2)))
self.p("\t\tjsr +")
self.p("\t\tjmp ++")
self.p("+\t\tjmp ({:s})".format(self.to_hex(Zeropage.SCRATCH_B1)))
self.p("+")
else:
self.p("\t\tjsr +")
self.p("\t\tjmp ++")
self.p("+\t\tjmp ({:s})".format(call_target))
self.p("+")
else:
self.p("\t\tjsr " + call_target)
elif isinstance(stmt, ParseResult.InlineAsm):
self.p("\t\t; inline asm, src l. {:d}".format(stmt.lineno))
for line in stmt.asmlines:
self.p(line)
self.p("\t\t; end inline asm, src l. {:d}".format(stmt.lineno))
else:
raise CodeError("unknown statement " + repr(stmt))
self.previous_stmt_was_assignment = isinstance(stmt, ParseResult.AssignmentStmt)
def generate_assignment(self, stmt: ParseResult.AssignmentStmt) -> None:
self.p("\t\t\t\t\t; src l. {:d}".format(stmt.lineno))
if isinstance(stmt.right, ParseResult.IntegerValue):
for lv in stmt.leftvalues:
if isinstance(lv, ParseResult.RegisterValue):
self.generate_assign_integer_to_reg(lv.register, stmt.right)
elif isinstance(lv, ParseResult.MemMappedValue):
self.generate_assign_integer_to_mem(lv, stmt.right)
else:
raise CodeError("invalid assignment target (1)", str(stmt))
elif isinstance(stmt.right, ParseResult.RegisterValue):
for lv in stmt.leftvalues:
if isinstance(lv, ParseResult.RegisterValue):
self.generate_assign_reg_to_reg(lv, stmt.right.register)
elif isinstance(lv, ParseResult.MemMappedValue):
self.generate_assign_reg_to_memory(lv, stmt.right.register)
else:
raise CodeError("invalid assignment target (2)", str(stmt))
elif isinstance(stmt.right, ParseResult.StringValue):
r_str = self.output_string(stmt.right.value, True)
for lv in stmt.leftvalues:
if isinstance(lv, ParseResult.RegisterValue):
if len(stmt.right.value) == 1:
self.generate_assign_char_to_reg(lv, r_str)
else:
self.generate_assign_string_to_reg(lv, stmt.right)
elif isinstance(lv, ParseResult.MemMappedValue):
if len(stmt.right.value) == 1:
self.generate_assign_char_to_memory(lv, r_str)
else:
self.generate_assign_string_to_memory(lv, stmt.right)
else:
raise CodeError("invalid assignment target (2)", str(stmt))
elif isinstance(stmt.right, ParseResult.MemMappedValue):
for lv in stmt.leftvalues:
if isinstance(lv, ParseResult.RegisterValue):
self.generate_assign_mem_to_reg(lv.register, stmt.right)
elif isinstance(lv, ParseResult.MemMappedValue):
self.generate_assign_mem_to_mem(lv, stmt.right)
else:
raise CodeError("invalid assignment target (4)", str(stmt))
elif isinstance(stmt.right, ParseResult.FloatValue):
mflpt = self.to_mflpt5(stmt.right.value)
for lv in stmt.leftvalues:
if isinstance(lv, ParseResult.MemMappedValue) and lv.datatype == DataType.FLOAT:
self.generate_store_immediate_float(lv, stmt.right.value, mflpt)
else:
raise CodeError("cannot assign float to ", str(lv))
else:
raise CodeError("invalid assignment value type", str(stmt))
def generate_store_immediate_float(self, mmv: ParseResult.MemMappedValue, floatvalue: float,
mflpt: bytearray, emit_pha: bool=True) -> None:
target = mmv.name or self.to_hex(mmv.address)
if emit_pha:
self.p("\t\tpha\t\t\t; {:s} = {}".format(target, floatvalue))
else:
self.p("\t\t\t\t\t; {:s} = {}".format(target, floatvalue))
for num in range(5):
self.p("\t\tlda #${:02x}".format(mflpt[num]))
self.p("\t\tsta {:s}+{:d}".format(target, num))
if emit_pha:
self.p("\t\tpla")
def generate_assign_reg_to_memory(self, lv: ParseResult.MemMappedValue, r_register: str) -> None:
# Memory = Register
lv_string = lv.name or self.to_hex(lv.address)
if lv.datatype == DataType.BYTE:
if len(r_register) > 1:
raise CodeError("cannot assign register pair to single byte memory")
self.p("\t\tst{:s} {}".format(r_register.lower(), lv_string))
elif lv.datatype == DataType.WORD:
if len(r_register) == 1:
self.p("\t\tst{:s} {}".format(r_register.lower(), lv_string)) # lsb
with self.preserving_registers({'A'}):
self.p("\t\tlda #0")
self.p("\t\tsta {:s}+1".format(lv_string)) # msb
else:
self.p("\t\tst{:s} {}".format(r_register[0].lower(), lv_string))
self.p("\t\tst{:s} {}+1".format(r_register[1].lower(), lv_string))
elif lv.datatype == DataType.FLOAT:
raise CodeError("assigning register to float not yet supported") # @todo support float=reg
else:
raise CodeError("invalid lvalue type", lv.datatype)
def generate_assign_reg_to_reg(self, lv: ParseResult.RegisterValue, r_register: str) -> None:
if lv.register != r_register:
if lv.register == 'A': # x/y -> a
self.p("\t\tt{:s}a".format(r_register.lower()))
elif lv.register == 'Y':
if r_register == 'A':
# a -> y
self.p("\t\ttay")
else:
# x -> y, 6502 doesn't have txy
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == 'X':
if r_register == 'A':
# a -> x
self.p("\t\ttax")
else:
# y -> x, 6502 doesn't have tyx
self.p("\t\tsty ${0:02x}\n\t\tldx ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register in REGISTER_WORDS:
if len(r_register) == 1:
# assign one register to a pair, so the hi byte is zero.
if lv.register == "AX" and r_register == "A":
self.p("\t\tldx #0")
elif lv.register == "AX" and r_register == "X":
self.p("\t\ttxa\n\t\tldx #0")
elif lv.register == "AX" and r_register == "Y":
self.p("\t\ttya\n\t\tldx #0")
elif lv.register == "AY" and r_register == "A":
self.p("\t\tldy #0")
elif lv.register == "AY" and r_register == "X":
self.p("\t\ttxa\n\t\tldy #0")
elif lv.register == "AY" and r_register == "Y":
self.p("\t\ttya\n\t\tldy #0")
elif lv.register == "XY" and r_register == "A":
self.p("\t\ttax\n\t\tldy #0")
elif lv.register == "XY" and r_register == "X":
self.p("\t\tldy #0")
elif lv.register == "XY" and r_register == "Y":
self.p("\t\ttyx\n\t\tldy #0")
else:
raise CodeError("invalid register combination", lv.register, r_register)
elif lv.register == "AX" and r_register == "AY":
# y -> x, 6502 doesn't have tyx
self.p("\t\tsty ${0:02x}\n\t\tldx ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == "AX" and r_register == "XY":
self.p("\t\ttxa")
# y -> x, 6502 doesn't have tyx
self.p("\t\tsty ${0:02x}\n\t\tldx ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == "AY" and r_register == "AX":
# x -> y, 6502 doesn't have txy
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == "AY" and r_register == "XY":
self.p("\t\ttxa")
elif lv.register == "XY" and r_register == "AX":
self.p("\t\ttax")
# x -> y, 6502 doesn't have txy
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
elif lv.register == "XY" and r_register == "AY":
self.p("\t\ttax")
else:
raise CodeError("invalid register combination", lv.register, r_register)
else:
raise CodeError("invalid register " + lv.register)
@contextlib.contextmanager
def preserving_registers(self, registers: Set[str]):
# this clobbers a ZP scratch register and is therefore safe to use in interrupts
# see http://6502.org/tutorials/register_preservation.html
if registers == {'A'}:
self.p("\t\tpha")
yield
self.p("\t\tpla")
elif registers:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
if 'A' in registers:
self.p("\t\tpha")
if 'X' in registers:
self.p("\t\ttxa\n\t\tpha")
if 'Y' in registers:
self.p("\t\ttya\n\t\tpha")
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
yield
if 'Y' in registers:
self.p("\t\tpla\n\t\ttay")
if 'X' in registers:
self.p("\t\tpla\n\t\ttax")
if 'A' in registers:
self.p("\t\tpla")
else:
yield
def generate_assign_integer_to_mem(self, lv: ParseResult.MemMappedValue, rvalue: ParseResult.IntegerValue) -> None:
if lv.name:
symblock, sym = self.cur_block.lookup(lv.name)
if not isinstance(sym, VariableDef):
raise TypeError("invalid lvalue type " + str(sym))
assign_target = symblock.label + "." + sym.name if symblock is not self.cur_block else lv.name
lvdatatype = sym.type
else:
assign_target = self.to_hex(lv.address)
lvdatatype = lv.datatype
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.value)
if lvdatatype == DataType.BYTE:
if rvalue.value is not None and not lv.assignable_from(rvalue) or rvalue.datatype != DataType.BYTE:
raise OverflowError("value doesn't fit in a byte")
with self.preserving_registers({'A'}):
self.p("\t\tlda #" + r_str)
self.p("\t\tsta " + assign_target)
elif lvdatatype == DataType.WORD:
if rvalue.value is not None and not lv.assignable_from(rvalue):
raise OverflowError("value doesn't fit in a word")
with self.preserving_registers({'A'}):
self.p("\t\tlda #<" + r_str)
self.p("\t\tsta " + assign_target)
self.p("\t\tlda #>" + r_str)
self.p("\t\tsta {}+1".format(assign_target))
elif lvdatatype == DataType.FLOAT:
if rvalue.value is not None and not DataType.FLOAT.assignable_from_value(rvalue.value):
raise ValueError("value cannot be assigned to a float")
floatvalue = float(rvalue.value)
self.generate_store_immediate_float(lv, floatvalue, self.to_mflpt5(floatvalue), False)
else:
raise TypeError("invalid lvalue type " + str(lvdatatype))
def generate_assign_mem_to_reg(self, l_register: str, rvalue: ParseResult.MemMappedValue) -> None:
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.address)
if len(l_register) == 1:
if rvalue.datatype != DataType.BYTE:
raise CodeError("can only assign a byte to a register")
self.p("\t\tld{:s} {:s}".format(l_register.lower(), r_str))
else:
if rvalue.datatype != DataType.WORD:
raise CodeError("can only assign a word to a register pair")
raise NotImplementedError # @todo other mmapped types
def generate_assign_mem_to_mem(self, lv: ParseResult.MemMappedValue, rvalue: ParseResult.MemMappedValue) -> None:
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.address)
if lv.datatype == DataType.BYTE:
if rvalue.datatype != DataType.BYTE:
raise CodeError("can only assign a byte to a byte")
with self.preserving_registers({'A'}):
self.p("\t\tlda " + r_str)
self.p("\t\tsta " + (lv.name or self.to_hex(lv.address)))
elif lv.datatype == DataType.WORD:
if rvalue.datatype == DataType.BYTE:
raise NotImplementedError # XXX
with self.preserving_registers({'A'}):
l_str = lv.name or self.to_hex(lv.address)
self.p("\t\tlda #0")
self.p("\t\tsta " + l_str)
self.p("\t\tlda " + r_str)
self.p("\t\tsta {:s}+1".format(l_str))
elif rvalue.datatype == DataType.WORD:
with self.preserving_registers({'A'}):
l_str = lv.name or self.to_hex(lv.address)
self.p("\t\tlda {:s}".format(r_str))
self.p("\t\tsta {:s}".format(l_str))
self.p("\t\tlda {:s}+1".format(r_str))
self.p("\t\tsta {:s}+1".format(l_str))
else:
# @todo other mmapped types
raise CodeError("can only assign a byte or word to a word")
else:
raise CodeError("can only assign to a memory mapped byte or word value for now") # @todo
def generate_assign_char_to_memory(self, lv: ParseResult.MemMappedValue, char_str: str) -> None:
# Memory = Character
with self.preserving_registers({'A'}):
self.p("\t\tlda #" + char_str)
if not lv.name:
self.p("\t\tsta " + self.to_hex(lv.address))
return
# assign char value to a memory location by symbol name
symblock, sym = self.cur_block.lookup(lv.name)
if isinstance(sym, VariableDef):
assign_target = lv.name
if symblock is not self.cur_block:
assign_target = symblock.label + "." + sym.name
if sym.type == DataType.BYTE:
self.p("\t\tsta " + assign_target)
elif sym.type == DataType.WORD:
self.p("\t\tsta " + assign_target)
self.p("\t\tlda #0")
self.p("\t\tsta {}+1".format(assign_target))
else:
raise TypeError("invalid lvalue type " + str(sym))
else:
raise TypeError("invalid lvalue type " + str(sym))
def generate_assign_integer_to_reg(self, l_register: str, rvalue: ParseResult.IntegerValue) -> None:
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.value)
if l_register in ('A', 'X', 'Y'):
self.p("\t\tld{:s} #{:s}".format(l_register.lower(), r_str))
elif l_register in REGISTER_WORDS:
self.p("\t\tld{:s} #<{:s}".format(l_register[0].lower(), r_str))
self.p("\t\tld{:s} #>{:s}".format(l_register[1].lower(), r_str))
elif l_register == "SC":
# set/clear S carry bit
if rvalue.value:
self.p("\t\tsec")
else:
self.p("\t\tclc")
else:
raise CodeError("invalid register in immediate integer assignment", l_register, rvalue.value)
def generate_assign_char_to_reg(self, lv: ParseResult.RegisterValue, char_str: str) -> None:
# Register = Char (string of length 1)
if lv.register not in ('A', 'X', 'Y'):
raise CodeError("invalid register for char assignment", lv.register)
self.p("\t\tld{:s} #{:s}".format(lv.register.lower(), char_str))
def generate_assign_string_to_reg(self, lv: ParseResult.RegisterValue, rvalue: ParseResult.StringValue) -> None:
if lv.register not in ("AX", "AY", "XY"):
raise CodeError("need register pair AX, AY or XY for string address assignment", lv.register)
if rvalue.name:
self.p("\t\tld{:s} #<{:s}".format(lv.register[0].lower(), rvalue.name))
self.p("\t\tld{:s} #>{:s}".format(lv.register[1].lower(), rvalue.name))
else:
raise CodeError("cannot assign immediate string, it should be a string variable")
def generate_assign_string_to_memory(self, lv: ParseResult.MemMappedValue, rvalue: ParseResult.StringValue) -> None:
if lv.datatype != DataType.WORD:
raise CodeError("need word memory type for string address assignment")
if rvalue.name:
assign_target = lv.name if lv.name else self.to_hex(lv.address)
self.p("\t\tlda #<{:s}".format(rvalue.name))
self.p("\t\tsta " + assign_target)
self.p("\t\tlda #>{:s}".format(rvalue.name))
self.p("\t\tsta {}+1".format(assign_target))
else:
raise CodeError("cannot assign immediate string, it should be a string variable")
def footer(self) -> None:
self.p("\n\n.end")
def output_string(self, value: str, screencodes: bool = False) -> str:
if len(value) == 1 and screencodes:
if value[0].isprintable() and ord(value[0]) < 128:
return "'{:s}'".format(value[0])
else:
return str(ord(value[0]))
result = '"'
for char in value:
if char in "{}":
result += '", {:d}, "'.format(ord(char))
elif char.isprintable() and ord(char) < 128:
result += char
else:
if screencodes:
result += '", {:d}, "'.format(ord(char))
else:
if char == "\f":
result += "{clear}"
elif char == "\b":
result += "{delete}"
elif char == "\n":
result += "{lf}"
elif char == "\r":
result += "{cr}"
elif char == "\t":
result += "{tab}"
else:
result += '", {:d}, "'.format(ord(char))
return result + '"'
class Assembler64Tass:
def __init__(self, format: ProgramFormat) -> None:
self.format = format
def assemble(self, inputfilename: str, outputfilename: str) -> None:
args = ["64tass", "--ascii", "--case-sensitive", "-Wall", "-Wno-strict-bool", "--dump-labels",
"--labels", outputfilename+".labels.txt", "--output", outputfilename, inputfilename]
if self.format == ProgramFormat.PRG:
args.append("--cbm-prg")
elif self.format == ProgramFormat.RAW:
args.append("--nostart")
else:
raise ValueError("don't know how to create format "+str(self.format))
try:
if self.format == ProgramFormat.PRG:
print("\ncreating C-64 .prg")
elif self.format == ProgramFormat.RAW:
print("\ncreating raw binary")
subprocess.check_call(args)
except subprocess.CalledProcessError as x:
print("assembler failed with returncode", x.returncode)
def main() -> None:
description = "Compiler for IL65 language, code name 'Sick'"
ap = argparse.ArgumentParser(description=description)
ap.add_argument("-o", "--output", help="output directory")
ap.add_argument("sourcefile", help="the source .ill/.il65 file to compile")
args = ap.parse_args()
assembly_filename = os.path.splitext(args.sourcefile)[0] + ".asm"
program_filename = os.path.splitext(args.sourcefile)[0] + ".prg"
if args.output:
os.makedirs(args.output, mode=0o700, exist_ok=True)
assembly_filename = os.path.join(args.output, os.path.split(assembly_filename)[1])
program_filename = os.path.join(args.output, os.path.split(program_filename)[1])
print("\n" + description)
pp = PreprocessingParser(args.sourcefile)
sourcelines, symbols = pp.preprocess()
symbols.print_table(True)
p = Parser(args.sourcefile, args.output, sourcelines, ppsymbols=symbols)
parsed = p.parse()
if parsed:
opt = Optimizer(parsed)
parsed = opt.optimize()
cg = CodeGenerator(parsed)
cg.generate()
cg.optimize()
with open(assembly_filename, "wt") as out:
cg.write_assembly(out)
assembler = Assembler64Tass(parsed.format)
assembler.assemble(assembly_filename, program_filename)
print("Output file: ", program_filename)
print()

1343
il65/parse.py Normal file

File diff suppressed because it is too large Load Diff

61
il65/preprocess.py Normal file
View File

@ -0,0 +1,61 @@
"""
Intermediate Language for 6502/6510 microprocessors
This is the preprocessing parser of the IL65 code, that only generates a symbol table.
Written by Irmen de Jong (irmen@razorvine.net)
License: GNU GPL 3.0, see LICENSE
"""
from typing import List, Tuple
from .parse import Parser, ParseResult, SymbolTable, SymbolDefinition
class PreprocessingParser(Parser):
def __init__(self, filename: str) -> None:
super().__init__(filename, "", parsing_import=True)
def preprocess(self) -> Tuple[List[Tuple[int, str]], SymbolTable]:
def cleanup_table(symbols: SymbolTable):
symbols.owning_block = None # not needed here
for name, symbol in list(symbols.symbols.items()):
if isinstance(symbol, SymbolTable):
cleanup_table(symbol)
elif not isinstance(symbol, SymbolDefinition):
del symbols.symbols[name]
self.parse()
cleanup_table(self.root_scope)
return self.lines, self.root_scope
def load_source(self, filename: str) -> List[Tuple[int, str]]:
lines = super().load_source(filename)
# can do some additional source-level preprocessing here
return lines
def parse_file(self) -> ParseResult:
print("\npreprocessing", self.sourceref.file)
self._parse_1()
return self.result
def parse_asminclude(self, line: str) -> ParseResult.InlineAsm:
return ParseResult.InlineAsm(self.sourceref.line, [])
def parse_statement(self, line: str) -> ParseResult._Stmt:
return None # type: ignore
def parse_var_def(self, line: str) -> None:
super().parse_var_def(line)
def parse_const_def(self, line: str) -> None:
super().parse_const_def(line)
def parse_memory_def(self, line: str, is_zeropage: bool=False) -> None:
super().parse_memory_def(line, is_zeropage)
def parse_label(self, line: str) -> None:
super().parse_label(line)
def parse_subx_def(self, line: str) -> None:
super().parse_subx_def(line)
def create_import_parser(self, filename: str, outputdir: str) -> 'Parser':
return PreprocessingParser(filename)

638
il65/symbols.py Normal file
View File

@ -0,0 +1,638 @@
"""
Intermediate Language for 6502/6510 microprocessors
Here are the symbol (name) operations such as lookups, datatype definitions.
Written by Irmen de Jong (irmen@razorvine.net)
License: GNU GPL 3.0, see LICENSE
"""
import inspect
import math
import enum
import builtins
from functools import total_ordering
from typing import Optional, Set, Union, Tuple, Dict, Iterable, Sequence, Any, List
PrimitiveType = Union[int, float, str]
REGISTER_SYMBOLS = {"A", "X", "Y", "AX", "AY", "XY", "SC"}
REGISTER_SYMBOLS_RETURNVALUES = REGISTER_SYMBOLS | {"SZ"}
REGISTER_BYTES = {"A", "X", "Y", "SC"}
REGISTER_WORDS = {"AX", "AY", "XY"}
# 5-byte cbm MFLPT format limitations:
FLOAT_MAX_POSITIVE = 1.7014118345e+38
FLOAT_MAX_NEGATIVE = -1.7014118345e+38
RESERVED_NAMES = {'true', 'false', 'var', 'memory', 'const', 'asm'}
RESERVED_NAMES |= REGISTER_SYMBOLS
MATH_SYMBOLS = {name for name in dir(math) if name[0].islower()}
BUILTIN_SYMBOLS = {name for name in dir(builtins) if name[0].islower()}
@total_ordering
class DataType(enum.Enum):
"""The possible data types of values"""
BYTE = 1
WORD = 2
FLOAT = 3
BYTEARRAY = 4
WORDARRAY = 5
MATRIX = 6
STRING = 7
STRING_P = 8
STRING_S = 9
STRING_PS = 10
def assignable_from_value(self, value: PrimitiveType) -> bool:
if isinstance(value, (int, float)):
if self == DataType.BYTE:
return 0 <= value < 0x100
if self == DataType.WORD:
return 0 <= value < 0x10000
if self == DataType.FLOAT:
return type(value) in (float, int)
return False
def __lt__(self, other):
if self.__class__ == other.__class__:
return self.value < other.value
return NotImplemented
STRING_DATATYPES = {DataType.STRING, DataType.STRING_P, DataType.STRING_S, DataType.STRING_PS}
class SymbolError(Exception):
pass
_identifier_seq_nr = 0
class SourceRef:
__slots__ = ("file", "line", "column")
def __init__(self, file: str, line: int, column: int=0) -> None:
self.file = file
self.line = line
self.column = column
def __str__(self) -> str:
if self.column:
return "{:s}:{:d}:{:d}".format(self.file, self.line, self.column)
if self.line:
return "{:s}:{:d}".format(self.file, self.line)
return self.file
def copy(self) -> 'SourceRef':
return SourceRef(self.file, self.line, self.column)
class SymbolDefinition:
def __init__(self, blockname: str, name: str, sourceref: SourceRef, allocate: bool) -> None:
self.blockname = blockname
self.name = name
self.sourceref = sourceref
self.allocate = allocate # set to false if the variable is memory mapped (or a constant) instead of allocated
global _identifier_seq_nr
self.seq_nr = _identifier_seq_nr
_identifier_seq_nr += 1
def __lt__(self, other: 'SymbolDefinition') -> bool:
if not isinstance(other, SymbolDefinition):
return NotImplemented
return (self.blockname, self.name, self.seq_nr) < (other.blockname, other.name, self.seq_nr)
def __str__(self):
return "<{:s} {:s}.{:s}>".format(self.__class__.__name__, self.blockname, self.name)
class LabelDef(SymbolDefinition):
pass
class VariableDef(SymbolDefinition):
# if address is None, it's a dynamically allocated variable.
# if address is not None, it's a memory mapped variable (=memory address referenced by a name).
def __init__(self, blockname: str, name: str, sourceref: SourceRef,
datatype: DataType, allocate: bool, *,
value: PrimitiveType, length: int, address: Optional[int]=None,
register: str=None, matrixsize: Tuple[int, int]=None) -> None:
super().__init__(blockname, name, sourceref, allocate)
self.type = datatype
self.address = address
self.length = length
self.value = value
self.register = register
self.matrixsize = matrixsize
@property
def is_memmap(self):
return self.address is not None
def __repr__(self):
return "<Variable {:s}.{:s}, {:s}, addr {:s}, len {:s}, value {:s}>"\
.format(self.blockname, self.name, self.type, str(self.address), str(self.length), str(self.value))
def __lt__(self, other: 'SymbolDefinition') -> bool:
if not isinstance(other, VariableDef):
return NotImplemented
v1 = (self.blockname, self.name or "", self.address or 0, self.seq_nr)
v2 = (other.blockname, other.name or "", other.address or 0, self.seq_nr)
return v1 < v2
class ConstantDef(SymbolDefinition):
def __init__(self, blockname: str, name: str, sourceref: SourceRef, datatype: DataType, *,
value: PrimitiveType, length: int) -> None:
super().__init__(blockname, name, sourceref, False)
self.type = datatype
self.length = length
self.value = value
def __repr__(self):
return "<Constant {:s}.{:s}, {:s}, len {:s}, value {:s}>"\
.format(self.blockname, self.name, self.type, str(self.length), str(self.value))
def __lt__(self, other: 'SymbolDefinition') -> bool:
if not isinstance(other, ConstantDef):
return NotImplemented
v1 = (str(self.value) or "", self.blockname, self.name or "", self.seq_nr)
v2 = (str(other.value) or "", other.blockname, other.name or "", self.seq_nr)
return v1 < v2
class SubroutineDef(SymbolDefinition):
def __init__(self, blockname: str, name: str, sourceref: SourceRef,
parameters: Sequence[Tuple[str, str]], returnvalues: Set[str], address: Optional[int]=None) -> None:
super().__init__(blockname, name, sourceref, False)
self.address = address
self.parameters = parameters
self.input_registers = set() # type: Set[str]
self.return_registers = set() # type: Set[str]
self.clobbered_registers = set() # type: Set[str]
for _, param in parameters:
if param in REGISTER_BYTES:
self.input_registers.add(param)
elif param in REGISTER_WORDS:
self.input_registers.add(param[0])
self.input_registers.add(param[1])
else:
raise SymbolError("invalid parameter spec: " + param)
for register in returnvalues:
if register in REGISTER_SYMBOLS_RETURNVALUES:
self.return_registers.add(register)
elif register[-1] == "?":
for r in register[:-1]:
if r not in REGISTER_SYMBOLS_RETURNVALUES:
raise SymbolError("invalid return value spec: " + r)
self.clobbered_registers.add(r)
else:
raise SymbolError("invalid return value spec: " + register)
class Zeropage:
SCRATCH_B1 = 0x02
SCRATCH_B2 = 0x03
def __init__(self) -> None:
self.unused_bytes = [] # type: List[int]
self.unused_words = [] # type: List[int]
def configure(self, clobber_zp: bool = False) -> None:
if clobber_zp:
self.unused_bytes = list(range(0x04, 0x80))
self.unused_words = list(range(0x80, 0x100, 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)
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
def get_unused_byte(self):
return self.unused_bytes.pop()
def get_unused_word(self):
return self.unused_words.pop()
@property
def available_byte_vars(self) -> int:
return len(self.unused_bytes)
@property
def available_word_vars(self) -> int:
return len(self.unused_words)
# the single, global Zeropage object
zeropage = Zeropage()
class SymbolTable:
def __init__(self, name: str, parent: Optional['SymbolTable'], owning_block: Any) -> None:
self.name = name
self.symbols = {} # type: Dict[str, Union[SymbolDefinition, SymbolTable]]
self.parent = parent
self.owning_block = owning_block
self.eval_dict = None
def __iter__(self):
yield from self.symbols.values()
def __getitem__(self, symbolname: str) -> Union[SymbolDefinition, 'SymbolTable']:
return self.symbols[symbolname]
def __contains__(self, symbolname: str) -> bool:
return symbolname in self.symbols
def lookup(self, dottedname: str, include_builtin_names: bool=False) -> Tuple['SymbolTable', Union[SymbolDefinition, 'SymbolTable']]:
nameparts = dottedname.split('.')
if len(nameparts) == 1:
try:
return self, self.symbols[nameparts[0]]
except LookupError:
if include_builtin_names:
if nameparts[0] in MATH_SYMBOLS:
return self, getattr(math, nameparts[0])
elif nameparts[0] in BUILTIN_SYMBOLS:
return self, getattr(builtins, nameparts[0])
raise SymbolError("undefined symbol '{:s}'".format(nameparts[0]))
# start from toplevel namespace:
scope = self
while scope.parent:
scope = scope.parent
for namepart in nameparts[:-1]:
try:
scope = scope.symbols[namepart] # type: ignore
assert scope.name == namepart
except LookupError:
raise SymbolError("undefined block '{:s}'".format(namepart))
if isinstance(scope, SymbolTable):
return scope.lookup(nameparts[-1])
else:
raise SymbolError("invalid block name '{:s}' in dotted name".format(namepart))
def get_address(self, name: str) -> int:
scope, symbol = self.lookup(name)
if isinstance(symbol, ConstantDef):
raise SymbolError("cannot take the address of a constant")
if not symbol or not isinstance(symbol, VariableDef):
raise SymbolError("no var or const defined by that name")
if symbol.address is None:
raise SymbolError("can only take address of memory mapped variables")
return symbol.address
def as_eval_dict(self) -> Dict[str, Any]:
# return a dictionary suitable to be passed as locals or globals to eval()
if self.eval_dict is None:
d = Eval_symbol_dict(self)
self.eval_dict = d # type: ignore
return self.eval_dict
def iter_variables(self) -> Iterable[VariableDef]:
yield from sorted((v for v in self.symbols.values() if isinstance(v, VariableDef)))
def iter_constants(self) -> Iterable[ConstantDef]:
yield from sorted((v for v in self.symbols.values() if isinstance(v, ConstantDef)))
def iter_subroutines(self) -> Iterable[SubroutineDef]:
yield from sorted((v for v in self.symbols.values() if isinstance(v, SubroutineDef)))
def iter_labels(self) -> Iterable[LabelDef]:
yield from sorted((v for v in self.symbols.values() if isinstance(v, LabelDef)))
def check_identifier_valid(self, name: str, sourceref: SourceRef) -> None:
if not name.isidentifier():
raise SymbolError("invalid identifier")
identifier = self.symbols.get(name, None)
if identifier:
if isinstance(identifier, SymbolDefinition):
raise SymbolError("identifier was already defined at " + str(identifier.sourceref))
raise SymbolError("identifier already defined as " + str(type(identifier)))
if name in MATH_SYMBOLS:
print("warning: {}: identifier shadows a name from the math module".format(sourceref))
elif name in BUILTIN_SYMBOLS:
print("warning: {}: identifier shadows a builtin name".format(sourceref))
def define_variable(self, name: str, sourceref: SourceRef, datatype: DataType, *,
address: int=None, length: int=0, value: PrimitiveType=0,
matrixsize: Tuple[int, int]=None, register: str=None) -> None:
# this defines a new variable and also checks if the prefill value is allowed for the variable type.
assert value is not None
self.check_identifier_valid(name, sourceref)
range_error = check_value_in_range(datatype, register, length, value)
if range_error:
raise ValueError(range_error)
if type(value) in (int, float):
_, value = coerce_value(sourceref, datatype, value) # type: ignore
allocate = address is None
if datatype == DataType.BYTE:
if allocate and self.name == "ZP":
try:
address = zeropage.get_unused_byte()
except LookupError:
raise SymbolError("too many global 8-bit variables in ZP")
self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.BYTE, allocate,
value=value, length=1, address=address)
elif datatype == DataType.WORD:
if allocate and self.name == "ZP":
try:
address = zeropage.get_unused_word()
except LookupError:
raise SymbolError("too many global 16-bit variables in ZP")
self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.WORD, allocate,
value=value, length=1, address=address)
elif datatype == DataType.FLOAT:
if allocate and self.name == "ZP":
raise SymbolError("floats cannot be stored in the ZP")
self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.FLOAT, allocate,
value=value, length=1, address=address)
elif datatype == DataType.BYTEARRAY:
self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.BYTEARRAY, allocate,
value=value, length=length, address=address)
elif datatype == DataType.WORDARRAY:
self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.WORDARRAY, allocate,
value=value, length=length, address=address)
elif datatype in (DataType.STRING, DataType.STRING_P, DataType.STRING_S, DataType.STRING_PS):
self.symbols[name] = VariableDef(self.name, name, sourceref, datatype, True,
value=value, length=len(value)) # type: ignore
elif datatype == DataType.MATRIX:
assert isinstance(matrixsize, tuple)
length = matrixsize[0] * matrixsize[1]
self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.MATRIX, allocate,
value=value, length=length, address=address, matrixsize=matrixsize)
else:
raise ValueError("unknown type " + str(datatype))
self.eval_dict = None
def define_sub(self, name: str, sourceref: SourceRef,
parameters: Sequence[Tuple[str, str]], returnvalues: Set[str], address: Optional[int]) -> None:
self.check_identifier_valid(name, sourceref)
self.symbols[name] = SubroutineDef(self.name, name, sourceref, parameters, returnvalues, address)
def define_label(self, name: str, sourceref: SourceRef) -> None:
self.check_identifier_valid(name, sourceref)
self.symbols[name] = LabelDef(self.name, name, sourceref, False)
def define_scope(self, scope: 'SymbolTable', sourceref: SourceRef) -> None:
self.check_identifier_valid(scope.name, sourceref)
self.symbols[scope.name] = scope
def define_constant(self, name: str, sourceref: SourceRef, datatype: DataType, *,
length: int=0, value: PrimitiveType=0) -> None:
# this defines a new constant and also checks if the value is allowed for the data type.
assert value is not None
self.check_identifier_valid(name, sourceref)
if type(value) in (int, float):
_, value = coerce_value(sourceref, datatype, value) # type: ignore
range_error = check_value_in_range(datatype, "", length, value)
if range_error:
raise ValueError(range_error)
if datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
self.symbols[name] = ConstantDef(self.name, name, sourceref, datatype, value=value, length=length or 1)
elif datatype in STRING_DATATYPES:
strlen = len(value) # type: ignore
self.symbols[name] = ConstantDef(self.name, name, sourceref, datatype, value=value, length=strlen)
else:
raise ValueError("invalid data type for constant: " + str(datatype))
self.eval_dict = None
def merge_roots(self, other_root: 'SymbolTable') -> None:
for name, thing in other_root.symbols.items():
if isinstance(thing, SymbolTable):
self.define_scope(thing, thing.owning_block.sourceref)
def print_table(self, summary_only: bool=False) -> None:
if summary_only:
def count_symbols(symbols: 'SymbolTable') -> int:
count = 0
for s in symbols.symbols.values():
if isinstance(s, SymbolTable):
count += count_symbols(s)
else:
count += 1
return count
print("number of symbols:", count_symbols(self))
else:
def print_symbols(symbols: 'SymbolTable', level: int) -> None:
indent = '\t' * level
print("\n" + indent + "BLOCK:", symbols.name)
for name, s in sorted(symbols.symbols.items(), key=lambda x: getattr(x[1], "sourceref", ("", 0))):
if isinstance(s, SymbolTable):
print_symbols(s, level + 1)
elif isinstance(s, SubroutineDef):
print(indent * 2 + "SUB: " + s.name, s.sourceref, sep="\t")
elif isinstance(s, LabelDef):
print(indent * 2 + "LABEL: " + s.name, s.sourceref, sep="\t")
elif isinstance(s, VariableDef):
print(indent * 2 + "VAR: " + s.name, s.sourceref, s.type, sep="\t")
elif isinstance(s, ConstantDef):
print(indent * 2 + "CONST: " + s.name, s.sourceref, s.type, sep="\t")
else:
raise TypeError("invalid symbol def type", s)
print("\nSymbols defined in the symbol table:")
print("------------------------------------")
print_symbols(self, 0)
print()
class Eval_symbol_dict(dict):
def __init__(self, symboltable: SymbolTable, constants: bool=True) -> None:
super().__init__()
self._symboltable = symboltable
self._constants = constants
def __getattr__(self, name):
return self.__getitem__(name)
def __getitem__(self, name):
if name[0] != '_' and name in builtins.__dict__:
return builtins.__dict__[name]
try:
scope, symbol = self._symboltable.lookup(name)
except (LookupError, SymbolError):
# attempt lookup from global scope
global_scope = self._symboltable
while global_scope.parent:
global_scope = global_scope.parent
scope, symbol = global_scope.lookup(name, True)
if self._constants:
if isinstance(symbol, ConstantDef):
return symbol.value
elif isinstance(symbol, VariableDef):
return symbol.value
elif inspect.isbuiltin(symbol):
return symbol
elif isinstance(symbol, SymbolTable):
return symbol.as_eval_dict()
else:
raise SymbolError("invalid datatype referenced" + repr(symbol))
else:
raise SymbolError("no support for non-constant expression evaluation yet")
def coerce_value(sourceref: SourceRef, datatype: DataType, value: PrimitiveType) -> Tuple[bool, PrimitiveType]:
# if we're a BYTE type, and the value is a single character, convert it to the numeric value
if datatype in (DataType.BYTE, DataType.BYTEARRAY, DataType.MATRIX) and isinstance(value, str):
if len(value) == 1:
return True, char_to_bytevalue(value)
# if we're an integer value and the passed value is float, truncate it (and give a warning)
if datatype in (DataType.BYTE, DataType.WORD, DataType.MATRIX) and type(value) is float:
frac = math.modf(value) # type:ignore
if frac != 0:
print("warning: {}: Float value truncated.".format(sourceref))
return True, int(value)
return False, value
def check_value_in_range(datatype: DataType, register: str, length: int, value: PrimitiveType) -> Optional[str]:
if register:
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_WORDS:
if value is None and datatype in (DataType.BYTE, DataType.WORD):
return None
if value < 0 or value > 0xffff: # type: ignore
return "value out of range, must be (unsigned) word for 2 combined registers"
else:
return "strange register"
elif datatype in (DataType.BYTE, DataType.BYTEARRAY, DataType.MATRIX):
if value is None and datatype == DataType.BYTE:
return None
if value < 0 or value > 0xff: # type: ignore
return "value out of range, must be (unsigned) byte"
elif datatype in (DataType.WORD, DataType.WORDARRAY):
if value is None and datatype in (DataType.BYTE, DataType.WORD):
return None
if value < 0 or value > 0xffff: # type: ignore
return "value out of range, must be (unsigned) word"
elif datatype in STRING_DATATYPES:
if type(value) is not str:
return "value must be a string"
elif datatype == DataType.FLOAT:
if type(value) not in (int, float):
return "value must be a number"
else:
raise SymbolError("missing value check for type", datatype, register, length, value)
return None # all ok !
def char_to_bytevalue(character: str, petscii: bool=True) -> int:
assert len(character) == 1
if petscii:
return ord(character.translate(ascii_to_petscii_trans))
else:
raise NotImplementedError("screencode conversion not yet implemented for chars")
# ASCII/UNICODE-to-PETSCII translation table
# Unicode symbols supported that map to a PETSCII character: £ ↑ ← ♠ ♥ ♦ ♣ π ● ○ and various others
ascii_to_petscii_trans = str.maketrans({
'\f': 147, # form feed becomes ClearScreen
'\n': 13, # line feed becomes a RETURN
'\r': 17, # CR becomes CursorDown
'a': 65,
'b': 66,
'c': 67,
'd': 68,
'e': 69,
'f': 70,
'g': 71,
'h': 72,
'i': 73,
'j': 74,
'k': 75,
'l': 76,
'm': 77,
'n': 78,
'o': 79,
'p': 80,
'q': 81,
'r': 82,
's': 83,
't': 84,
'u': 85,
'v': 86,
'w': 87,
'x': 88,
'y': 89,
'z': 90,
'A': 97,
'B': 98,
'C': 99,
'D': 100,
'E': 101,
'F': 102,
'G': 103,
'H': 104,
'I': 105,
'J': 106,
'K': 107,
'L': 108,
'M': 109,
'N': 110,
'O': 111,
'P': 112,
'Q': 113,
'R': 114,
'S': 115,
'T': 116,
'U': 117,
'V': 118,
'W': 119,
'X': 120,
'Y': 121,
'Z': 122,
'{': 179, # left squiggle
'}': 235, # right squiggle
'£': 92, # pound currency sign
'^': 94, # up arrow
'~': 126, # pi math symbol
'π': 126, # pi symbol
'`': 39, # single quote
'': 250, # check mark
'|': 221, # vertical bar
'': 221, # vertical bar
'': 96, # horizontal bar
'': 123, # vertical and horizontal bar
'': 94, # up arrow
'': 95, # left arrow
'': 163, # upper bar
'_': 164, # lower bar (underscore)
'': 164, # lower bar
'': 165, # left bar
'': 97, # spades
'': 113, # circle
'': 115, # hearts
'': 119, # open circle
'': 120, # clubs
'': 122, # diamonds
'': 171, # vertical and right
'': 179, # vertical and left
'': 177, # horiz and up
'': 178, # horiz and down
'': 173, # up right
'': 174, # down left
'': 175, # down right
'': 189, # up left
'': 172, # block lr
'': 187, # block ll
'': 188, # block ur
'': 190, # block ul
'': 191, # block ul and lr
'': 161, # left half
'': 162, # lower half
'': 230, # raster
})

549
lib/c64lib.ill Normal file
View File

@ -0,0 +1,549 @@
; IL65 definitions for the Commodore-64
; Including memory registers, I/O registers, Basic and Kernel subroutines, utility subroutines.
;
; Written by Irmen de Jong (irmen@razorvine.net)
; License: GNU GPL 3.0, see LICENSE
output raw
~ c64 {
memory SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
memory SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
memory COLOR = $286 ; cursor color
; ---- VIC-II registers ----
memory SP0X = $d000
memory SP0Y = $d001
memory SP1X = $d002
memory SP1Y = $d003
memory SP2X = $d004
memory SP2Y = $d005
memory SP3X = $d006
memory SP3Y = $d007
memory SP4X = $d008
memory SP4Y = $d009
memory SP5X = $d00a
memory SP5Y = $d00b
memory SP6X = $d00c
memory SP6Y = $d00d
memory SP7X = $d00e
memory SP7Y = $d00f
memory MSIGX = $d010
memory SCROLY = $d011
memory RASTER = $d012
memory LPENX = $d013
memory LPENY = $d014
memory SPENA = $d015
memory SCROLX = $d016
memory YXPAND = $d017
memory VMCSB = $d018
memory VICIRQ = $d019
memory IREQMASK = $d01a
memory SPBGPR = $d01b
memory SPMC = $d01c
memory XXPAND = $d01d
memory SPSPCL = $d01e
memory SPBGCL = $d01f
memory EXTCOL = $d020 ; border color
memory BGCOL0 = $d021 ; screen color
memory BGCOL1 = $d022
memory BGCOL2 = $d023
memory BGCOL4 = $d024
memory SPMC0 = $d025
memory SPMC1 = $d026
memory SP0COL = $d027
memory SP1COL = $d028
memory SP2COL = $d029
memory SP3COL = $d02a
memory SP4COL = $d02b
memory SP5COL = $d02c
memory SP6COL = $d02d
memory SP7COL = $d02e
; ---- end of VIC-II registers ----
; ---- C64 basic and kernal ROM float constants and functions ----
; note: the fac1 and fac2 are working registers and take 6 bytes each,
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
; constants in five-byte "mflpt" format in the BASIC ROM
memory .float FL_PIVAL = $aea8 ; 3.1415926...
memory .float FL_N32768 = $b1a5 ; -32768
memory .float FL_FONE = $b9bc ; 1
memory .float FL_SQRHLF = $b9d6 ; SQR(2) / 2
memory .float FL_SQRTWO = $b9db ; SQR(2)
memory .float FL_NEGHLF = $b9e0 ; -.5
memory .float FL_LOG2 = $b9e5 ; LOG(2)
memory .float FL_TENC = $baf9 ; 10
memory .float FL_NZMIL = $bdbd ; 1e9 (1 billion)
memory .float FL_FHALF = $bf11 ; .5
memory .float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
memory .float FL_PIHALF = $e2e0 ; PI / 2
memory .float FL_TWOPI = $e2e5 ; 2 * PI
memory .float FL_FR4 = $e2ea ; .25
; @todo verify clobbered registers?
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
; checked functions below:
subx MOVFM (mflpt: AY) -> (A?, Y?) = $bba2 ; load mflpt value from memory in A/Y into fac1
subx FREADMEM () -> (A?, Y?) = $bba6 ; load mflpt value from memory in $22/$23 into fac1
subx CONUPK (mflpt: AY) -> (A?, Y?) = $ba8c ; load mflpt value from memory in A/Y into fac2
subx FAREADMEM () -> (A?, Y?) = $ba90 ; load mflpt value from memory in $22/$23 into fac2
subx MOVFA () -> (A?, X?) = $bbfc ; copy fac2 to fac1
subx MOVAF () -> (A?, X?) = $bc0c ; copy fac1 to fac2 (rounded)
subx MOVEF () -> (A?, X?) = $bc0f ; copy fac1 to fac2
subx FTOMEMXY (mflpt: XY) -> (A?, Y?) = $bbd4 ; store fac1 to memory X/Y as 5-byte mflpt
subx FTOSWORDYA () -> (Y, A, X?) = $b1aa ; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; use c64util.FTOSWRDAY to get A/Y output (lo/hi switched to normal order)
subx GETADR () -> (Y, A, X?) = $b7f7 ; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY)
; (result also in $14/15) use c64util.GETADRAY to get A/Y output (lo/hi switched to normal order)
subx QINT () -> (A?, X?, Y?) = $bc9b ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
subx AYINT () -> (A?, X?, Y?) = $b1bf ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
subx GIVAYF (lo: Y, hi: A) -> (A?, X?, Y?) = $b391 ; signed word in Y/A -> float in fac1
; use c64util.GIVAYFAY to use A/Y input (lo/hi switched to normal order)
; there is also c64util.GIVUAYF - unsigned word in A/Y (lo/hi) to fac1
; there is also c64util.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64util.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64util.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
subx FREADUY (ubyte: Y) -> (A?, X?, Y?) = $b3a2 ; 8 bit unsigned Y -> float in fac1
subx FREADSA (sbyte: A) -> (A?, X?, Y?) = $bc3c ; 8 bit signed A -> float in fac1
subx FREADSTR (len: A) -> (A?, X?, Y?) = $b7b5 ; str -> fac1, $22/23 must point to string, A=string length
subx FPRINTLN () -> (A?, X?, Y?) = $aabc ; print string of fac1, on one line (= with newline)
subx FOUT () -> (AY, X?) = $bddd ; fac1 -> string, address returned in AY ($0100)
subx FADDH () -> (A?, X?, Y?) = $b849 ; fac1 += 0.5, for rounding- call this before INT
subx MUL10 () -> (A?, X?, Y?) = $bae2 ; fac1 *= 10
subx DIV10 () -> (A?, X?, Y?) = $bafe ; fac1 /= 10 , CAUTION: result is always positive!
subx FCOMP (mflpt: AY) -> (A, X?, Y?) = $bc5b ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
subx FADDT () -> (A?, X?, Y?) = $b86a ; fac1 += fac2
subx FADD (mflpt: AY) -> (A?, X?, Y?) = $b867 ; fac1 += mflpt value from A/Y
subx FSUBT () -> (A?, X?, Y?) = $b853 ; fac1 = fac2-fac1 mind the order of the operands
subx FSUB (mflpt: AY) -> (A?, X?, Y?) = $b850 ; fac1 = mflpt from A/Y - fac1
subx FMULTT () -> (A?, X?, Y?) = $ba2b ; fac1 *= fac2
subx FMULT (mflpt: AY) -> (A?, X?, Y?) = $ba28 ; fac1 *= mflpt value from A/Y
subx FDIVT () -> (A?, X?, Y?) = $bb12 ; fac1 = fac2/fac1 mind the order of the operands
subx FDIV (mflpt: AY) -> (A?, X?, Y?) = $bb0f ; fac1 = mflpt in A/Y / fac1
subx FPWRT () -> (A?, X?, Y?) = $bf7b ; fac1 = fac2 ** fac1
subx FPWR (mflpt: AY) -> (A?, X?, Y?) = $bf78 ; fac1 = fac2 ** mflpt from A/Y
subx NOTOP () -> (A?, X?, Y?) = $aed4 ; fac1 = NOT(fac1)
subx INT () -> (A?, X?, Y?) = $bccc ; INT() truncates, use FADDH first to round instead of trunc
subx LOG () -> (A?, X?, Y?) = $b9ea ; fac1 = LN(fac1) (natural log)
subx SGN () -> (A?, X?, Y?) = $bc39 ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
subx SIGN () -> (A) = $bc2b ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
subx ABS () -> () = $bc58 ; fac1 = ABS(fac1)
subx SQR () -> (A?, X?, Y?) = $bf71 ; fac1 = SQRT(fac1)
subx EXP () -> (A?, X?, Y?) = $bfed ; fac1 = EXP(fac1) (e ** fac1)
subx NEGOP () -> (A?) = $bfb4 ; switch the sign of fac1
subx RND () -> (A?, X?, Y?) = $e097 ; fac1 = RND()
subx COS () -> (A?, X?, Y?) = $e264 ; fac1 = COS(fac1)
subx SIN () -> (A?, X?, Y?) = $e26b ; fac1 = SIN(fac1)
subx TAN () -> (A?, X?, Y?) = $e2b4 ; fac1 = TAN(fac1)
subx ATN () -> (A?, X?, Y?) = $e30e ; fac1 = ATN(fac1)
; ---- C64 basic routines ----
subx CLEARSCR () -> (A?, X?, Y?) = $E544 ; clear the screen
subx HOMECRSR () -> (A?, X?, Y?) = $E566 ; cursor to top left of screen
; ---- end of C64 basic routines ----
; ---- C64 kernal routines ----
subx CINT () -> (A?, X?, Y?) = $FF81 ; (alias: SCINIT) initialize screen editor and video chip
subx IOINIT () -> (A?, X?) = $FF84 ; initialize I/O devices
subx RAMTAS () -> (A?, X?, Y?) = $FF87 ; initialize RAM, tape buffer, screen
subx RESTOR () -> () = $FF8A ; restore default I/O vectors
subx VECTOR (dir: SC, userptr: XY) -> (A?, Y?) = $FF8D ; read/set I/O vector table
subx SETMSG (value: A) -> () = $FF90 ; set Kernal message control flag
subx SECOND (address: A) -> (A?) = $FF93 ; (alias: LSTNSA) send secondary address after LISTEN
subx TKSA (address: A) -> (A?) = $FF96 ; (alias: TALKSA) send secondary address after TALK
subx MEMTOP (dir: SC, address: XY) -> (XY) = $FF99 ; read/set top of memory pointer
subx MEMBOT (dir: SC, address: XY) -> (XY) = $FF9C ; read/set bottom of memory pointer
subx SCNKEY () -> (A?, X?, Y?) = $FF9F ; scan the keyboard
subx SETTMO (timeout: A) -> () = $FFA2 ; set time-out flag for IEEE bus
subx ACPTR () -> (A) = $FFA5 ; (alias: IECIN) input byte from serial bus
subx CIOUT (byte: A) -> () = $FFA8 ; (alias: IECOUT) output byte to serial bus
subx UNTLK () -> (A?) = $FFAB ; command serial bus device to UNTALK
subx UNLSN () -> (A?) = $FFAE ; command serial bus device to UNLISTEN
subx LISTEN (device: A) -> (A?) = $FFB1 ; command serial bus device to LISTEN
subx TALK (device: A) -> (A?) = $FFB4 ; command serial bus device to TALK
subx READST () -> (A) = $FFB7 ; read I/O status word
subx SETLFS (logical: A, device: X, address: Y) -> () = $FFBA ; set logical file parameters
subx SETNAM (namelen: A, filename: XY) -> () = $FFBD ; set filename parameters
subx OPEN () -> (A?, X?, Y?) = $FFC0 ; (via 794 ($31A)) open a logical file
subx CLOSE (logical: A) -> (A?, X?, Y?) = $FFC3 ; (via 796 ($31C)) close a logical file
subx CHKIN (logical: X) -> (A?, X?) = $FFC6 ; (via 798 ($31E)) define an input channel
subx CHKOUT (logical: X) -> (A?, X?) = $FFC9 ; (via 800 ($320)) define an output channel
subx CLRCHN () -> (A?, X?) = $FFCC ; (via 802 ($322)) restore default devices
subx CHRIN () -> (A, Y?) = $FFCF ; (via 804 ($324)) input a character
subx CHROUT (char: A) -> () = $FFD2 ; (via 806 ($326)) output a character
subx LOAD (verify: A, address: XY) -> (SC, A, X, Y) = $FFD5 ; (via 816 ($330)) load from device
subx SAVE (zp_startaddr: A, endaddr: XY) -> (SC, A) = $FFD8 ; (via 818 ($332)) save to a device
subx SETTIM (low: A, middle: X, high: Y) -> () = $FFDB ; set the software clock
subx RDTIM () -> (A, X, Y) = $FFDE ; read the software clock
subx STOP () -> (SZ, SC, A?, X?) = $FFE1 ; (via 808 ($328)) check the STOP key
subx GETIN () -> (A, X?, Y?) = $FFE4 ; (via 810 ($32A)) get a character
subx CLALL () -> (A?, X?) = $FFE7 ; (via 812 ($32C)) close all files
subx UDTIM () -> (A?, X?) = $FFEA ; update the software clock
subx SCREEN () -> (X, Y) = $FFED ; read number of screen rows and columns
subx PLOT (dir: SC, col: X, row: Y) -> (X, Y) = $FFF0 ; read/set position of cursor on screen
subx IOBASE () -> (X, Y) = $FFF3 ; read base address of I/O devices
; ---- end of C64 kernal routines ----
memory .word NMI_VEC = $FFFA
memory .word RESET_VEC = $FFFC
memory .word IRQ_VEC = $FFFE
}
~ c64util {
; @todo use user-defined subroutines here to have param definitions
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
FREADS32 ; () -> (A?, X?, Y?)
asm {
lda $62
eor #$ff
asl a
lda #0
ldx #$a0
jmp $bc4f
}
; ---- fac1 = uint32 from $62-$65 big endian (MSB FIRST)
FREADUS32 ; () -> (A?, X?, Y?)
asm {
sec
lda #0
ldx #$a0
jmp $bc4f
}
; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes)
; note: there is no FREADU24AXY (unsigned), use FREADUS32 instead.
FREADS24AXY ; (lo: A, mid: X, hi: Y) -> (A?, X?, Y?)
asm {
sty $62
stx $63
sta $64
lda $62
eor #$FF
asl a
lda #0
sta $65
ldx #$98
jmp $bc4f
}
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
GIVUAYF ; (uword: AY) -> (A?, X?, Y?)
asm {
sty $62
sta $63
ldx #$90
sec
jmp $bc49
}
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
GIVAYFAY ; (sword: AY) -> (A?, X?, Y?)
asm {
sta c64.SCRATCH_ZP1
tya
ldy c64.SCRATCH_ZP1
jmp c64.GIVAYF ; this uses the inverse order, Y/A
}
; ---- fac1 to signed word in A/Y
FTOSWRDAY ; () -> (A, Y, X?)
asm {
jsr c64.FTOSWORDYA ; note the inverse Y/A order
sta c64.SCRATCH_ZP1
tya
ldy c64.SCRATCH_ZP1
rts
}
; ---- fac1 to unsigned word in A/Y
GETADRAY ; () -> (A, Y, X?)
asm {
jsr c64.GETADR ; this uses the inverse order, Y/A
sta c64.SCRATCH_ZP1
tya
ldy c64.SCRATCH_ZP1
rts
}
; ---- print null terminated string from X/Y
print_string ; (address: XY) -> (A?, Y?)
asm {
stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZP2
ldy #0
- lda (c64.SCRATCH_ZP1),y
beq +
jsr c64.CHROUT
iny
bne -
+ rts
}
; ---- print pstring (length as first byte) from X/Y, returns str len in Y
print_pstring ; (address: XY) -> (A?, X?, Y)
asm {
stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZP2
ldy #0
lda (c64.SCRATCH_ZP1),y
beq +
tax
- iny
lda (c64.SCRATCH_ZP1),y
jsr c64.CHROUT
dex
bne -
+ rts ; output string length is in Y
}
; ---- print pstring in memory immediately following the fcall instruction (don't use call!)
print_pimmediate
asm {
tsx
lda $102,x
tay ; put high byte in y
lda $101,x
tax ; and low byte in x.
inx
bne +
iny
+ jsr print_pstring ; print string in XY, returns string length in y.
tya
tsx
clc
adc $101,x ; add content of 1st (length) byte to return addr.
bcc + ; if that made the low byte roll over to 00,
inc $102,x ; then increment the high byte too.
+ clc
adc #1 ; now add 1 for the length byte itself.
sta $101,x
bne + ; if that made it (the low byte) roll over to 00,
inc $102,x ; increment the high byte of the return addr too.
+ rts
}
; ---- A to decimal string in Y/X/A (100s in Y, 10s in X, 1s in A)
byte2decimal ; (ubyte: A) -> (Y, X, A)
asm {
ldy #$2f
ldx #$3a
sec
- iny
sbc #100
bcs -
- dex
adc #10
bmi -
adc #$2f
rts
}
; ---- A to hex string in XY (first hex char in X, second hex char in Y)
byte2hex ; (ubyte: A) -> (X, Y, A?)
asm {
pha
and #$0f
tax
ldy hex_digits,x
pla
lsr a
lsr a
lsr a
lsr a
tax
lda hex_digits,x
tax
rts
hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
}
; Convert an 16 bit binary value to BCD
;
; This function converts a 16 bit binary value in X/Y into a 24 bit BCD. It
; works by transferring one bit a time from the source and adding it
; into a BCD value that is being doubled on each iteration. As all the
; arithmetic is being done in BCD the result is a binary to decimal
; conversion.
var .array(3) word2bcd_bcdbuff
word2bcd ; (address: XY) -> (A?, X?)
asm {
stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZP2
sed ; switch to decimal mode
lda #0 ; ensure the result is clear
sta word2bcd_bcdbuff+0
sta word2bcd_bcdbuff+1
sta word2bcd_bcdbuff+2
ldx #16 ; the number of source bits
- asl c64.SCRATCH_ZP1 ; shift out one bit
rol c64.SCRATCH_ZP2
lda word2bcd_bcdbuff+0 ; and add into result
adc word2bcd_bcdbuff+0
sta word2bcd_bcdbuff+0
lda word2bcd_bcdbuff+1 ; propagating any carry
adc word2bcd_bcdbuff+1
sta word2bcd_bcdbuff+1
lda word2bcd_bcdbuff+2 ; ... thru whole result
adc word2bcd_bcdbuff+2
sta word2bcd_bcdbuff+2
dex ; and repeat for next bit
bne -
cld ; back to binary
rts
}
; ---- convert 16 bit word in X/Y into decimal string into memory 'word2decimal_output'
var .array(5) word2decimal_output
word2decimal ; (address: XY) -> (A?, X?, Y?)
asm {
jsr word2bcd
lda word2bcd_bcdbuff+2
clc
adc #'0'
sta word2decimal_output
ldy #1
lda word2bcd_bcdbuff+1
jsr +
lda word2bcd_bcdbuff+0
+ pha
lsr a
lsr a
lsr a
lsr a
clc
adc #'0'
sta word2decimal_output,y
iny
pla
and #$0f
adc #'0'
sta word2decimal_output,y
iny
rts
}
; ---- print the byte in A in decimal form, with left padding 0s (3 positions total)
print_byte_decimal0 ; (ubyte: A) -> (A?, X?, Y?)
asm {
jsr byte2decimal
pha
tya
jsr c64.CHROUT
txa
jsr c64.CHROUT
pla
jmp c64.CHROUT
}
; ---- print the byte in A in decimal form, without left padding 0s
print_byte_decimal ; (ubyte: A) -> (A?, X?, Y?)
asm {
jsr byte2decimal
pha
tya
cmp #'0'
beq +
jsr c64.CHROUT
+ txa
cmp #'0'
beq +
jsr c64.CHROUT
+ pla
jmp c64.CHROUT
}
; ---- print the byte in A in hex form
print_byte_hex ; (ubyte: A) -> (A?, X?, Y?)
asm {
jsr byte2hex
txa
jsr c64.CHROUT
tya
jmp c64.CHROUT
}
; ---- print the word in X/Y in decimal form, with left padding 0s (5 positions total)
print_word_decimal0 ; (address: XY) -> (A?, X?, Y?)
asm {
jsr word2decimal
lda word2decimal_output
jsr c64.CHROUT
lda word2decimal_output+1
jsr c64.CHROUT
lda word2decimal_output+2
jsr c64.CHROUT
lda word2decimal_output+3
jsr c64.CHROUT
lda word2decimal_output+4
jmp c64.CHROUT
}
; ---- print the word in X/Y in decimal form, without left padding 0s
print_word_decimal ; (address: XY) -> (A?, X? Y?)
asm {
jsr word2decimal
ldy #0
lda word2decimal_output
cmp #'0'
bne _pr_decimal
iny
lda word2decimal_output+1
cmp #'0'
bne _pr_decimal
iny
lda word2decimal_output+2
cmp #'0'
bne _pr_decimal
iny
lda word2decimal_output+3
cmp #'0'
bne _pr_decimal
iny
_pr_decimal
lda word2decimal_output,y
jsr c64.CHROUT
iny
cpy #5
bcc _pr_decimal
rts
}
}

4
mypy.ini Normal file
View File

@ -0,0 +1,4 @@
[mypy]
follow_imports = normal
ignore_missing_imports = True
incremental = True

386
reference.txt Normal file
View File

@ -0,0 +1,386 @@
------------------------------------------------------------
il65 - "Intermediate Language for 6502/6510 microprocessors"
------------------------------------------------------------
Written by Irmen de Jong (irmen@razorvine.net)
License: GNU GPL 3.0, see LICENSE
------------------------------------------------------------
The python program parses it and generates 6502 assembler code.
It uses the 64tass macro cross assembler to assemble it into binary files.
Memory Model
------------
Zero page: $00 - $ff
Hardware stack: $100 - $1ff
Free RAM/ROM: $0200 - $ffff
Reserved:
data direction $00
bank select $01
NMI VECTOR $fffa
RESET VECTOR $fffc
IRQ VECTOR $fffe
A particular 6502/6510 machine such as the Commodore-64 will have many other
special addresses due to:
- ROMs installed in the machine (basic, kernel and character generator roms)
- memory-mapped I/O registers (for the video and sound chip for example)
- RAM areas used for screen graphics and sprite data.
Usable Hardware registers:
A, X, Y,
AX, AY, XY (16-bit combined register pairs)
SC (status register Carry flag)
These cannot occur as variable names - they will always refer to the hardware registers.
The zero page locations $02-$ff can be regarded as 254 other registers.
Free zero page addresses on the C-64:
$02,$03 # reserved as scratch addresses
$04,$05
$06
$0a
$2a
$52
$93
$f7,$f8
$f9,$fa
$fb,$fc
$fd,$fe
IL program parsing structure:
-----------------------------
OUTPUT MODES:
-------------
output raw ; no load address bytes
output prg ; include the first two load address bytes, (default is $0801), no basic program
output prg,sys ; include the first two load address bytes, basic start program with sys call to code, default code start
; immediately after the basic program at $081d, or beyond.
address $0801 ; override program start address (default is set to $c000 for raw mode and $0801 for c-64 prg mode)
; cannot be used if output mode is prg,sys because basic programs always have to start at $0801
data types:
byte 8 bits $8f (unsigned, @todo signed bytes)
int 16 bits $8fee (unsigned, @todo signed ints)
bool true/false (aliases for the integer values 1 and 0, not a true datatype by itself)
char '@' (converted to a byte)
float 40 bits 1.2345 (stored in 5-byte cbm MFLPT format)
@todo 24 and 32 bits integers, unsigned and signed?
string 0-terminated sequence of bytes "hello." (implicit 0-termination byte)
pstring sequence of bytes where first byte is the length. (no 0-termination byte)
For strings, both petscii and screencode variants can be written in source, they will be translated at compile/assembler time.
Note: for many floating point operations, the compiler uses routines in the C64 BASIC and KERNAL ROMs.
So they will only work if the BASIC ROM (and KERNAL ROM) are banked in.
largest 5-byte MFLPT float: 1.7014118345e+38 (negative: -1.7014118345e+38)
Note: with the # prefix you can take the address of something. This is sometimes useful,
for instance when you want to manipulate the ADDRESS of a memory mapped variable rather than
the value it represents. You can take the address of a string as well, but the compiler already
treats those as a value that you manipulate via its address, so the # is ignored here.
BLOCKS
------
~ blockname [address] {
statements
}
The blockname "ZP" is reserved and always means the ZeroPage. Its start address is always set to $04,
because $00/$01 are used by the hardware and $02/$03 are reserved as general purpose scratch registers.
Block names cannot occur more than once, EXCEPT 'ZP' where the contents of every occurrence of it are merged.
Block address must be >= $0200 (because $00-$fff is the ZP and $100-$200 is the cpu stack)
You can omit the blockname but then you can only refer to the contents of the block via its absolute address,
which is required in this case. If you omit both, the block is ignored altogether (and a warning is displayed).
IMPORTING, INCLUDING and BINARY-INCLUDING files
-----------------------------------------------
import "filename[.ill]"
Can only be used outside of a block (usually at the top of your file).
Reads everything from the named IL65 file at this point and compile it as a normal part of the program.
asminclude "filename.txt", scopelabel
Can only be used in a block.
The assembler will include the file as asm source text at this point, il65 will not process this at all.
The scopelabel will be used as a prefix to access the labels from the included source code,
otherwise you would risk symbol redefinitions or duplications.
asmbinary "filename.bin" [, <offset>[, <length>]]
Can only be used in a block.
The assembler will include the file as binary bytes at this point, il65 will not process this at all.
The optional offset and length can be used to select a particular piece of the file.
MACROS
------
@todo macros are meta-code (written in Python syntax) that actually runs in a preprecessing step
during the compilation, and produces output value that is then replaced on that point in the input source.
Allows us to create pre calculated sine tables and such. Something like:
var .array sinetable ``[sin(x) * 10 for x in range(100)]``
EXPRESSIONS
-----------
In most places where a number or other value is expected, you can use just the number, or a full constant expression.
The expression is parsed and evaluated by Python itself at compile time, and the (constant) resulting value is used in its place.
Ofcourse the special il65 syntax for hexadecimal numbers ($xxxx), binary numbers (%bbbbbb),
and the address-of (#xxxx) is supported. Other than that it must be valid Python syntax.
Expressions can contain function calls to the math library (sin, cos, etc) and you can also use
all builtin functions (max, avg, min, sum etc). They can also reference idendifiers defined elsewhere in your code,
if this makes sense.
The syntax "[address]" means: the contents of the memory at address.
By default, if not otherwise known, a single byte is assumed. You can add the ".byte" or ".word" or ".float" suffix
to make it clear what data type the address points to.
Everything after a semicolon ';' is a comment and is ignored.
# @todo Everything after a double semicolon ';;' is a comment and is ignored, but is copied into the resulting assembly source code.
FLOW CONTROL
------------
Required building blocks: additional forms of 'go' statement: including an if clause, comparison statement.
- a primitive conditional branch instruction (special case of 'go'): directly translates to a branch instruction:
if[_XX] go <label>
XX is one of: (cc, cs, vc, vs, eq, ne, pos, min,
lt==cc, lts==min, gt==eq+cs, gts==eq+pos, le==cc+eq, les==neg+eq, ge==cs, ges==pos)
and when left out, defaults to ne (not-zero, i.e. true)
NOTE: some combination branches such as cc+eq an be peephole optimized see http://www.6502.org/tutorials/compare_beyond.html#2.2
- conditional go with expression: where the if[_XX] is followed by a <expression>
in that case, evaluate the <expression> first (whatever it is) and then emit the primitive if[_XX] go
if[_XX] <expression> go <label>
eventually translates to:
<expression-code>
bXX <label>
- comparison statement: compares left with right: compare <first_value>, <second_value>
(and keeps the comparison result in the status register.)
this translates into a lda first_value, cmp second_value sequence after which a conditional branch is possible.
IF_XX:
------
if[_XX] [<expression>] {
...
}
[ else {
... ; evaluated when the condition is not met
} ]
==> DESUGARING ==>
(no else:)
if[_!XX] [<expression>] go il65_if_999_end ; !XX being the conditional inverse of XX
.... (true part)
il65_if_999_end ; code continues after this
(with else):
if[_XX] [<expression>] go il65_if_999
... (else part)
go il65_if_999_end
il65_if_999 ... (true part)
il65_if_999_end ; code continues after this
IF X <COMPARISON> Y:
-----------------------
==> DESUGARING ==>
compare X, Y
if_XX go ....
XX based on <COMPARISON>.
WHILE:
------
while[_XX] <expression> {
...
continue
break
}
==> DESUGARING ==>
go il65_while_999_check ; jump to the check
il65_while_999
... (code)
go il65_while_999 ;continue
go il65_while_999_end ;break
il65_while_999_check
if[_XX] <expression> go il65_while_999 ; loop condition
il65_while_999_end ; code continues after this
REPEAT:
------
repeat {
...
continue
break
} until[_XX] <expressoin>
==> DESUGARING ==>
il65_repeat_999
... (code)
go il65_repeat_999 ;continue
go il65_repeat_999_end ;break
if[_!XX] <expression> go il65_repeat_999 ; loop condition via conditional inverse of XX
il65_repeat_999_end ; code continues after this
FOR:
----
for <loopvar> = <from_expression> to <to_expression> [step <step_expression>] {
...
break
continue
}
@todo how to do signed integer loopvars?
==> DESUGARING ==>
loopvar = <from_expression>
compare loopvar, <to_expression>
if_ge go il65_for_999_end ; loop condition
step = <step_expression> ; (store only if step < -1 or step > 1)
il65_for_999
go il65_for_999_end ;break
go il65_for_999_loop ;continue
.... (code)
il65_for_999_loop
loopvar += step ; (if step > 1 or step < -1)
loopvar++ ; (if step == 1)
loopvar-- ; (if step == -1)
go il65_for_999 ; continue the loop
il65_for_999_end ; code continues after this
MEMORY BLOCK OPERATIONS:
@todo matrix,list,string memory block operations:
- matrix type operations (whole matrix, per row, per column, individual row/column)
operations: set, get, copy (from another matrix with the same dimensions, or list with same length),
shift-N (up, down, left, right, and diagonals, meant for scrolling)
rotate-N (up, down, left, right, and diagonals, meant for scrolling)
clear (set whole matrix to the given value, default 0)
- list operations (whole list, individual element)
operations: set, get, copy (from another list with the same length), shift-N(left,right), rotate-N(left,right)
clear (set whole list to the given value, default 0)
- list and matrix operations ofcourse work identical on vars and on memory mapped vars of these types.
- strings: identical operations as on lists.
these call (or emit inline) optimized pieces of assembly code, so they run as fast as possible
SUBROUTINES DEFINITIONS
-----------------------
External subroutines for instance defined in ROM, can be defined using the 'subx' statement.
subx <identifier> ([proc_parameters]) -> ([proc_results]) <address>
proc_parameters = sequence of "<parametername>:<register>" pairs that specify what the input parameters are
proc_results = sequence of <register> names that specify in which register(s) the output is returned
if the name ends with a '?', that means the register doesn't contain a real return value but
is clobbered in the process so the original value it had before calling the sub is no longer valid.
example: "subx CLOSE (logical: A) -> (A?, X?, Y?) $FFC3"
ISOLATION (register preservation when calling subroutines): @todo isolation
isolate [regs] { .... } that adds register preservation around the containing code default = all 3 regs, or specify which.
fcall -> fastcall, doesn't do register preservations
call -> as before, alsways does it, even when already in isolate block
@todo user defined subroutines
SUBROUTINE CALLS
----------------
CALL and FCALL:
They are just inserting a call to the specified location or subroutine.
[F]CALL: calls subroutine and continue afterwards ('gosub'):
[f]call <subroutine> / <label> / <address> / `[`indirect-pointer`]` [arguments...]
A 'call' preserves all registers when doing the procedure call and restores them afterwards.
'fcall' (fast call) doesn't preserve registers, so generates code that is a lot faster.
It's basically one jmp or jsr instruction. It can clobber register values because of this.
If you provide arguments (not required) these will be matched to the subroutine's parameters.
If you don't provide arguments, it is assumed you have prepared the correct registers etc yourself.
The following contemporary syntax to call a subroutine is also available:
subroutine `(` [arguments...] `)`
subroutine! `(` [arguments...] `)`
These are understood as: "call subroutine arguments" and "fcall subroutine arguments" respectively.
You can only call a subroutine or label this way. This syntax cannot be used
to call a memory address or variable, you have to use the call statement for that.
GO:
'go' continues execution with the specified routine or address and doesn't retuurn (it is a 'goto'):
go <subroutine> / <label> / <address> / [indirect-pointer]
@todo support call non-register args (variable parameter passing)
@todo support call return values (so that you can assign these to other variables, and allows the line to be a full expression)
@todo BITMAP DEFINITIONS:
to define CHARACTERS (8x8 monochrome or 4x8 multicolor = 8 bytes)
--> PLACE in memory on correct address (???k aligned)
and SPRITES (24x21 monochrome or 12x21 multicolor = 63 bytes)
--> PLACE in memory on correct address (base+sprite pointer, 64-byte aligned)

3
setup.cfg Normal file
View File

@ -0,0 +1,3 @@
[pycodestyle]
max-line-length = 140
exclude = .git,__pycache__,.tox,docs,tests,build,dist

0
tests/__init__.py Normal file
View File

45
tests/test_floats.py Normal file
View File

@ -0,0 +1,45 @@
import pytest
from il65 import il65, symbols
def test_float_to_mflpt5():
mflpt = il65.CodeGenerator.to_mflpt5(1.0)
assert type(mflpt) is bytearray
assert b"\x00\x00\x00\x00\x00" == il65.CodeGenerator.to_mflpt5(0)
assert b"\x82\x49\x0F\xDA\xA1" == il65.CodeGenerator.to_mflpt5(3.141592653)
assert b"\x82\x49\x0F\xDA\xA2" == il65.CodeGenerator.to_mflpt5(3.141592653589793)
assert b"\x90\x80\x00\x00\x00" == il65.CodeGenerator.to_mflpt5(-32768)
assert b"\x81\x00\x00\x00\x00" == il65.CodeGenerator.to_mflpt5(1)
assert b"\x80\x35\x04\xF3\x34" == il65.CodeGenerator.to_mflpt5(0.7071067812)
assert b"\x80\x35\x04\xF3\x33" == il65.CodeGenerator.to_mflpt5(0.7071067811865476)
assert b"\x81\x35\x04\xF3\x34" == il65.CodeGenerator.to_mflpt5(1.4142135624)
assert b"\x81\x35\x04\xF3\x33" == il65.CodeGenerator.to_mflpt5(1.4142135623730951)
assert b"\x80\x80\x00\x00\x00" == il65.CodeGenerator.to_mflpt5(-.5)
assert b"\x80\x31\x72\x17\xF8" == il65.CodeGenerator.to_mflpt5(0.69314718061)
assert b"\x80\x31\x72\x17\xF7" == il65.CodeGenerator.to_mflpt5(0.6931471805599453)
assert b"\x84\x20\x00\x00\x00" == il65.CodeGenerator.to_mflpt5(10)
assert b"\x9E\x6E\x6B\x28\x00" == il65.CodeGenerator.to_mflpt5(1000000000)
assert b"\x80\x00\x00\x00\x00" == il65.CodeGenerator.to_mflpt5(.5)
assert b"\x81\x38\xAA\x3B\x29" == il65.CodeGenerator.to_mflpt5(1.4426950408889634)
assert b"\x81\x49\x0F\xDA\xA2" == il65.CodeGenerator.to_mflpt5(1.5707963267948966)
assert b"\x83\x49\x0F\xDA\xA2" == il65.CodeGenerator.to_mflpt5(6.283185307179586)
assert b"\x7F\x00\x00\x00\x00" == il65.CodeGenerator.to_mflpt5(.25)
def test_float_range():
assert b"\xff\x7f\xff\xff\xff" == il65.CodeGenerator.to_mflpt5(symbols.FLOAT_MAX_POSITIVE)
assert b"\xff\xff\xff\xff\xff" == il65.CodeGenerator.to_mflpt5(symbols.FLOAT_MAX_NEGATIVE)
with pytest.raises(OverflowError):
il65.CodeGenerator.to_mflpt5(1.7014118346e+38)
with pytest.raises(OverflowError):
il65.CodeGenerator.to_mflpt5(-1.7014118346e+38)
with pytest.raises(OverflowError):
il65.CodeGenerator.to_mflpt5(1.7014118347e+38)
with pytest.raises(OverflowError):
il65.CodeGenerator.to_mflpt5(-1.7014118347e+38)
assert b"\x03\x39\x1d\x15\x63" == il65.CodeGenerator.to_mflpt5(1.7e-38)
assert b"\x00\x00\x00\x00\x00" == il65.CodeGenerator.to_mflpt5(1.7e-39)
assert b"\x03\xb9\x1d\x15\x63" == il65.CodeGenerator.to_mflpt5(-1.7e-38)
assert b"\x00\x00\x00\x00\x00" == il65.CodeGenerator.to_mflpt5(-1.7e-39)

63
testsource/calls.ill Normal file
View File

@ -0,0 +1,63 @@
; call tests
;output
~ foo {
var .word var1 = 99
memory .word mem1 = $cff0
var .byte varb1 = 99
memory .byte memb1 = $cff0
bar
go [AX] ; @todo check indrection jmp (AX)
go [var1] ; @todo check indirection jmp (var1)
go [#mem1] ; @todo check indirection jmp ($cff0)
; go mem1 ; @todo support this, should jmp $cff0
go [$c2]
go [$c2dd]
go $c000
go $c2
asm {
nop
nop
nop
nop
}
fcall [XY]
fcall [var1]
fcall [#mem1]
;fcall mem1 ; @todo support this, should jsr $cff0
fcall [$c2]
fcall [$c2dd]
fcall $c000
fcall $c2
asm {
nop
nop
nop
nop
}
call [AX]
call [var1]
call [#mem1]
;call mem1 ; @todo support this, should jsr $cff0
call [$c2]
call [$c2dd]
call $c000
call $c2
}
~ main {
start
call foo.bar
return
}

242
testsource/dtypes.ill Normal file
View File

@ -0,0 +1,242 @@
; var defintions and immediate primitive data type tests
output raw
clobberzp
~ ZP {
; ZeroPage block definition:
; base address is set to $04 (because $00 and $01 are used by the hardware, and $02/$03 are scratch)
; everything here ends up in the zeropage, as long as there is space.
; you can NOT put subroutines in here (yet).
}
~ ZP $0004 {
var zpvar1
memory zpmem1 = $f0
const zpconst = 1.234
}
~ ZP {
; will be merged with other ZP blocks!
var zpvar1b = $88
}
~ foo {
var foovar1
memory foomem1 = $f0f0
const fooconst = 1.234
}
~ main {
; variables
var uninitbyte1
var .byte uninitbyte2
var initbyte1 = $12
var initbyte1b = true
var .byte initbyte2 = $12
var .byte initbyte2b = false
var .byte initbyte3 = 99.876
var initchar1 = '@'
var .byte initchar2 = '@'
var .word uninitword
var .word initword1 = $1234
var .word initword1b = true
var .word initword2 = false
var .word initword3 = 9876.554321
var .word initword4 = initword3
var .word initword5 = 20
var .float uninitfloat
var .float initfloat1 = 0
var .float initfloat1b = true
var .float initfloat2 = -1.234e-14
var .float initfloat3 = 9.87e+14
var .float initfloat4 = 1.70141183e+38
var .float initfloat5 = -1.70141183e+38
var .float initfloat6 = 1.234
var .array( 10) uninit_bytearray
var .array(10 ) init_bytearray =$12
var .array(10 ) init_bytearrayb =true
var .array(10 ) init_bytearrayc ='@'
var .wordarray( 10 ) uninit_wordarray
var .wordarray(10) init_wordarray = $1234
var .wordarray(10) init_wordarrayb = true
var .text text = "hello"+"-null"
var .ptext ptext = 'hello-pascal'
var .stext stext = 'screencodes-null'
var .pstext pstext = "screencodes-pascal"
var .matrix( 10, 20 ) uninitmatrix
var .matrix(10, 20) initmatrix1 = $12
var .matrix(10, 20) initmatrix1b = true
var .matrix(10, 20) initmatrix1c = '@'
var .matrix(10, 20) initmatrix1d = 123.456
; memory-mapped variables
memory membyte1 = $cf01
memory .byte membyte2 = $c2
memory .byte membyte3 = initbyte2
memory .word memword1 = $cf03
memory .float memfloat = $cf04
memory .array(10 ) membytes = $cf05
memory .wordarray( 10) memwords = $cf06
memory .matrix( 10, 20 ) memmatrix = $cf07
; constants (= names for constant values, can never occur as lvalue)
const cbyte1 = 1
const cbyte1b = false
const .byte cbyte2 = 1
const .byte cbyte3 = '@'
const .byte cbyte4 = true
const .word cbyte6 = false
const .word cword2 = $1234
const .word cword5 = 9876.5432
const cfloat1 = 1.2345
const .float cfloat2 = 2.3456
const .float cfloat2b = cfloat2*3.44
const .float cfloat3 = true
const .text ctext3 = "constant-text"
const .ptext ctext4 = "constant-ptext"
const .stext ctext5 = "constant-stext"
const .pstext ctext6 = "constant-pstext"
; taking the address of various things:
var .word vmemaddr1 = #membyte1
var .word vmemaddr2 = #memword1
var .word vmemaddr3 = #memfloat
var .word vmemaddr4 = #membytes
var .word vmemaddr5 = #memwords
var .word vmemaddr6 = #memmatrix
var .word vmemaddr7 = vmemaddr1
var .word vmemaddr8 = 100*sin(cbyte1)
var .word vmemaddr9 = cword2+$5432
var .word vmemaddr10 = cfloat2b
; taking the address of things from the ZP will work even when it is a var
; because zp-vars get assigned a specific address (from a pool). Also, it's a byte.
var .word initword0a = #ZP.zpmem1
var .word initword0 = #ZP.zpvar1
var initbytea0 = #ZP.zpmem1
var .word initworda1 = #ZP.zpvar1
; (constant) expressions
var expr_byte1b = -1-2-3-4-$22+0x80+ZP.zpconst
var .byte expr_fault2 = 1 + (8/3)+sin(33) + max(1,2,3)
sin
return
max
return
start
; --- immediate primitive value assignments ----
A = 0
A = '@'
A = 1.2345
A = true
A = false
A = 255
A = X
A = [$c020]
A = [membyte2]
;A = [membyte2.byte] ; @todo ok
;A = [membyte2.word] ; @todo type error
;A = [membyte2.float] ; @todo type error
; A = #expr_byte1b ; @todo cannot assign address to byte
XY = 0
XY = '@'
XY = 1.2345
XY = 456.66
XY = 65535
XY = true
XY = false
XY = text
XY = "text-immediate"
AY = "text-immediate"
AX = #"text-immediate" ; equivalent to simply assigning the string directly
AX = # "text-immediate" ; equivalent to simply assigning the string directly
AX = ""
AX = XY
AX = Y
[$c000] = 255
[$c000] = '@'
[$c000] = 1.2345
[$c000] = true
[$c000] = false
[$c000.word] = 65535
[$c000.word] = 456.66
[$c000.word] = "text"
[$c000.word] = ""
[$c000.float] = 65535
[$c000.float] = 456.66
[$c000.float] = 1.70141183e+38
;[$c000.byte] = AX ; @todo out of range
[$c000.word] = AX
[$c001] = [$c002]
;[$c001.word] = [$c002] ;@todo okay (pad)
;[$c001.word] = [$c002.byte] ;@todo okay (pad)
[$c001.word] = [$c002.word]
;[$c001.word] = [$c002.float] ;@todo parse error
;[$c001.float] = [$c002.byte] ;@todo ok
;[$c001.float] = [$c002.word] ;@todo support this
;[$c001.float] = [$c002.float] ;@todo support this
SC = 0
SC = 1
SC = false
uninitbyte1 = 99
uninitbyte1 = 1.234
uninitbyte1 = '@'
initbyte1 = 99
initbyte1 = 1.234
initbyte1 = '@'
initbyte1 = A
uninitword = 99
uninitword = 5.6778
uninitword = "test"
uninitword = '@'
uninitword = A
uninitword = XY
initfloat1 = 99
initfloat1 = 9.8765
initfloat1 = '@'
uninitfloat = 99
uninitfloat = 9.8765
uninitfloat = '@'
;uninitfloat = A ; @todo support this
; uninitfloat = XY ; @todo support this
membyte1 = 22
memword1 = 2233
memfloat = 3.4567
membyte1 = A
memword1 = A
memword1 = XY
;memfloat = A ; @todo support this
;memfloat = XY ; @todo support this
return
}
~ footer {
XY = "text-immediate" ; reuses existing
AY = "text-immediate" ; reuses existing
AX = "another"
AX = ""
[$c000.word] = "another" ; reuse
[$c100.word] = "text-immediate" ; reuse
[$c200.word] = "" ; reuse
}

140
testsource/floats.ill Normal file
View File

@ -0,0 +1,140 @@
; floating point tests
output prg, sys
import "c64lib"
~ main_testing {
start
var .float myfloat1 = 1234.56789
var .float myfloat2 = 9876.54321
var .float myfloatneg1 = -555.666
var .float myfloat_large1 = 987654.1111
var .float myfloat_large2 = -123456.2222
var .text myfloatstr = "1234.998877"
var .float myfloatzero = 0
var .float myfloatsmall1 = 1.234
var .float myfloatsmall2 = 2.6677
c64.MOVFM!(#myfloatsmall1)
c64.FCOMP!(#myfloatsmall1)
[$0400]=A
c64.MOVFM!(#myfloatsmall1)
c64.FCOMP!(#myfloatsmall2)
[$0401]=A
c64.MOVFM!(#myfloatsmall2)
c64.FCOMP!(#myfloatsmall1)
[$0402]=A
c64util.FREADS32()
return
}
~ main {
var .float flt_pi = 3.141592653589793
var .float flt_minus32768 = -32768
var .float flt_1 = 1
var .float flt_half_sqr2 = 0.7071067811865476
var .float flt_sqr2 = 1.4142135623730951
var .float flt_minus_half = -.5
var .float flt_log_2 = 0.6931471805599453
var .float flt_10 = 10
var .float flt_1e9 = 1000000000
var .float flt_half = .5
var .float flt_one_over_log_2 = 1.4426950408889634
var .float flt_half_pi = 1.5707963267948966
var .float flt_double_pi = 6.283185307179586
var .float flt_point25 = .25
memory .word some_address = $ccdd
memory .byte some_addressb = $ccee
start
AY = #flt_pi
[some_address] = # flt_pi
[some_address] = # flt_pi
[some_address] = # flt_pi
[some_address] = 4123.2342342222
[some_address] = 4123.2342342222
[some_address] = 4123.2342342222
[some_address.word] = #flt_pi
[some_address.word] = #flt_pi
[some_address.word] = #flt_pi
[$c000.word] = # flt_pi
c64.MOVFM!(#flt_pi)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_PIVAL)
c64.FPRINTLN !()
c64.MOVFM!(#flt_minus32768)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_N32768)
c64.FPRINTLN!()
c64.MOVFM!(#flt_1)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_FONE)
c64.FPRINTLN!()
c64.MOVFM!(#flt_half_sqr2)
c64.FPRINTLN!()
c64.MOVFM!( # c64.FL_SQRHLF)
c64.FPRINTLN!()
c64.MOVFM!(#flt_sqr2)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_SQRTWO)
c64.FPRINTLN!()
c64.MOVFM!(#flt_minus_half)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_NEGHLF)
c64.FPRINTLN!()
c64.MOVFM!(#flt_log_2)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_LOG2)
c64.FPRINTLN!()
c64.MOVFM!(#flt_10)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_TENC)
c64.FPRINTLN!()
c64.MOVFM!(#flt_1e9)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_NZMIL)
c64.FPRINTLN!()
c64.MOVFM!(#flt_half)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_FHALF)
c64.FPRINTLN!()
c64.MOVFM!(#flt_one_over_log_2)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_LOGEB2)
c64.FPRINTLN!()
c64.MOVFM!(#flt_half_pi)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_PIHALF)
c64.FPRINTLN!()
c64.MOVFM!(#flt_double_pi)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_TWOPI)
c64.FPRINTLN!()
c64.MOVFM!(# flt_point25)
c64.FPRINTLN!()
c64.MOVFM!(#c64.FL_FR4)
c64.FPRINTLN!()
return
}

BIN
testsource/included.binary Normal file

Binary file not shown.

View File

@ -0,0 +1,2 @@
; this assembly code is included as-is
nop

158
testsource/source1.ill Normal file
View File

@ -0,0 +1,158 @@
; source IL file
; these are comments
output prg,sys ; create a c-64 program with basic SYS call to launch it
;clobberzp restore ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
~main $0a00
{
memory screen = $d021
memory border = $d020
memory cursor = 646
const cyan = 3
const .word redw = $2002
var uninitbyte1
var .byte uninitbyte2
var .word uninitword1
var .byte initbyte33 = 33
var .word initword9876 = $9876
var .array(10) bytes2 = $44
var .wordarray(10) words2 = $bbcc
start ;foo
X = 55
Y = $77
Y = X
Y = X
X = 66
screen = 0
screen = border = cursor = X = Y = A = X = Y = A = border = cursor = border = cursor = 66 ; multi-assign!
border = false
border = true
border = 0
border = 0
[$d020] = 0
screen = X
cursor = Y
[646] = Y
uninitbyte1 = 123
uninitword1 = 12345
uninitword1 = A
initbyte33 = 133
initword9876 = 9999
initword9876 = 1
initword9876 = Y
initbyte33 ++
initword9876 ++
Y = 0
Y = 1
Y = 2
Y = true
Y = false
A = Y = X = 0 ; multi assign!
; [646,Y] = [$d020,X]
_loop A ++
X ++
Y ++
[$d020] ++
A --
X --
Y--
[$d020]--
call fidget.subroutine
go _loop
return 155,2,%00000101 ; will end up in A, X, Y
}
~ block2 {
somelabel1222
return
var zp1
var zp2
var .word zpw1
var .word zpw2
var .array(100) some_array1 = $aa ; initialized with $aa bytes
const white = 1
const red = 2
const .word border2 = $d020
const .word screen2 = $d021
}
~ block3 {
somelabel1
asm {
nop
nop
nop
}
go somelabel1
go block2.somelabel1222
A=X=Y=A=X=Y=A=X=Y=99
[$d020]=[$d021]=[$d020]=[$d021]=55
A=1
X=1
[$d020]=1
[$aa]=1
[$bb]=1
Y=1
X=1
A=1
[$d021]=1
A=2
[$cc]=1
[$cd]=1
[$ce]=1
[$cf]=1
[$cf00]=1
[$cf01]=2
[$cf02]=1
Y=2
A=2
X=1
return
}
~ block4 {
A=1
A=2
A=3
A=4
A=5
X=8
X=9
Y=10
[$cc]=1
[$cc]=2
[$cc]=3
[$cc]=4
[$dd]=A
[$dd]=X
[$dd]=Y
return
}
~ fidget $1000
{
subroutine
; subroutine (A, X, Y)
;[border2] = red
;[screen2] = white
return $99
return ,$99
return ,,$99
}

71
testsource/source2.ill Normal file
View File

@ -0,0 +1,71 @@
; source IL file
; these are comments
output prg,sys ; create a c-64 program with basic SYS call to launch it
clobberzp restore ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
~main
{
memory borderb1 = $d020
memory .byte borderb2 = $d020
memory .word screencolors = $d020
memory .array($10) screenblock1 = $0400 ; 10 bytes
memory .array($0100) screenblock2 = $0500 ; 256 bytes
memory .array($0234) screenblock3 = $2000 ; some weird size
memory .wordarray($20) screenblock4 = $0600 ; 32 words
memory .matrix(40,25) charscreen = $0400
var .array($10) var1 = $aa
var .wordarray($10) var2 = $9988
var .array($10) var3 = $11
var var4
var .matrix(40,25) vcharscreen ; init with 0
var .matrix(40,25) vcharscreen2 = 111 ; init with 111
start
; set some colors
Y = 0
borderb1 = Y
X=Y=A = 0
X = 1
screencolors = X
borderb1 = 0
borderb2 = 1
screencolors = $0
screencolors = $0207
; fill block of memory with a single value (byte or word)
;screenblock1,* = A
;screenblock1,* = X
;screenblock1,* = Y
;screenblock1,* = 2
;screenblock1,* = $aaa
;screenblock2,* = A
;screenblock2,* = X
;screenblock2,* = Y
;screenblock2,* = 2
;screenblock2,* = $aaa
;screenblock3,* = A
;screenblock3,* = X
;screenblock3,* = Y
;screenblock3,* = 2
;screenblock3,* = $aaa
;screenblock4,* = A
;screenblock4,* = X
;screenblock4,* = Y
;screenblock4,* = 2
;screenblock4,* = $aaa
; swap bytes
;A = var1[0]
;X = var1[1]
;var1[0] = X
;var1[1] = A
; swap words
;A = var2[0] ; must crash, cannot put word in register
;X = var2[1] ;crash
;var2[0] = X ; ok, 00-padded
;var2[1] = A ; ok, 00-padded
return
}

86
testsource/source3.ill Normal file
View File

@ -0,0 +1,86 @@
; source IL file
; these are comments
output prg,sys ; create a c-64 program with basic SYS call to launch it
; clobberzp restore ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
import "c64lib" ; searched in several locations and with .ill file extension added
~ main
{
memory screen2 = $0401
memory screen3 = $0402
memory .word screenw = $0500
; ascii to petscii, 0 terminated
var .text hello = "hello everyone out there."
; ascii to petscii, with length as first byte
var .ptext hellopascalstr = "Hello!\0x00\x01\x02d ag\0x01."
; ascii to screen codes, 0 terminated
var .stext hello_screen = "Hello!\n guys123."
; ascii to screen codes, length as first byte
var .pstext hellopascal_screen = "Hello! \n."
var .text hello2 = "@@\f\b\n\r\t@@"
start
call global2.make_screen_black
A='?'
[$d020] = '?'
[$d021] = '?'
[$d022] = '?'
[$d023] = 'q'
c64.BGCOL0 = 'a'
screen2 = 'a'
screen3 = 'a'
screenw = '2'
A='?'
X='?'
Y='?'
A='\002'
X=A
A='\xf2'
X=A
A='A'
call c64.CHROUT ;(A)
call c64.CHROUT ;(char=66)
A='\f'
X=A
A='\b'
X=A
A='\n'
X=A
A='\r'
X=A
A='\t'
X=A
call c64.CHROUT ;(foo=A)
A='0'
call c64.CHROUT ;('0')
A='1'
call c64.CHROUT ;(49)
A='2'
call c64.CHROUT
XY = hello
call c64util.print_string
A='!'
go c64.CHROUT
return
}
~ global2 {
make_screen_black
c64.EXTCOL = c64.BGCOL0 = 0
c64.COLOR = 3
Y = true
return
}

85
testsource/source4.ill Normal file
View File

@ -0,0 +1,85 @@
output prg,sys ; create a c-64 program with basic SYS call to launch it
import "c64lib.ill"
~ main
{
var .text greeting = "hello world!\r12345678 is a big number.\r"
var .ptext p_greeting = "hello world!\r12345678 is a big number.\r"
const .word BORDER = $d020
start
fcall c64util.print_pimmediate ; this prints the pstring immediately following it
asm {
.ptext "hello-pimmediate!{cr}"
}
A = 19
fcall c64util.print_byte_decimal0
A = 13
fcall c64.CHROUT
A = 19
fcall c64util.print_byte_decimal
A = 13
fcall c64.CHROUT
X = $01
Y = $02
fcall c64util.print_word_decimal0
A = 13
fcall c64.CHROUT
X = $01
Y = $02
fcall c64util.print_word_decimal
A = 13
fcall c64.CHROUT
return
start2
call global2.make_screen_black
call c64.CLEARSCR
XY = greeting
call c64util.print_string
XY = p_greeting
call c64util.print_pstring
A = 0
call c64util.print_byte_decimal
A = 0
call c64util.print_byte_hex
A = 13
call c64.CHROUT
call c64util.print_byte_decimal
A = 13
call c64util.print_byte_hex
A = 13
call c64.CHROUT
A = 255
call c64util.print_byte_decimal
A = 254
call c64util.print_byte_hex
A = 129
call c64util.print_byte_hex
A = 13
call c64.CHROUT
A = 13
call c64.CHROUT
X = 1
Y = 0
call c64util.print_word_decimal
A = 13
call c64.CHROUT
return
}
~ global2 {
make_screen_black
c64.EXTCOL = c64.BGCOL0 = 0
c64.COLOR = 3
return
}

24
testsource/source5.ill Normal file
View File

@ -0,0 +1,24 @@
output prg
address $c000
~ test {
var .byte localvar = 33
return
}
~ main {
start
A=0
[$d020]=A
X=false
Y=true
return
;included_assembly
asminclude "included.source" test_include
;included_binary
asmbinary "included.binary"
asmbinary "included.binary" $40
asmbinary "included.binary" $40 $200
}