mirror of
https://github.com/irmen/prog8.git
synced 2025-01-11 13:29:45 +00:00
removed most old stuff
This commit is contained in:
parent
7218c17689
commit
1990f75e98
@ -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))
|
||||
|
@ -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):
|
||||
|
@ -1 +0,0 @@
|
||||
# package
|
@ -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)
|
@ -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
@ -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
@ -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)
|
@ -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
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user