removed most old stuff

This commit is contained in:
Irmen de Jong 2018-01-13 16:09:23 +01:00
parent 7218c17689
commit 1990f75e98
11 changed files with 38 additions and 3237 deletions

View File

@ -16,7 +16,7 @@ from .plyparse import parse_file, ParseError, Module, Directive, Block, Subrouti
SymbolName, Dereference, AddressOf
from .plylex import SourceRef, print_bold
from .optimize import optimize
from .datatypes import DataType, STRING_DATATYPES
from .datatypes import DataType, VarType, STRING_DATATYPES
class CompileError(Exception):
@ -97,7 +97,7 @@ class PlyParser:
for block, parent in module.all_scopes():
parentname = (parent.name + ".") if parent else ""
blockname = parentname + block.name
if blockname in encountered_blocks:
if blockname in encountered_blocks:
raise ValueError("block names not unique:", blockname)
encountered_blocks.add(blockname)
for node in block.nodes:
@ -429,6 +429,7 @@ class Zeropage:
def allocate(self, vardef: VarDef) -> int:
assert not vardef.name or vardef.name not in {a[0] for a in self.allocations.values()}, "var name is not unique"
assert vardef.vartype == VarType.VAR, "can only allocate var"
def sequential_free(location: int) -> bool:
return all(location + i in self.free for i in range(size))

View File

@ -245,7 +245,7 @@ class AssemblyGenerator:
self.p("_il65_init_block\v; (re)set vars to initial values")
float_inits = {}
string_inits = []
string_inits = [] # type: List[VarDef]
prev_value_a, prev_value_x = None, None
vars_by_datatype = defaultdict(list) # type: Dict[DataType, List[VarDef]]
for vardef in block.scope.filter_nodes(VarDef):

View File

@ -1 +0,0 @@
# package

View File

@ -1,284 +0,0 @@
"""
Programming Language for 6502/6510 microprocessors
This is the expression parser/evaluator.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
import ast
import attr
from typing import Union, Optional, List, Tuple, Any
from .symbols import FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE, SourceRef, SymbolTable, SymbolError, PrimitiveType
class ParseError(Exception):
def __init__(self, message: str, sourcetext: Optional[str], sourceref: SourceRef) -> None:
self.sourceref = sourceref
self.msg = message
self.sourcetext = sourcetext
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_arguments(text: str, sourceref: SourceRef) -> List[Tuple[str, PrimitiveType]]:
src = SourceLine(text, sourceref)
text = src.preprocess()
try:
nodes = ast.parse("__func({:s})".format(text), sourceref.file, "eval")
except SyntaxError as x:
raise src.to_error(str(x))
args = [] # type: List[Tuple[str, Any]]
if isinstance(nodes, ast.Expression):
for arg in nodes.body.args:
reprvalue = astnode_to_repr(arg)
args.append((None, reprvalue))
for kwarg in nodes.body.keywords:
reprvalue = astnode_to_repr(kwarg.value)
args.append((kwarg.arg, reprvalue))
return args
else:
raise TypeError("ast.Expression expected")
def parse_expr_as_comparison(text: str, sourceref: SourceRef) -> Tuple[str, str, str]:
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 not isinstance(node, ast.Expression):
raise TypeError("ast.Expression expected")
if isinstance(node.body, ast.Compare):
if len(node.body.ops) != 1:
raise src.to_error("only one comparison operator at a time is supported")
operator = {
"Eq": "==",
"NotEq": "!=",
"Lt": "<",
"LtE": "<=",
"Gt": ">",
"GtE": ">=",
"Is": None,
"IsNot": None,
"In": None,
"NotIn": None
}[node.body.ops[0].__class__.__name__]
if not operator:
raise src.to_error("unsupported comparison operator")
left = text[node.body.left.col_offset:node.body.comparators[0].col_offset].rstrip()[:-len(operator)]
right = text[node.body.comparators[0].col_offset:]
return left.strip(), operator, right.strip()
left = astnode_to_repr(node.body)
return left, "", ""
def parse_expr_as_int(text: str, context: Optional[SymbolTable], ppcontext: Optional[SymbolTable], sourceref: SourceRef, *,
minimum: int=0, maximum: int=0xffff) -> int:
result = parse_expr_as_primitive(text, context, ppcontext, 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], ppcontext: Optional[SymbolTable], sourceref: SourceRef, *,
minimum: float=FLOAT_MAX_NEGATIVE, maximum: float=FLOAT_MAX_POSITIVE) -> Union[int, float]:
result = parse_expr_as_primitive(text, context, ppcontext, 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], ppcontext: Optional[SymbolTable], sourceref: SourceRef) -> str:
result = parse_expr_as_primitive(text, context, ppcontext, 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], ppcontext: 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, ppcontext).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__)
class EvaluatingTransformer(ast.NodeTransformer):
def __init__(self, src: SourceLine, context: SymbolTable, ppcontext: SymbolTable) -> None:
super().__init__()
self.src = src
self.context = context
self.ppcontext = ppcontext
def error(self, message: str, column: int=0) -> ParseError:
ref = attr.evolve(self.src.sourceref, column=column)
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(self.ppcontext)
else:
globals = {"__builtins__": {}}
locals = None
try:
result = eval(code, globals, locals) # XXX unsafe...
except Exception as x:
raise self.src.to_error(str(x)) from 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)
if isinstance(node.op, ast.Invert):
if isinstance(node.operand, ast.Num):
node = self.generic_visit(node)
return ast.copy_location(ast.Num(~node.operand.n), node)
else:
raise self.error("can only bitwise invert a number")
raise self.error("expected unary + or - or ~")
elif isinstance(node.operand, ast.UnaryOp):
# nested unary ops, for instance: "~-2" = invert(minus(2))
node = self.generic_visit(node)
return self.visit_UnaryOp(node)
else:
print(node.operand)
raise self.error("expected constant 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
def astnode_to_repr(node: ast.AST) -> str:
if isinstance(node, ast.Name):
return node.id
if isinstance(node, ast.Num):
return repr(node.n)
if isinstance(node, ast.Str):
return repr(node.s)
if isinstance(node, ast.BinOp):
if node.left.id == "__ptr" and isinstance(node.op, ast.MatMult): # type: ignore
return '&' + astnode_to_repr(node.right)
else:
print("error", ast.dump(node))
raise TypeError("invalid arg ast node type", node)
if isinstance(node, ast.Attribute):
return astnode_to_repr(node.value) + "." + node.attr
if isinstance(node, ast.UnaryOp):
if isinstance(node.op, ast.USub):
return "-" + astnode_to_repr(node.operand)
if isinstance(node.op, ast.UAdd):
return "+" + astnode_to_repr(node.operand)
if isinstance(node.op, ast.Invert):
return "~" + astnode_to_repr(node.operand)
if isinstance(node.op, ast.Not):
return "not " + astnode_to_repr(node.operand)
if isinstance(node, ast.List):
# indirect values get turned into a list...
return "[" + ",".join(astnode_to_repr(elt) for elt in node.elts) + "]"
raise TypeError("invalid arg ast node type", node)

View File

@ -1,154 +0,0 @@
"""
Programming Language for 6502/6510 microprocessors
This is the code to optimize the parse tree.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
from typing import List
from .parse import ParseResult
from .symbols import Block, AugmentedAssignmentStmt, IntegerValue, FloatValue, AssignmentStmt, CallStmt, \
Value, MemMappedValue, RegisterValue, AstNode
class Optimizer:
def __init__(self, parseresult: ParseResult) -> None:
self.parsed = parseresult
def optimize(self) -> ParseResult:
print("\noptimizing parse tree")
for block in self.parsed.all_blocks():
self.remove_augmentedassign_incrdecr_nops(block)
self.remove_identity_assigns(block)
self.combine_assignments_into_multi(block)
self.optimize_multiassigns(block)
self.remove_unused_subroutines(block)
self.optimize_compare_with_zero(block)
return self.parsed
def remove_augmentedassign_incrdecr_nops(self, block: Block) -> None:
have_removed_stmts = False
for index, stmt in enumerate(list(block.statements)):
if isinstance(stmt, AugmentedAssignmentStmt):
if isinstance(stmt.right, (IntegerValue, FloatValue)):
if stmt.right.value == 0 and stmt.operator in ("+=", "-=", "|=", "<<=", ">>=", "^="):
print("{}: removed statement that has no effect".format(stmt.sourceref))
have_removed_stmts = True
block.statements[index] = None
if stmt.right.value >= 8 and stmt.operator in ("<<=", ">>="):
print("{}: shifting that many times always results in zero".format(stmt.sourceref))
new_stmt = AssignmentStmt(stmt.leftvalues, IntegerValue(0, stmt.sourceref), stmt.sourceref)
block.statements[index] = new_stmt
if have_removed_stmts:
# remove the Nones
block.statements = [s for s in block.statements if s is not None]
def optimize_compare_with_zero(self, block: Block) -> None:
# a conditional goto that compares a value to zero will be simplified
# the comparison operator and rvalue (0) will be removed and the if-status changed accordingly
for stmt in block.statements:
if isinstance(stmt, CallStmt):
cond = stmt.condition
if cond and isinstance(cond.rvalue, (IntegerValue, FloatValue)) and cond.rvalue.value == 0:
simplified = False
if cond.ifstatus in ("true", "ne"):
if cond.comparison_op == "==":
# if_true something == 0 -> if_not something
cond.ifstatus = "not"
cond.comparison_op, cond.rvalue = "", None
simplified = True
elif cond.comparison_op == "!=":
# if_true something != 0 -> if_true something
cond.comparison_op, cond.rvalue = "", None
simplified = True
elif cond.ifstatus in ("not", "eq"):
if cond.comparison_op == "==":
# if_not something == 0 -> if_true something
cond.ifstatus = "true"
cond.comparison_op, cond.rvalue = "", None
simplified = True
elif cond.comparison_op == "!=":
# if_not something != 0 -> if_not something
cond.comparison_op, cond.rvalue = "", None
simplified = True
if simplified:
print("{}: simplified comparison with zero".format(stmt.sourceref))
def combine_assignments_into_multi(self, block: Block) -> None:
# fold multiple consecutive assignments with the same rvalue into one multi-assignment
statements = [] # type: List[AstNode]
multi_assign_statement = None
for stmt in block.statements:
if isinstance(stmt, AssignmentStmt) and not isinstance(stmt, AugmentedAssignmentStmt):
if multi_assign_statement and multi_assign_statement.right == stmt.right:
multi_assign_statement.leftvalues.extend(stmt.leftvalues)
print("{}: joined with previous line into multi-assign statement".format(stmt.sourceref))
else:
if multi_assign_statement:
statements.append(multi_assign_statement)
multi_assign_statement = stmt
else:
if multi_assign_statement:
statements.append(multi_assign_statement)
multi_assign_statement = None
statements.append(stmt)
if multi_assign_statement:
statements.append(multi_assign_statement)
block.statements = statements
def optimize_multiassigns(self, block: Block) -> None:
# optimize multi-assign statements.
for stmt in block.statements:
if isinstance(stmt, AssignmentStmt) and len(stmt.leftvalues) > 1:
# remove duplicates
lvalues = list(set(stmt.leftvalues))
if len(lvalues) != len(stmt.leftvalues):
print("{}: removed duplicate assignment targets".format(stmt.sourceref))
# change order: first registers, then zp addresses, then non-zp addresses, then the rest (if any)
stmt.leftvalues = list(sorted(lvalues, key=_value_sortkey))
def remove_identity_assigns(self, block: Block) -> None:
have_removed_stmts = False
for index, stmt in enumerate(list(block.statements)):
if isinstance(stmt, AssignmentStmt):
stmt.remove_identity_lvalues()
if not stmt.leftvalues:
print("{}: removed identity assignment statement".format(stmt.sourceref))
have_removed_stmts = True
block.statements[index] = None
if have_removed_stmts:
# remove the Nones
block.statements = [s for s in block.statements if s is not None]
def remove_unused_subroutines(self, block: Block) -> None:
# some symbols are used by the emitted assembly code from the code generator,
# and should never be removed or the assembler will fail
never_remove = {"c64.FREADUY", "c64.FTOMEMXY", "c64.FADD", "c64.FSUB",
"c64flt.GIVUAYF", "c64flt.copy_mflt", "c64flt.float_add_one", "c64flt.float_sub_one",
"c64flt.float_add_SW1_to_XY", "c64flt.float_sub_SW1_from_XY"}
discarded = []
for sub in list(block.symbols.iter_subroutines()):
usages = self.parsed.subroutine_usage[(sub.blockname, sub.name)]
if not usages and sub.blockname + '.' + sub.name not in never_remove:
block.symbols.remove_node(sub.name)
discarded.append(sub.name)
if discarded:
print("{}: discarded {:d} unused subroutines from block '{:s}'".format(block.sourceref, len(discarded), block.name))
def _value_sortkey(value: Value) -> int:
if isinstance(value, RegisterValue):
num = 0
for char in value.register:
num *= 100
num += ord(char)
return num
elif isinstance(value, MemMappedValue):
if value.address is None:
return 99999999
if value.address < 0x100:
return 10000 + value.address
else:
return 20000 + value.address
else:
return 99999999

File diff suppressed because it is too large Load Diff

View File

@ -1,68 +0,0 @@
"""
Programming 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
"""
from typing import List, Tuple, Set
from .parse import Parser, ParseResult, SymbolTable, SymbolDefinition
from .symbols import SourceRef, AstNode, InlineAsm
class PreprocessingParser(Parser):
def __init__(self, filename: str, existing_imports: Set[str], parsing_import: bool=False) -> None:
super().__init__(filename, "", existing_imports=existing_imports, parsing_import=parsing_import)
self.print_block_parsing = False
def preprocess(self) -> Tuple[List[Tuple[int, str]], SymbolTable]:
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 print_warning(self, text: str, sourceref: SourceRef=None) -> None:
pass
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("preprocessing", self.sourceref.file)
self._parse_1()
return self.result
def parse_asminclude(self, line: str) -> InlineAsm:
return InlineAsm([], self.sourceref)
def parse_statement(self, line: str) -> AstNode:
return None
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, labelname: str, rest: str) -> None:
super().parse_label(labelname, rest)
def parse_subroutine_def(self, line: str) -> None:
super().parse_subroutine_def(line)
def create_import_parser(self, filename: str, outputdir: str) -> Parser:
return PreprocessingParser(filename, parsing_import=True, existing_imports=self.existing_imports)
def print_import_progress(self, message: str, *args: str) -> None:
pass

File diff suppressed because it is too large Load Diff

View File

@ -1,65 +0,0 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the main program that drives the rest.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
import time
import os
import argparse
import subprocess
from .handwritten.parse import Parser
from .handwritten.optimize import Optimizer
from .handwritten.preprocess import PreprocessingParser
from .handwritten.codegen import CodeGenerator, Assembler64Tass
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("-no", "--nooptimize", action="store_true", help="do not optimize the parse tree")
ap.add_argument("-sv", "--startvice", action="store_true", help="autostart vice x64 emulator after compilation")
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)
start = time.perf_counter()
pp = PreprocessingParser(args.sourcefile, set())
sourcelines, symbols = pp.preprocess()
# symbols.print_table()
p = Parser(args.sourcefile, args.output, set(), sourcelines=sourcelines, ppsymbols=symbols, sub_usage=pp.result.subroutine_usage)
parsed = p.parse()
if parsed:
if args.nooptimize:
p.print_bold("not optimizing the parse tree!")
else:
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)
mon_command_file = assembler.generate_breakpoint_list(program_filename)
duration_total = time.perf_counter() - start
print("Compile duration: {:.2f} seconds".format(duration_total))
p.print_bold("Output file: " + program_filename)
print()
if args.startvice:
print("Autostart vice emulator...")
cmdline = ["x64", "-remotemonitor", "-moncommands", mon_command_file,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", program_filename]
with open(os.devnull, "wb") as shutup:
subprocess.call(cmdline, stdout=shutup)

View File

@ -1869,79 +1869,3 @@ class CodeGenerator:
self.p("\t\tsta {}+1".format(assign_target))
else:
raise CodeError("cannot assign immediate string, it must 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 += "{cr}"
elif char == '\r':
result += "{down}"
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", "--vice-labels", "-l", outputfilename+".vice-mon-list",
"-L", outputfilename+".final-asm", "--no-monitor", "--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")
try:
subprocess.check_call(args)
except FileNotFoundError as x:
raise SystemExit("ERROR: cannot run assembler program: "+str(x))
except subprocess.CalledProcessError as x:
raise SystemExit("assembler failed with returncode " + str(x.returncode))
def generate_breakpoint_list(self, program_filename: str) -> str:
breakpoints = []
with open(program_filename + ".final-asm", "rU") as f:
for line in f:
match = re.fullmatch(CodeGenerator.BREAKPOINT_COMMENT_DETECTOR, line, re.DOTALL)
if match:
breakpoints.append("$" + match.group("address"))
cmdfile = program_filename + ".vice-mon-list"
with open(cmdfile, "at") as f:
print("; vice monitor breakpoint list now follows", file=f)
print("; {:d} breakpoints have been defined here".format(len(breakpoints)), file=f)
print("del", file=f)
for b in breakpoints:
print("break", b, file=f)
return cmdfile

View File

@ -1,65 +1,74 @@
import pytest
from il65.compile import Zeropage, CompileError
from il65.plyparse import ZpOptions
from il65.plyparse import ZpOptions, VarDef
from il65.plylex import SourceRef
from il65.datatypes import DataType
def test_zp_names():
sref = SourceRef("test", 1, 1)
zp = Zeropage(ZpOptions.NOCLOBBER)
zp.allocate("", DataType.BYTE)
zp.allocate("", DataType.BYTE)
zp.allocate("varname", DataType.BYTE)
with pytest.raises(AssertionError):
zp.allocate("varname", DataType.BYTE)
zp.allocate("varname2", DataType.BYTE)
zp.allocate(VarDef(name="", vartype="memory", datatype=DataType.BYTE, sourceref=sref))
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
zp.allocate(VarDef(name="varname", vartype="var", datatype=DataType.BYTE, sourceref=sref))
with pytest.raises(AssertionError):
zp.allocate(VarDef(name="varname", vartype="var", datatype=DataType.BYTE, sourceref=sref))
zp.allocate(VarDef(name="varname2", vartype="var", datatype=DataType.BYTE, sourceref=sref))
def test_zp_noclobber_allocation():
sref = SourceRef("test", 1, 1)
zp = Zeropage(ZpOptions.NOCLOBBER)
assert zp.available() == 9
with pytest.raises(CompileError):
zp.allocate("impossible", DataType.FLOAT) # in regular zp there aren't 5 sequential bytes free
# in regular zp there aren't 5 sequential bytes free
zp.allocate(VarDef(name="impossible", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
for i in range(zp.available()):
loc = zp.allocate("bytevar"+str(i), DataType.BYTE)
loc = zp.allocate(VarDef(name="bvar"+str(i), vartype="var", datatype=DataType.BYTE, sourceref=sref))
assert loc > 0
assert zp.available() == 0
with pytest.raises(CompileError):
zp.allocate("", DataType.BYTE)
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
with pytest.raises(CompileError):
zp.allocate("", DataType.WORD)
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
def test_zp_clobber_allocation():
sref = SourceRef("test", 1, 1)
zp = Zeropage(ZpOptions.CLOBBER)
assert zp.available() == 239
loc = zp.allocate("", DataType.FLOAT)
loc = zp.allocate(VarDef(name="", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
assert loc > 3 and loc not in zp.free
num, rest = divmod(zp.available(), 5)
for _ in range(num-3):
zp.allocate("", DataType.FLOAT)
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
assert zp.available() == 19
with pytest.raises(CompileError):
zp.allocate("", DataType.FLOAT) # can't allocate because no more sequential bytes, only fragmented
# can't allocate because no more sequential bytes, only fragmented
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
for _ in range(14):
zp.allocate("", DataType.BYTE)
zp.allocate("", DataType.WORD)
zp.allocate("", DataType.WORD)
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
with pytest.raises(CompileError):
zp.allocate("", DataType.WORD)
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
assert zp.available() == 1
zp.allocate("last", DataType.BYTE)
zp.allocate(VarDef(name="last", vartype="var", datatype=DataType.BYTE, sourceref=sref))
with pytest.raises(CompileError):
zp.allocate("impossible", DataType.BYTE)
zp.allocate(VarDef(name="impossible", vartype="var", datatype=DataType.BYTE, sourceref=sref))
def test_zp_efficient_allocation():
# free = [0x04, 0x05, 0x06, 0x2a, 0x52, 0xf7, 0xf8, 0xf9, 0xfa]
sref = SourceRef("test", 1, 1)
zp = Zeropage(ZpOptions.NOCLOBBER)
assert zp.available() == 9
assert 0x2a == zp.allocate("", DataType.BYTE)
assert 0x52 == zp.allocate("", DataType.BYTE)
assert 0x04 == zp.allocate("", DataType.WORD)
assert 0xf7 == zp.allocate("", DataType.WORD)
assert 0x06 == zp.allocate("", DataType.BYTE)
assert 0xf9 == zp.allocate("", DataType.WORD)
assert 0x2a == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
assert 0x52 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
assert 0x04 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
assert 0xf7 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
assert 0x06 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
assert 0xf9 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
assert zp.available() == 0