mirror of
https://github.com/irmen/prog8.git
synced 2025-01-10 20:30:23 +00:00
initial
This commit is contained in:
parent
13e1d5c62c
commit
a228bcd8fc
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal 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
6
README.md
Normal 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
6
il65/__init__.py
Normal 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
9
il65/__main__.py
Normal 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
206
il65/astparse.py
Normal 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
858
il65/il65.py
Normal 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
1343
il65/parse.py
Normal file
File diff suppressed because it is too large
Load Diff
61
il65/preprocess.py
Normal file
61
il65/preprocess.py
Normal 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
638
il65/symbols.py
Normal 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
549
lib/c64lib.ill
Normal 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
4
mypy.ini
Normal file
@ -0,0 +1,4 @@
|
||||
[mypy]
|
||||
follow_imports = normal
|
||||
ignore_missing_imports = True
|
||||
incremental = True
|
386
reference.txt
Normal file
386
reference.txt
Normal 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
3
setup.cfg
Normal file
@ -0,0 +1,3 @@
|
||||
[pycodestyle]
|
||||
max-line-length = 140
|
||||
exclude = .git,__pycache__,.tox,docs,tests,build,dist
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
45
tests/test_floats.py
Normal file
45
tests/test_floats.py
Normal 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
63
testsource/calls.ill
Normal 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
242
testsource/dtypes.ill
Normal 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
140
testsource/floats.ill
Normal 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
BIN
testsource/included.binary
Normal file
Binary file not shown.
2
testsource/included.source
Normal file
2
testsource/included.source
Normal file
@ -0,0 +1,2 @@
|
||||
; this assembly code is included as-is
|
||||
nop
|
158
testsource/source1.ill
Normal file
158
testsource/source1.ill
Normal 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
71
testsource/source2.ill
Normal 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
86
testsource/source3.ill
Normal 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
85
testsource/source4.ill
Normal 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
24
testsource/source5.ill
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user