mirror of
https://github.com/irmen/prog8.git
synced 2025-08-18 19:33:18 +00:00
removed most old stuff
This commit is contained in:
@@ -16,7 +16,7 @@ from .plyparse import parse_file, ParseError, Module, Directive, Block, Subrouti
|
|||||||
SymbolName, Dereference, AddressOf
|
SymbolName, Dereference, AddressOf
|
||||||
from .plylex import SourceRef, print_bold
|
from .plylex import SourceRef, print_bold
|
||||||
from .optimize import optimize
|
from .optimize import optimize
|
||||||
from .datatypes import DataType, STRING_DATATYPES
|
from .datatypes import DataType, VarType, STRING_DATATYPES
|
||||||
|
|
||||||
|
|
||||||
class CompileError(Exception):
|
class CompileError(Exception):
|
||||||
@@ -97,7 +97,7 @@ class PlyParser:
|
|||||||
for block, parent in module.all_scopes():
|
for block, parent in module.all_scopes():
|
||||||
parentname = (parent.name + ".") if parent else ""
|
parentname = (parent.name + ".") if parent else ""
|
||||||
blockname = parentname + block.name
|
blockname = parentname + block.name
|
||||||
if blockname in encountered_blocks:
|
if blockname in encountered_blocks:
|
||||||
raise ValueError("block names not unique:", blockname)
|
raise ValueError("block names not unique:", blockname)
|
||||||
encountered_blocks.add(blockname)
|
encountered_blocks.add(blockname)
|
||||||
for node in block.nodes:
|
for node in block.nodes:
|
||||||
@@ -429,6 +429,7 @@ class Zeropage:
|
|||||||
|
|
||||||
def allocate(self, vardef: VarDef) -> int:
|
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 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:
|
def sequential_free(location: int) -> bool:
|
||||||
return all(location + i in self.free for i in range(size))
|
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")
|
self.p("_il65_init_block\v; (re)set vars to initial values")
|
||||||
float_inits = {}
|
float_inits = {}
|
||||||
string_inits = []
|
string_inits = [] # type: List[VarDef]
|
||||||
prev_value_a, prev_value_x = None, None
|
prev_value_a, prev_value_x = None, None
|
||||||
vars_by_datatype = defaultdict(list) # type: Dict[DataType, List[VarDef]]
|
vars_by_datatype = defaultdict(list) # type: Dict[DataType, List[VarDef]]
|
||||||
for vardef in block.scope.filter_nodes(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))
|
self.p("\t\tsta {}+1".format(assign_target))
|
||||||
else:
|
else:
|
||||||
raise CodeError("cannot assign immediate string, it must be a string variable")
|
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
|
import pytest
|
||||||
from il65.compile import Zeropage, CompileError
|
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
|
from il65.datatypes import DataType
|
||||||
|
|
||||||
|
|
||||||
def test_zp_names():
|
def test_zp_names():
|
||||||
|
sref = SourceRef("test", 1, 1)
|
||||||
zp = Zeropage(ZpOptions.NOCLOBBER)
|
zp = Zeropage(ZpOptions.NOCLOBBER)
|
||||||
zp.allocate("", DataType.BYTE)
|
|
||||||
zp.allocate("", DataType.BYTE)
|
|
||||||
zp.allocate("varname", DataType.BYTE)
|
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(AssertionError):
|
||||||
zp.allocate("varname", DataType.BYTE)
|
zp.allocate(VarDef(name="", vartype="memory", datatype=DataType.BYTE, sourceref=sref))
|
||||||
zp.allocate("varname2", DataType.BYTE)
|
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():
|
def test_zp_noclobber_allocation():
|
||||||
|
sref = SourceRef("test", 1, 1)
|
||||||
zp = Zeropage(ZpOptions.NOCLOBBER)
|
zp = Zeropage(ZpOptions.NOCLOBBER)
|
||||||
assert zp.available() == 9
|
assert zp.available() == 9
|
||||||
with pytest.raises(CompileError):
|
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()):
|
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 loc > 0
|
||||||
assert zp.available() == 0
|
assert zp.available() == 0
|
||||||
with pytest.raises(CompileError):
|
with pytest.raises(CompileError):
|
||||||
zp.allocate("", DataType.BYTE)
|
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||||
with pytest.raises(CompileError):
|
with pytest.raises(CompileError):
|
||||||
zp.allocate("", DataType.WORD)
|
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||||
|
|
||||||
|
|
||||||
def test_zp_clobber_allocation():
|
def test_zp_clobber_allocation():
|
||||||
|
sref = SourceRef("test", 1, 1)
|
||||||
zp = Zeropage(ZpOptions.CLOBBER)
|
zp = Zeropage(ZpOptions.CLOBBER)
|
||||||
assert zp.available() == 239
|
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
|
assert loc > 3 and loc not in zp.free
|
||||||
num, rest = divmod(zp.available(), 5)
|
num, rest = divmod(zp.available(), 5)
|
||||||
for _ in range(num-3):
|
for _ in range(num-3):
|
||||||
zp.allocate("", DataType.FLOAT)
|
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
|
||||||
assert zp.available() == 19
|
assert zp.available() == 19
|
||||||
with pytest.raises(CompileError):
|
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):
|
for _ in range(14):
|
||||||
zp.allocate("", DataType.BYTE)
|
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||||
zp.allocate("", DataType.WORD)
|
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||||
zp.allocate("", DataType.WORD)
|
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||||
with pytest.raises(CompileError):
|
with pytest.raises(CompileError):
|
||||||
zp.allocate("", DataType.WORD)
|
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||||
assert zp.available() == 1
|
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):
|
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():
|
def test_zp_efficient_allocation():
|
||||||
# free = [0x04, 0x05, 0x06, 0x2a, 0x52, 0xf7, 0xf8, 0xf9, 0xfa]
|
# free = [0x04, 0x05, 0x06, 0x2a, 0x52, 0xf7, 0xf8, 0xf9, 0xfa]
|
||||||
|
sref = SourceRef("test", 1, 1)
|
||||||
zp = Zeropage(ZpOptions.NOCLOBBER)
|
zp = Zeropage(ZpOptions.NOCLOBBER)
|
||||||
assert zp.available() == 9
|
assert zp.available() == 9
|
||||||
assert 0x2a == zp.allocate("", DataType.BYTE)
|
assert 0x2a == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||||
assert 0x52 == zp.allocate("", DataType.BYTE)
|
assert 0x52 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||||
assert 0x04 == zp.allocate("", DataType.WORD)
|
assert 0x04 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||||
assert 0xf7 == zp.allocate("", DataType.WORD)
|
assert 0xf7 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||||
assert 0x06 == zp.allocate("", DataType.BYTE)
|
assert 0x06 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||||
assert 0xf9 == zp.allocate("", DataType.WORD)
|
assert 0xf9 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||||
assert zp.available() == 0
|
assert zp.available() == 0
|
||||||
|
Reference in New Issue
Block a user