prog8/il65/parse.py

1435 lines
72 KiB
Python
Raw Normal View History

2017-12-21 13:52:30 +00:00
"""
2017-12-25 15:00:25 +00:00
Programming Language for 6502/6510 microprocessors
2017-12-21 13:52:30 +00:00
This is the parser of the IL65 code, that generates a parse tree.
Written by Irmen de Jong (irmen@razorvine.net)
License: GNU GPL 3.0, see LICENSE
"""
import math
2017-12-21 13:52:30 +00:00
import re
import os
2017-12-26 00:30:22 +00:00
import sys
2017-12-21 13:52:30 +00:00
import shutil
import enum
2017-12-25 18:09:10 +00:00
from collections import defaultdict
2017-12-31 03:45:27 +00:00
from typing import Set, List, Tuple, Optional, Dict, Union, Generator
from .exprparse import ParseError, parse_expr_as_int, parse_expr_as_number, parse_expr_as_primitive,\
2017-12-27 18:01:14 +00:00
parse_expr_as_string, parse_arguments, parse_expr_as_comparison
2017-12-31 03:45:27 +00:00
from .astdefs import *
2017-12-21 13:52:30 +00:00
from .symbols import SourceRef, SymbolTable, DataType, SymbolDefinition, SubroutineDef, LabelDef, \
2017-12-31 03:45:27 +00:00
Zeropage, char_to_bytevalue, \
PrimitiveType, VariableDef, ConstantDef, SymbolError, STRING_DATATYPES, \
2017-12-28 03:20:59 +00:00
REGISTER_SYMBOLS, REGISTER_WORDS, REGISTER_BYTES, REGISTER_SBITS, RESERVED_NAMES
2017-12-21 13:52:30 +00:00
class ProgramFormat(enum.Enum):
PRG = "prg"
RAW = "raw"
class ParseResult:
def __init__(self, sourcefile: str) -> None:
self.format = ProgramFormat.RAW
self.with_sys = False
self.sourcefile = sourcefile
self.clobberzp = False
self.restorezp = False
self.start_address = 0
2017-12-31 03:45:27 +00:00
self.blocks = [] # type: List[Block]
2017-12-25 18:09:10 +00:00
self.subroutine_usage = defaultdict(set) # type: Dict[Tuple[str, str], Set[str]]
2017-12-29 02:52:26 +00:00
self.zeropage = Zeropage()
2017-12-21 13:52:30 +00:00
2017-12-31 03:45:27 +00:00
def all_blocks(self) -> Generator[Block, None, None]:
2017-12-28 22:03:59 +00:00
for block in self.blocks:
yield block
for sub in block.symbols.iter_subroutines(True):
yield sub.sub_block
2017-12-31 03:45:27 +00:00
def add_block(self, block: Block, position: Optional[int]=None) -> None:
2017-12-31 03:10:27 +00:00
if position is not None:
self.blocks.insert(position, block)
else:
self.blocks.append(block)
def merge(self, parsed: 'ParseResult') -> None:
existing_blocknames = set(block.name for block in self.blocks)
other_blocknames = set(block.name for block in parsed.blocks)
overlap = existing_blocknames & other_blocknames
if overlap != {"<header>"}:
raise SymbolError("double block names: {}".format(overlap))
for block in parsed.blocks:
if block.name != "<header>":
self.blocks.append(block)
2017-12-31 03:45:27 +00:00
def find_block(self, name: str) -> Block:
2017-12-31 03:10:27 +00:00
for block in self.blocks:
if block.name == name:
return block
raise KeyError("block not found: " + name)
def sub_used_by(self, sub: SubroutineDef, sourceref: SourceRef) -> None:
self.subroutine_usage[(sub.blockname, sub.name)].add(str(sourceref))
2017-12-21 13:52:30 +00:00
class Parser:
2017-12-30 12:34:52 +00:00
def __init__(self, filename: str, outputdir: str, existing_imports: Set[str], parsing_import: bool = False,
sourcelines: List[Tuple[int, str]] = None, ppsymbols: SymbolTable = None, sub_usage: Dict=None) -> None:
2017-12-21 13:52:30 +00:00
self.result = ParseResult(filename)
2017-12-25 18:09:10 +00:00
if sub_usage is not None:
# re-use the (global) subroutine usage tracking
self.result.subroutine_usage = sub_usage
2017-12-21 13:52:30 +00:00
self.sourceref = SourceRef(filename, -1, 0)
if sourcelines:
self.lines = sourcelines
else:
self.lines = self.load_source(filename)
self.outputdir = outputdir
self.parsing_import = parsing_import # are we parsing a import file?
2017-12-25 12:20:23 +00:00
self._cur_lineidx = -1 # used to efficiently go to next/previous line in source
2017-12-31 03:45:27 +00:00
self.cur_block = None # type: Block
2017-12-21 13:52:30 +00:00
self.root_scope = SymbolTable("<root>", None, None)
2017-12-29 02:52:26 +00:00
self.root_scope.set_zeropage(self.result.zeropage)
self.ppsymbols = ppsymbols # symboltable from preprocess phase
self.print_block_parsing = True
2017-12-30 12:34:52 +00:00
self.existing_imports = existing_imports
2018-01-01 04:49:12 +00:00
self.parse_errors = 0
2017-12-21 13:52:30 +00:00
def load_source(self, filename: str) -> List[Tuple[int, str]]:
with open(filename, "rU") as source:
sourcelines = source.readlines()
2017-12-21 22:05:35 +00:00
# store all lines that aren't empty
2017-12-23 13:36:23 +00:00
# comments are kept (end-of-line comments are stripped though)
2017-12-21 13:52:30 +00:00
lines = []
for num, line in enumerate(sourcelines, start=1):
2017-12-23 13:36:23 +00:00
line = line.rstrip()
if line.lstrip().startswith(';'):
lines.append((num, line.lstrip()))
else:
line2, sep, comment = line.rpartition(';')
if sep:
line = line2.rstrip()
if line:
lines.append((num, line))
2017-12-21 13:52:30 +00:00
return lines
def parse(self) -> Optional[ParseResult]:
# start the parsing
try:
2018-01-01 04:49:12 +00:00
result = self.parse_file()
2017-12-21 13:52:30 +00:00
except ParseError as x:
2018-01-01 04:49:12 +00:00
self.handle_parse_error(x)
2017-12-21 13:52:30 +00:00
except Exception as x:
2017-12-26 00:30:22 +00:00
if sys.stderr.isatty():
print("\x1b[1m", file=sys.stderr)
print("\nERROR: internal parser error: ", x, file=sys.stderr)
2017-12-28 22:03:59 +00:00
if self.cur_block:
print(" file:", self.sourceref.file, "block:", self.cur_block.name, "line:", self.sourceref.line, file=sys.stderr)
else:
print(" file:", self.sourceref.file, file=sys.stderr)
2017-12-26 00:30:22 +00:00
if sys.stderr.isatty():
2018-01-01 04:49:12 +00:00
print("\x1b[0m", file=sys.stderr, end="", flush=True)
raise
if self.parse_errors:
self.print_bold("\nNo output; there were {:d} errors in file {:s}\n".format(self.parse_errors, self.sourceref.file))
raise SystemExit(1)
return result
def handle_parse_error(self, exc: ParseError) -> None:
self.parse_errors += 1
if sys.stderr.isatty():
print("\x1b[1m", file=sys.stderr)
if exc.sourcetext:
print("\t" + exc.sourcetext, file=sys.stderr)
if exc.sourceref.column:
print("\t" + ' ' * exc.sourceref.column + ' ^', file=sys.stderr)
if self.parsing_import:
print("Error (in imported file):", str(exc), file=sys.stderr)
else:
print("Error:", str(exc), file=sys.stderr)
if sys.stderr.isatty():
print("\x1b[0m", file=sys.stderr, end="", flush=True)
2017-12-21 13:52:30 +00:00
def parse_file(self) -> ParseResult:
print("\nparsing", self.sourceref.file)
2017-12-21 13:52:30 +00:00
self._parse_1()
2017-12-30 12:34:52 +00:00
self._parse_import_file("il65lib") # compiler support library is always imported.
2017-12-21 13:52:30 +00:00
self._parse_2()
return self.result
2017-12-28 18:08:33 +00:00
def print_warning(self, text: str, sourceref: SourceRef=None) -> None:
self.print_bold("warning: {}: {:s}".format(sourceref or self.sourceref, text))
def print_bold(self, text: str) -> None:
2017-12-26 00:30:22 +00:00
if sys.stdout.isatty():
2018-01-01 04:49:12 +00:00
print("\x1b[1m" + text + "\x1b[0m", flush=True)
2017-12-26 00:30:22 +00:00
else:
print(text)
2017-12-21 22:05:35 +00:00
def _parse_comments(self) -> None:
while True:
line = self.next_line().lstrip()
if line.startswith(';'):
2017-12-31 03:45:27 +00:00
self.cur_block.statements.append(Comment(line, self.sourceref))
2017-12-21 22:05:35 +00:00
continue
self.prev_line()
break
2017-12-21 13:52:30 +00:00
def _parse_1(self) -> None:
2017-12-31 03:45:27 +00:00
self.cur_block = Block("<header>", self.sourceref, self.root_scope)
2017-12-21 22:05:35 +00:00
self.result.add_block(self.cur_block)
2017-12-21 13:52:30 +00:00
self.parse_header()
2017-12-29 02:52:26 +00:00
if not self.parsing_import:
self.result.zeropage.configure(self.result.clobberzp)
2017-12-21 13:52:30 +00:00
while True:
2017-12-21 22:05:35 +00:00
self._parse_comments()
next_line = self.peek_next_line().lstrip()
if next_line.startswith("~"):
2017-12-21 13:52:30 +00:00
block = self.parse_block()
if block:
self.result.add_block(block)
2017-12-30 12:34:52 +00:00
elif next_line.startswith(("import ", "import\t")):
2017-12-21 13:52:30 +00:00
self.parse_import()
else:
break
line = self.next_line()
if line:
raise self.PError("invalid statement or characters, block expected")
if not self.parsing_import:
# check if we have a proper main block to contain the program's entry point
2017-12-25 12:20:23 +00:00
main_found = False
2017-12-21 13:52:30 +00:00
for block in self.result.blocks:
if block.name == "main":
2017-12-25 12:20:23 +00:00
main_found = True
2017-12-21 13:52:30 +00:00
if "start" not in block.label_names:
self.sourceref.line = block.sourceref.line
self.sourceref.column = 0
2017-12-28 18:08:33 +00:00
raise self.PError("block 'main' should contain the program entry point 'start'")
2017-12-25 12:20:23 +00:00
self._check_return_statement(block, "'main' block")
for sub in block.symbols.iter_subroutines(True):
2017-12-28 18:08:33 +00:00
self._check_return_statement(sub.sub_block, "subroutine '{:s}'".format(sub.name))
2017-12-25 12:20:23 +00:00
if not main_found:
2017-12-28 18:08:33 +00:00
raise self.PError("a block 'main' should be defined and contain the program's entry point label 'start'")
2017-12-21 13:52:30 +00:00
2017-12-31 03:45:27 +00:00
def _check_return_statement(self, block: Block, message: str) -> None:
2017-12-25 12:20:23 +00:00
# find last statement that isn't a comment
for stmt in reversed(block.statements):
2017-12-31 03:45:27 +00:00
if isinstance(stmt, Comment):
2017-12-25 12:20:23 +00:00
continue
2017-12-31 03:45:27 +00:00
if isinstance(stmt, ReturnStmt) or isinstance(stmt, CallStmt) and stmt.is_goto:
2017-12-25 12:20:23 +00:00
return
2017-12-31 03:45:27 +00:00
if isinstance(stmt, InlineAsm):
2017-12-25 12:20:23 +00:00
# check that the last asm line is a jmp or a rts
for asmline in reversed(stmt.asmlines):
if asmline.lstrip().startswith(';'):
continue
if " rts" in asmline or "\trts" in asmline or " jmp" in asmline or "\tjmp" in asmline:
return
if asmline.strip():
if asmline.split()[0].isidentifier():
continue
break
break
2017-12-28 18:08:33 +00:00
self.print_warning("{:s} doesn't end with a return statement".format(message), block.sourceref)
2017-12-25 12:20:23 +00:00
2018-01-01 04:04:04 +00:00
_immediate_floats = {} # type: Dict[float, Tuple[str, str]]
2017-12-21 13:52:30 +00:00
def _parse_2(self) -> None:
2017-12-24 23:15:04 +00:00
# parsing pass 2 (not done during preprocessing!)
2017-12-21 13:52:30 +00:00
self.cur_block = None
self.sourceref.line = -1
self.sourceref.column = 0
2017-12-25 12:20:23 +00:00
2017-12-31 03:45:27 +00:00
def desugar_immediate_strings(stmt: _AstNode, containing_block: Block) -> None:
if isinstance(stmt, CallStmt):
2017-12-28 22:03:59 +00:00
for s in stmt.desugared_call_arguments:
2017-12-31 03:10:27 +00:00
self.sourceref = s.sourceref.copy()
2017-12-31 03:45:27 +00:00
s.desugar_immediate_string(containing_block)
2017-12-28 22:03:59 +00:00
for s in stmt.desugared_output_assignments:
2017-12-31 03:10:27 +00:00
self.sourceref = s.sourceref.copy()
2017-12-31 03:45:27 +00:00
s.desugar_immediate_string(containing_block)
if isinstance(stmt, AssignmentStmt):
2017-12-31 03:10:27 +00:00
self.sourceref = stmt.sourceref.copy()
2017-12-31 03:45:27 +00:00
stmt.desugar_immediate_string(containing_block)
2017-12-28 22:03:59 +00:00
2018-01-01 04:04:04 +00:00
def desugar_immediate_floats(stmt: _AstNode, containing_block: Block) -> None:
if isinstance(stmt, (InplaceIncrStmt, InplaceDecrStmt)):
2018-01-01 16:56:55 +00:00
howmuch = stmt.value.value
if howmuch is None:
assert stmt.value.name
2018-01-01 04:04:04 +00:00
return
2018-01-01 16:56:55 +00:00
if howmuch in (0, 1) or type(howmuch) is int:
2018-01-01 04:04:04 +00:00
return # 1 is special cased in the code generator
rom_floats = {
1: "c64.FL_FONE",
.25: "c64.FL_FR4",
.5: "c64.FL_FHALF",
-.5: "c64.FL_NEGHLF",
10: "c64.FL_TENC",
-32768: "c64.FL_N32768",
1e9: "c64.FL_NZMIL",
math.pi: "c64.FL_PIVAL",
math.pi / 2: "c64.FL_PIHALF",
math.pi * 2: "c64.FL_TWOPI",
math.sqrt(2)/2.0: "c64.FL_SQRHLF",
math.sqrt(2): "c64.FL_SQRTWO",
math.log(2): "c64.FL_LOG2",
1.0 / math.log(2): "c64.FL_LOGEB2",
}
for fv, name in rom_floats.items():
2018-01-01 16:56:55 +00:00
if math.isclose(howmuch, fv, rel_tol=0, abs_tol=1e-9):
2018-01-01 04:04:04 +00:00
# use one of the constants available in ROM
2018-01-01 16:56:55 +00:00
stmt.value.name = name
2018-01-01 04:04:04 +00:00
return
2018-01-01 16:56:55 +00:00
if howmuch in self._immediate_floats:
2018-01-01 04:04:04 +00:00
# reuse previously defined float constant
2018-01-01 16:56:55 +00:00
blockname, floatvar_name = self._immediate_floats[howmuch]
2018-01-01 04:04:04 +00:00
if blockname:
2018-01-01 16:56:55 +00:00
stmt.value.name = blockname + '.' + floatvar_name
2018-01-01 04:04:04 +00:00
else:
2018-01-01 16:56:55 +00:00
stmt.value.name = floatvar_name
2018-01-01 04:04:04 +00:00
else:
# define new float variable to hold the incr/decr value
# note: not a constant, because we need the MFLT bytes
floatvar_name = "il65_float_{:d}".format(id(stmt))
2018-01-01 16:56:55 +00:00
containing_block.symbols.define_variable(floatvar_name, stmt.sourceref, DataType.FLOAT, value=howmuch)
self._immediate_floats[howmuch] = (containing_block.name, floatvar_name)
stmt.value.name = floatvar_name
2018-01-01 04:04:04 +00:00
2017-12-21 13:52:30 +00:00
for block in self.result.blocks:
self.cur_block = block
2017-12-28 22:03:59 +00:00
self.sourceref = block.sourceref.copy()
self.sourceref.column = 0
2017-12-31 03:45:27 +00:00
for _, sub, stmt in block.all_statements():
if isinstance(stmt, CallStmt):
2017-12-31 03:10:27 +00:00
self.sourceref = stmt.sourceref.copy()
2017-12-31 03:45:27 +00:00
self.desugar_call_arguments_and_outputs(stmt)
desugar_immediate_strings(stmt, self.cur_block)
2018-01-01 04:04:04 +00:00
desugar_immediate_floats(stmt, self.cur_block)
2017-12-31 03:45:27 +00:00
def desugar_call_arguments_and_outputs(self, stmt: CallStmt) -> None:
stmt.desugared_call_arguments.clear()
stmt.desugared_output_assignments.clear()
for name, value in stmt.arguments or []:
assert name is not None, "all call arguments should have a name or be matched on a named parameter"
assignment = self.parse_assignment(name, value)
if assignment.leftvalues[0].datatype != DataType.BYTE:
if isinstance(assignment.right, IntegerValue) and assignment.right.constant:
# a call that doesn't expect a BYTE argument but gets one, converted from a 1-byte string most likely
if value.startswith("'") and value.endswith("'"):
self.print_warning("possible problematic string to byte conversion (use a .text var instead?)")
if not assignment.is_identity():
assignment.sourceref = stmt.sourceref.copy() # @todo why set this?
stmt.desugared_call_arguments.append(assignment)
if all(not isinstance(v, RegisterValue) for r, v in stmt.outputvars or []):
# if none of the output variables are registers, we can simply generate the assignments without issues
for register, value in stmt.outputvars or []:
rvalue = self.parse_expression(register)
assignment = AssignmentStmt([value], rvalue, stmt.sourceref)
stmt.desugared_output_assignments.append(assignment)
else:
result_reg_mapping = [(register, value.register, value) for register, value in stmt.outputvars or []
if isinstance(value, RegisterValue)]
if any(r[0] != r[1] for r in result_reg_mapping):
# not all result parameter registers line up with the correct order of registers in the statement,
# reshuffling call results is not supported yet.
raise self.PError("result registers and/or their ordering is not the same as in the "
"subroutine definition, this isn't supported yet")
else:
# no register alignment issues, just generate the assignments
# note: do not remove the identity assignment here or the output register handling generates buggy code
for register, value in stmt.outputvars or []:
rvalue = self.parse_expression(register)
assignment = AssignmentStmt([value], rvalue, stmt.sourceref)
stmt.desugared_output_assignments.append(assignment)
2017-12-21 13:52:30 +00:00
def next_line(self) -> str:
2017-12-25 12:20:23 +00:00
self._cur_lineidx += 1
2017-12-21 13:52:30 +00:00
try:
2017-12-25 12:20:23 +00:00
self.sourceref.line, line = self.lines[self._cur_lineidx]
2017-12-21 13:52:30 +00:00
self.sourceref.column = 0
return line
except IndexError:
return ""
def prev_line(self) -> str:
2017-12-25 12:20:23 +00:00
self._cur_lineidx -= 1
self.sourceref.line, line = self.lines[self._cur_lineidx]
2017-12-21 13:52:30 +00:00
return line
def peek_next_line(self) -> str:
2017-12-25 12:20:23 +00:00
if (self._cur_lineidx + 1) < len(self.lines):
return self.lines[self._cur_lineidx + 1][1]
2017-12-21 13:52:30 +00:00
return ""
def PError(self, message: str, lineno: int=0, column: int=0) -> ParseError:
2017-12-21 13:52:30 +00:00
sourceline = ""
2017-12-25 12:20:23 +00:00
lineno = lineno or self.sourceref.line
column = column or self.sourceref.column
for num, text in self.lines:
if num == lineno:
sourceline = text.strip()
break
return ParseError(message, sourceline, SourceRef(self.sourceref.file, lineno, column))
def get_datatype(self, typestr: str) -> Tuple[DataType, int, Optional[Tuple[int, int]]]:
if typestr == ".byte":
return DataType.BYTE, 1, None
elif typestr == ".word":
return DataType.WORD, 1, None
elif typestr == ".float":
return DataType.FLOAT, 1, None
elif typestr.endswith("text"):
if typestr == ".text":
return DataType.STRING, 0, None
elif typestr == ".ptext":
return DataType.STRING_P, 0, None
elif typestr == ".stext":
return DataType.STRING_S, 0, None
elif typestr == ".pstext":
return DataType.STRING_PS, 0, None
elif typestr.startswith(".array(") and typestr.endswith(")"):
return DataType.BYTEARRAY, self._size_from_arraydecl(typestr), None
elif typestr.startswith(".wordarray(") and typestr.endswith(")"):
return DataType.WORDARRAY, self._size_from_arraydecl(typestr), None
elif typestr.startswith(".matrix(") and typestr.endswith(")"):
dimensions = self._size_from_matrixdecl(typestr)
return DataType.MATRIX, dimensions[0] * dimensions[1], dimensions
raise self.PError("invalid data type: " + typestr)
2017-12-21 13:52:30 +00:00
def parse_header(self) -> None:
self.result.with_sys = False
self.result.format = ProgramFormat.RAW
output_specified = False
2017-12-28 22:03:59 +00:00
zp_specified = False
2017-12-21 13:52:30 +00:00
while True:
2017-12-21 22:05:35 +00:00
self._parse_comments()
2017-12-21 13:52:30 +00:00
line = self.next_line()
2017-12-28 22:03:59 +00:00
if line.startswith(("output ", "output\t")):
2017-12-21 13:52:30 +00:00
if output_specified:
2017-12-28 22:03:59 +00:00
raise self.PError("can only specify output options once")
2017-12-21 13:52:30 +00:00
output_specified = True
2017-12-28 22:03:59 +00:00
_, _, optionstr = line.partition(" ")
options = set(optionstr.replace(' ', '').split(','))
2017-12-21 13:52:30 +00:00
self.result.with_sys = False
self.result.format = ProgramFormat.RAW
2017-12-28 22:03:59 +00:00
if "raw" in options:
options.remove("raw")
if "prg" in options:
options.remove("prg")
2017-12-21 13:52:30 +00:00
self.result.format = ProgramFormat.PRG
2017-12-28 22:03:59 +00:00
if "basic" in options:
options.remove("basic")
if self.result.format == ProgramFormat.PRG:
self.result.with_sys = True
else:
raise self.PError("can only use basic output option with prg, not raw")
if options:
raise self.PError("invalid output option(s): " + str(options))
elif line.startswith(("zp ", "zp\t")):
if zp_specified:
raise self.PError("can only specify ZP options once")
zp_specified = True
_, _, optionstr = line.partition(" ")
options = set(optionstr.replace(' ', '').split(','))
self.result.clobberzp = False
self.result.restorezp = False
if "clobber" in options:
options.remove("clobber")
self.result.clobberzp = True
if "restore" in options:
options.remove("restore")
if self.result.clobberzp:
self.result.restorezp = True
else:
raise self.PError("can only use restore zp option if clobber zp is used as well")
if options:
raise self.PError("invalid zp option(s): " + str(options))
2017-12-21 13:52:30 +00:00
elif line.startswith("address"):
if self.result.start_address:
raise self.PError("multiple occurrences of 'address'")
_, _, arg = line.partition(" ")
try:
self.result.start_address = parse_expr_as_int(arg, None, None, self.sourceref)
2017-12-21 13:52:30 +00:00
except ParseError:
raise self.PError("invalid address")
if self.result.format == ProgramFormat.PRG and self.result.with_sys and self.result.start_address != 0x0801:
raise self.PError("cannot use non-default 'address' when output format includes basic SYS program")
else:
# header parsing finished!
self.prev_line()
if not self.result.start_address:
# set the proper default start address
if self.result.format == ProgramFormat.PRG:
self.result.start_address = 0x0801 # normal C-64 basic program start address
elif self.result.format == ProgramFormat.RAW:
self.result.start_address = 0xc000 # default start for raw assembly
if self.result.format == ProgramFormat.PRG and self.result.with_sys and self.result.start_address != 0x0801:
raise self.PError("cannot use non-default 'address' when output format includes basic SYS program")
return
def parse_import(self) -> None:
line = self.next_line()
line = line.lstrip()
2017-12-30 12:34:52 +00:00
if not line.startswith(("import ", "import\t")):
2017-12-21 13:52:30 +00:00
raise self.PError("expected import")
try:
_, arg = line.split(maxsplit=1)
except ValueError:
raise self.PError("invalid import statement")
if not arg.startswith('"') or not arg.endswith('"'):
raise self.PError("filename must be between quotes")
filename = arg[1:-1]
if not filename:
raise self.PError("invalid filename")
2017-12-30 12:34:52 +00:00
self._parse_import_file(filename)
def _parse_import_file(self, filename: str) -> None:
2017-12-21 13:52:30 +00:00
filename_at_source_location = os.path.join(os.path.split(self.sourceref.file)[0], filename)
filename_at_libs_location = os.path.join(os.getcwd(), "lib", filename)
candidates = [filename,
filename_at_source_location,
filename_at_libs_location,
filename+".ill",
filename_at_source_location+".ill",
filename_at_libs_location+".ill"]
for filename in candidates:
if os.path.isfile(filename):
2017-12-30 12:34:52 +00:00
if not self.check_import_okay(filename):
return
self.print_import_progress("importing", filename)
2017-12-21 13:52:30 +00:00
parser = self.create_import_parser(filename, self.outputdir)
result = parser.parse()
2017-12-30 12:34:52 +00:00
self.print_import_progress("\ncontinuing", self.sourceref.file)
2017-12-21 13:52:30 +00:00
if result:
# merge the symbol table of the imported file into our own
2017-12-21 22:05:35 +00:00
try:
self.root_scope.merge_roots(parser.root_scope)
self.result.merge(result)
except SymbolError as x:
raise self.PError(str(x))
2017-12-21 13:52:30 +00:00
return
else:
raise self.PError("Error while parsing imported file")
raise self.PError("imported file not found")
2017-12-30 12:34:52 +00:00
def print_import_progress(self, message: str, *args: str) -> None:
print(message, *args)
2017-12-21 13:52:30 +00:00
def create_import_parser(self, filename: str, outputdir: str) -> 'Parser':
2017-12-30 12:34:52 +00:00
return Parser(filename, outputdir, self.existing_imports, True, ppsymbols=self.ppsymbols, sub_usage=self.result.subroutine_usage)
2017-12-21 13:52:30 +00:00
2017-12-31 03:45:27 +00:00
def parse_block(self) -> Optional[Block]:
2017-12-21 13:52:30 +00:00
# first line contains block header "~ [name] [addr]" followed by a '{'
2017-12-21 22:05:35 +00:00
self._parse_comments()
2017-12-21 13:52:30 +00:00
line = self.next_line()
line = line.lstrip()
if not line.startswith("~"):
raise self.PError("expected '~' (block)")
block_args = line[1:].split()
arg = ""
2017-12-31 03:45:27 +00:00
self.cur_block = Block("", self.sourceref.copy(), self.root_scope)
2017-12-21 13:52:30 +00:00
is_zp_block = False
while block_args:
arg = block_args.pop(0)
if arg.isidentifier():
if arg.lower() == "zeropage" or arg in ("zp", "zP", "Zp"):
raise self.PError("zero page block should be named 'ZP'")
is_zp_block = arg == "ZP"
if arg in set(b.name for b in self.result.blocks):
orig = [b for b in self.result.blocks if b.name == arg][0]
if not is_zp_block:
raise self.PError("duplicate block name '{:s}', original definition at {}".format(arg, orig.sourceref))
self.cur_block = orig # zero page block occurrences are merged
else:
2017-12-31 03:45:27 +00:00
self.cur_block = Block(arg, self.sourceref.copy(), self.root_scope)
2017-12-21 13:52:30 +00:00
try:
2017-12-21 22:05:35 +00:00
self.root_scope.define_scope(self.cur_block.symbols, self.cur_block.sourceref)
2017-12-21 13:52:30 +00:00
except SymbolError as x:
raise self.PError(str(x))
elif arg == "{":
break
elif arg.endswith("{"):
# when there is no whitespace before the {
block_args.insert(0, "{")
block_args.insert(0, arg[:-1])
continue
else:
try:
block_address = parse_expr_as_int(arg, self.cur_block.symbols, self.ppsymbols, self.sourceref)
2017-12-21 13:52:30 +00:00
except ParseError:
raise self.PError("Invalid block address")
if block_address == 0 or (block_address < 0x0200 and not is_zp_block):
raise self.PError("block address must be >= $0200 (or omitted)")
if is_zp_block:
if block_address not in (0, 0x04):
raise self.PError("zero page block address must be $04 (or omittted)")
block_address = 0x04
self.cur_block.address = block_address
if arg != "{":
line = self.peek_next_line()
if line != "{":
raise self.PError("expected '{' after block")
else:
self.next_line()
if self.print_block_parsing:
if self.cur_block.address:
print(" parsing block '{:s}' at ${:04x}".format(self.cur_block.name, self.cur_block.address))
else:
print(" parsing block '{:s}'".format(self.cur_block.name))
2017-12-28 18:08:33 +00:00
if self.cur_block.ignore:
# just skip the lines until we hit a '}' that closes the block
nesting_level = 1
while True:
line = self.next_line().strip()
if line.endswith("{"):
nesting_level += 1
elif line == "}":
nesting_level -= 1
if nesting_level == 0:
self.print_warning("ignoring block without name and address", self.cur_block.sourceref)
return None
else:
raise self.PError("invalid statement in block")
2017-12-21 13:52:30 +00:00
while True:
2018-01-01 04:49:12 +00:00
try:
go_on, resultblock = self._parse_block_statement(is_zp_block)
if not go_on:
return resultblock
except ParseError as x:
self.handle_parse_error(x)
def _parse_block_statement(self, is_zp_block: bool) -> Tuple[bool, Optional[Block]]:
# parse the statements inside a block
self._parse_comments()
line = self.next_line()
unstripped_line = line
line = line.strip()
if line == "}":
if is_zp_block and any(b.name == "ZP" for b in self.result.blocks):
return False, None # we already have the ZP block
if self.cur_block.ignore:
self.print_warning("ignoring block without name and address", self.cur_block.sourceref)
return False, None
return False, self.cur_block
if line.startswith(("var ", "var\t")):
self.parse_var_def(line)
elif line.startswith(("const ", "const\t")):
self.parse_const_def(line)
elif line.startswith(("memory ", "memory\t")):
self.parse_memory_def(line, is_zp_block)
elif line.startswith(("sub ", "sub\t")):
if is_zp_block:
raise self.PError("ZP block cannot contain subroutines")
self.parse_subroutine_def(line)
elif line.startswith(("asminclude ", "asminclude\t", "asmbinary ", "asmbinary\t")):
if is_zp_block:
raise self.PError("ZP block cannot contain assembler directives")
self.cur_block.statements.append(self.parse_asminclude(line))
elif line.startswith(("asm ", "asm\t")):
if is_zp_block:
raise self.PError("ZP block cannot contain code statements")
self.prev_line()
self.cur_block.statements.append(self.parse_asm())
elif line == "breakpoint":
self.cur_block.statements.append(BreakpointStmt(self.sourceref))
self.print_warning("breakpoint defined")
elif unstripped_line.startswith((" ", "\t")):
if is_zp_block:
raise self.PError("ZP block cannot contain code statements")
self.cur_block.statements.append(self.parse_statement(line))
elif line:
if is_zp_block:
raise self.PError("ZP block cannot contain code labels")
self.parse_label(line)
else:
raise self.PError("invalid statement in block")
return True, None # continue with more statements
2017-12-21 13:52:30 +00:00
def parse_label(self, line: str) -> None:
label_line = line.split(maxsplit=1)
if str.isidentifier(label_line[0]):
labelname = label_line[0]
if labelname in self.cur_block.label_names:
raise self.PError("label already defined")
if labelname in self.cur_block.symbols:
raise self.PError("symbol already defined")
self.cur_block.symbols.define_label(labelname, self.sourceref)
2017-12-31 03:45:27 +00:00
self.cur_block.statements.append(Label(labelname, self.sourceref))
2017-12-21 13:52:30 +00:00
if len(label_line) > 1:
rest = label_line[1]
self.cur_block.statements.append(self.parse_statement(rest))
else:
raise self.PError("invalid label name")
def parse_memory_def(self, line: str, is_zeropage: bool=False) -> None:
varname, datatype, length, dimensions, valuetext = self.parse_def_common(line, "memory")
memaddress = parse_expr_as_int(valuetext, self.cur_block.symbols, self.ppsymbols, self.sourceref)
2017-12-21 13:52:30 +00:00
if is_zeropage and memaddress > 0xff:
2017-12-29 02:52:26 +00:00
raise self.PError("address must be in zeropage $00-$ff")
2017-12-21 13:52:30 +00:00
try:
self.cur_block.symbols.define_variable(varname, self.sourceref, datatype,
length=length, address=memaddress, matrixsize=dimensions)
except SymbolError as x:
raise self.PError(str(x)) from x
def parse_const_def(self, line: str) -> None:
varname, datatype, length, dimensions, valuetext = self.parse_def_common(line, "const")
if dimensions:
raise self.PError("cannot declare a constant matrix")
value = parse_expr_as_primitive(valuetext, self.cur_block.symbols, self.ppsymbols, self.sourceref)
_, value = self.coerce_value(self.sourceref, datatype, value)
2017-12-21 13:52:30 +00:00
try:
self.cur_block.symbols.define_constant(varname, self.sourceref, datatype, length=length, value=value)
except (ValueError, SymbolError) as x:
raise self.PError(str(x)) from x
2017-12-25 18:09:10 +00:00
def parse_subroutine_def(self, line: str) -> None:
2017-12-27 11:39:19 +00:00
match = re.fullmatch(r"sub\s+(?P<name>\w+)\s+"
r"\((?P<parameters>[\w\s:,]*)\)"
r"\s*->\s*"
r"\((?P<results>[\w\s?,]*)\)\s*"
r"(?P<decltype>\s+=\s+(?P<address>\S*)|{)\s*", line)
2017-12-21 13:52:30 +00:00
if not match:
2017-12-24 23:15:04 +00:00
raise self.PError("invalid sub declaration")
2017-12-27 11:39:19 +00:00
groups = match.groupdict()
code_decl = groups["decltype"] == "{"
name, parameterlist, resultlist, address_str = groups["name"], groups["parameters"], groups["results"], groups["address"]
parameters = [(m.group("name"), m.group("target"))
for m in re.finditer(r"(?:(?:(?P<name>[\w]+)\s*:\s*)?(?P<target>[\w]+))(?:,|$)", parameterlist)]
2017-12-21 13:52:30 +00:00
for _, regs in parameters:
if regs not in REGISTER_SYMBOLS:
raise self.PError("invalid register(s) in parameter or return values")
all_paramnames = [p[0] for p in parameters if p[0]]
if len(all_paramnames) != len(set(all_paramnames)):
raise self.PError("duplicates in parameter names")
2017-12-27 11:39:19 +00:00
results = [m.group("name") for m in re.finditer(r"\s*(?P<name>(?:\w+)\??)\s*(?:,|$)", resultlist)]
2017-12-30 20:36:42 +00:00
if not results:
if resultlist == "?":
# a single '?' in the result spec means: all 3 registers clobbered
results = ['A?', 'X?', 'Y?']
elif resultlist:
raise self.PError("invalid return values spec")
2017-12-23 23:58:55 +00:00
subroutine_block = None
if code_decl:
address = None
# parse the subroutine code lines (until the closing '}')
2017-12-31 03:45:27 +00:00
subroutine_block = Block(self.cur_block.name + "." + name, self.sourceref, self.cur_block.symbols)
2017-12-23 23:58:55 +00:00
current_block = self.cur_block
self.cur_block = subroutine_block
while True:
self._parse_comments()
line = self.next_line()
unstripped_line = line
line = line.strip()
if line == "}":
# subroutine end
break
2017-12-24 23:15:04 +00:00
if line.startswith(("sub ", "sub\t")):
2017-12-23 23:58:55 +00:00
raise self.PError("cannot nest subroutines")
elif line.startswith(("asm ", "asm\t")):
self.prev_line()
subroutine_block.statements.append(self.parse_asm())
elif unstripped_line.startswith((" ", "\t")):
subroutine_block.statements.append(self.parse_statement(line))
elif line:
self.parse_label(line)
else:
2017-12-24 23:15:04 +00:00
raise self.PError("invalid statement in subroutine")
2017-12-23 23:58:55 +00:00
self.cur_block = current_block
self.cur_block.sourceref = subroutine_block.sourceref
else:
try:
address = parse_expr_as_int(address_str, self.cur_block.symbols, self.ppsymbols, self.sourceref)
except ParseError:
raise self.PError("invalid subroutine address")
2017-12-21 13:52:30 +00:00
try:
2017-12-23 23:58:55 +00:00
self.cur_block.symbols.define_sub(name, self.sourceref, parameters, results, address, subroutine_block)
2017-12-21 13:52:30 +00:00
except SymbolError as x:
raise self.PError(str(x)) from x
def parse_var_def(self, line: str) -> None:
varname, datatype, length, dimensions, valuetext = self.parse_def_common(line, "var", False)
value = parse_expr_as_primitive(valuetext, self.cur_block.symbols, self.ppsymbols, self.sourceref)
_, value = self.coerce_value(self.sourceref, datatype, value)
2017-12-21 13:52:30 +00:00
try:
self.cur_block.symbols.define_variable(varname, self.sourceref, datatype,
length=length, value=value, matrixsize=dimensions)
except (ValueError, SymbolError) as x:
raise self.PError(str(x)) from x
def parse_def_common(self, line: str, what: str, value_required: bool=True) -> \
Tuple[str, DataType, int, Optional[Tuple[int, int]], str]:
try:
vartext, valuetext = line.split("=", maxsplit=1)
except ValueError:
if '=' not in line:
if value_required:
raise self.PError("missing value assignment")
vartext, valuetext = line, "0" # unspecified value is '0'
else:
raise self.PError("invalid {:s} decl, '=' missing?".format(what))
args = self.psplit(vartext)
if args[0] != what or len(args) < 2:
raise self.PError("invalid {:s} decl".format(what))
if len(args) > 3 or valuetext.startswith('='):
raise self.PError("invalid {:s} decl, '=' missing?".format(what))
if len(args) == 2:
args.insert(1, ".byte") # unspecified data type is ".byte"
if not args[1].startswith("."):
raise self.PError("invalid {:s} decl, type is missing".format(what))
varname = args[2]
if not varname.isidentifier():
raise self.PError("invalid {:s} name".format(what))
if varname in RESERVED_NAMES:
raise self.PError("can't use a reserved name as {:s} name".format(what))
datatype, length, matrix_dimensions = self.get_datatype(args[1])
return varname, datatype, length, matrix_dimensions, valuetext
2017-12-31 03:45:27 +00:00
def parse_statement(self, line: str) -> _AstNode:
2017-12-27 18:01:14 +00:00
match = re.fullmatch(r"(?P<if>if(_[a-z]+)?)\s+(?P<cond>.+)?goto\s+(?P<subname>[\S]+?)\s*(\((?P<arguments>.*)\))?\s*", line)
if match:
# conditional goto
groups = match.groupdict()
subname = groups["subname"]
if '!' in subname:
raise self.PError("goto is always without register preservation, should not have exclamation mark")
if groups["if"] == "if" and not groups["cond"]:
raise self.PError("need explicit if status when a condition is not present")
condition = self.parse_if_condition(groups["if"], groups["cond"])
return self.parse_call_or_goto(subname, groups["arguments"], None, False, True, condition=condition)
2017-12-27 11:39:19 +00:00
match = re.fullmatch(r"goto\s+(?P<subname>[\S]+?)\s*(\((?P<arguments>.*)\))?\s*", line)
2017-12-26 00:30:22 +00:00
if match:
2017-12-27 11:39:19 +00:00
# goto
groups = match.groupdict()
subname = groups["subname"]
if '!' in subname:
raise self.PError("goto is always without register preservation, should not have exclamation mark")
2017-12-27 18:01:14 +00:00
return self.parse_call_or_goto(subname, groups["arguments"], None, False, True)
2017-12-27 11:39:19 +00:00
match = re.fullmatch(r"(?P<outputs>[^\(]*\s*=)?\s*(?P<subname>[\S]+?)\s*(?P<fcall>[!]?)\s*(\((?P<arguments>.*)\))?\s*", line)
if match:
# subroutine call (not a goto) with possible output param assignment
groups = match.groupdict()
preserve = not bool(groups["fcall"])
subname = groups["subname"]
arguments = groups["arguments"]
outputs = groups["outputs"] or ""
2017-12-26 00:30:22 +00:00
if outputs.strip() == "=":
raise self.PError("missing assignment target variables")
outputs = outputs.rstrip("=")
2017-12-27 11:39:19 +00:00
if arguments or match.group(4): # group 4 = (possibly empty) parenthesis
2017-12-26 00:30:22 +00:00
return self.parse_call_or_goto(subname, arguments, outputs, preserve, False)
# apparently it is not a call (no arguments), fall through
2017-12-23 13:36:23 +00:00
if line == "return" or line.startswith(("return ", "return\t")):
2017-12-21 13:52:30 +00:00
return self.parse_return(line)
elif line.endswith(("++", "--")):
incr = line.endswith("++")
what = self.parse_expression(line[:-2].rstrip())
2017-12-31 03:45:27 +00:00
if isinstance(what, IntegerValue):
2017-12-21 13:52:30 +00:00
raise self.PError("cannot in/decrement a constant value")
2018-01-01 16:56:55 +00:00
one_value = IntegerValue(1, self.sourceref)
2017-12-28 03:20:59 +00:00
if incr:
2018-01-01 16:56:55 +00:00
return InplaceIncrStmt(what, one_value, self.sourceref)
return InplaceDecrStmt(what, one_value, self.sourceref)
2017-12-21 13:52:30 +00:00
else:
2017-12-28 03:20:59 +00:00
# perhaps it is an augmented assignment statement
match = re.fullmatch(r"(?P<left>\S+)\s*(?P<assignment>\+=|-=|\*=|/=|%=|//=|\*\*=|&=|\|=|\^=|>>=|<<=)\s*(?P<right>\S.*)", line)
if match:
return self.parse_augmented_assignment(match.group("left"), match.group("assignment"), match.group("right"))
# a normal assignment perhaps?
splits = [s.strip() for s in line.split('=')]
2017-12-28 18:08:33 +00:00
if len(splits) > 1 and all(splits):
2017-12-28 03:20:59 +00:00
return self.parse_assignment(*splits)
2017-12-21 13:52:30 +00:00
raise self.PError("invalid statement")
2017-12-26 00:30:22 +00:00
def parse_call_or_goto(self, targetstr: str, argumentstr: str, outputstr: str,
2017-12-31 03:45:27 +00:00
preserve_regs=True, is_goto=False, condition: IfCondition=None) -> CallStmt:
2017-12-27 18:01:14 +00:00
if not is_goto:
assert condition is None
2017-12-23 13:36:23 +00:00
argumentstr = argumentstr.strip() if argumentstr else ""
2017-12-26 00:30:22 +00:00
outputstr = outputstr.strip() if outputstr else ""
2017-12-23 00:53:48 +00:00
arguments = None
2017-12-26 00:30:22 +00:00
outputvars = None
2017-12-23 13:36:23 +00:00
if argumentstr:
2017-12-25 21:22:19 +00:00
arguments = parse_arguments(argumentstr, self.sourceref)
2017-12-31 03:45:27 +00:00
target = None # type: Value
if targetstr[0] == '[' and targetstr[-1] == ']':
2017-12-21 13:52:30 +00:00
# indirect call to address in register pair or memory location
2017-12-24 23:15:04 +00:00
targetstr, target = self.parse_indirect_value(targetstr, True)
2017-12-23 00:53:48 +00:00
if target.datatype != DataType.WORD:
raise self.PError("invalid call target (should contain 16-bit)")
2017-12-21 13:52:30 +00:00
else:
2017-12-23 00:53:48 +00:00
target = self.parse_expression(targetstr)
2017-12-31 03:45:27 +00:00
if not isinstance(target, (IntegerValue, MemMappedValue, IndirectValue)):
2017-12-23 00:53:48 +00:00
raise self.PError("cannot call that type of symbol")
2017-12-31 03:45:27 +00:00
if isinstance(target, IndirectValue) \
and not isinstance(target.value, (IntegerValue, RegisterValue, MemMappedValue)):
2017-12-23 00:53:48 +00:00
raise self.PError("cannot call that type of indirect symbol")
2017-12-31 03:45:27 +00:00
address = target.address if isinstance(target, MemMappedValue) else None
try:
2017-12-25 21:22:19 +00:00
_, symbol = self.lookup_with_ppsymbols(targetstr)
except ParseError:
symbol = None # it's probably a number or a register then
2017-12-23 00:53:48 +00:00
if isinstance(symbol, SubroutineDef):
2017-12-27 18:01:14 +00:00
if condition and symbol.parameters:
raise self.PError("cannot use a subroutine that requires parameters as a target for conditional goto")
2017-12-23 00:53:48 +00:00
# verify subroutine arguments
2017-12-23 13:36:23 +00:00
if len(arguments or []) != len(symbol.parameters):
2017-12-23 00:53:48 +00:00
raise self.PError("invalid number of arguments ({:d}, expected {:d})"
2017-12-23 13:36:23 +00:00
.format(len(arguments or []), len(symbol.parameters)))
2017-12-23 00:53:48 +00:00
args_with_pnames = []
for i, (argname, value) in enumerate(arguments or []):
pname, preg = symbol.parameters[i]
2017-12-25 21:22:19 +00:00
required_name = pname or preg
if argname and argname != required_name:
raise self.PError("parameter mismatch ('{:s}', expected '{:s}')".format(argname, required_name))
argname = preg
2017-12-23 00:53:48 +00:00
args_with_pnames.append((argname, value))
arguments = args_with_pnames
2017-12-26 00:30:22 +00:00
# verify output parameters
if symbol.return_registers:
if outputstr:
outputs = [r.strip() for r in outputstr.split(",")]
if len(outputs) != len(symbol.return_registers):
raise self.PError("invalid number of output parameters consumed ({:d}, expected {:d})"
.format(len(outputs), len(symbol.return_registers)))
outputvars = list(zip(symbol.return_registers, (self.parse_expression(out) for out in outputs)))
else:
2017-12-28 18:08:33 +00:00
self.print_warning("return values discarded")
2017-12-26 00:30:22 +00:00
else:
if outputstr:
raise self.PError("this subroutine doesn't have output parameters")
self.result.sub_used_by(symbol, self.sourceref) # sub usage tracking
2017-12-23 13:36:23 +00:00
else:
2017-12-26 00:30:22 +00:00
if outputstr:
raise self.PError("call cannot use output parameter assignment here, a subroutine is required for that")
2017-12-23 13:36:23 +00:00
if arguments:
2017-12-26 00:30:22 +00:00
raise self.PError("call cannot take any arguments here, a subroutine is required for that")
# verify that all arguments have gotten a name
if any(not a[0] for a in arguments or []):
raise self.PError("all call arguments should have a name or be matched on a named parameter")
2017-12-31 03:45:27 +00:00
if isinstance(target, (type(None), Value)):
2017-12-29 02:52:26 +00:00
# special case for the C-64 lib's print function, to be able to use it with a single character argument
2017-12-31 02:19:06 +00:00
if target.name == "c64scr.print_string" and len(arguments) == 1 and isinstance(arguments[0], str):
2017-12-29 02:52:26 +00:00
if arguments[0][1].startswith("'") and arguments[0][1].endswith("'"):
target = self.parse_expression("c64.CHROUT")
address = target.address
outputvars = None
_, newsymbol = self.lookup_with_ppsymbols("c64.CHROUT")
assert len(newsymbol.parameters) == 1
arguments = [(newsymbol.parameters[0][1], arguments[0][1])]
2017-12-23 13:36:23 +00:00
if is_goto:
2017-12-31 03:45:27 +00:00
return CallStmt(self.sourceref, target, address=address, arguments=arguments,
outputs=outputvars, is_goto=True, condition=condition)
2017-12-21 13:52:30 +00:00
else:
2017-12-31 03:45:27 +00:00
return CallStmt(self.sourceref, target, address=address, arguments=arguments,
outputs=outputvars, preserve_regs=preserve_regs)
else:
2017-12-23 00:53:48 +00:00
raise TypeError("target should be a Value", target)
2017-12-21 13:52:30 +00:00
def parse_integer(self, text: str) -> int:
text = text.strip()
if text.startswith('$'):
return int(text[1:], 16)
if text.startswith('%'):
return int(text[1:], 2)
return int(text)
2017-12-31 03:45:27 +00:00
def parse_assignment(self, *parts) -> AssignmentStmt:
2017-12-28 03:20:59 +00:00
# parses the assignment of one rvalue to one or more lvalues
l_values = [self.parse_expression(p) for p in parts[:-1]]
r_value = self.parse_expression(parts[-1])
if any(lv.constant for lv in l_values):
2017-12-24 23:15:04 +00:00
raise self.PError("can't have a constant as assignment target, perhaps you wanted indirection [...] instead?")
2017-12-21 13:52:30 +00:00
for lv in l_values:
assignable, reason = lv.assignable_from(r_value)
if not assignable:
raise self.PError("cannot assign {0} to {1}; {2}".format(r_value, lv, reason))
if lv.datatype in (DataType.BYTE, DataType.WORD, DataType.MATRIX):
2017-12-28 03:20:59 +00:00
# truncate the rvalue if needed
2017-12-31 03:45:27 +00:00
if isinstance(r_value, FloatValue):
truncated, value = self.coerce_value(self.sourceref, lv.datatype, r_value.value)
2017-12-21 13:52:30 +00:00
if truncated:
2017-12-31 03:45:27 +00:00
r_value = IntegerValue(int(value), self.sourceref, datatype=lv.datatype, name=r_value.name)
return AssignmentStmt(l_values, r_value, self.sourceref)
2017-12-21 13:52:30 +00:00
2017-12-28 03:20:59 +00:00
def parse_augmented_assignment(self, leftstr: str, operator: str, rightstr: str) \
2017-12-31 03:45:27 +00:00
-> Union[AssignmentStmt, InplaceDecrStmt, InplaceIncrStmt]:
2017-12-28 03:20:59 +00:00
# parses an augmented assignment (for instance: value += 3)
2017-12-31 03:45:27 +00:00
if operator not in AugmentedAssignmentStmt.SUPPORTED_OPERATORS:
2017-12-28 03:20:59 +00:00
raise self.PError("augmented assignment operator '{:s}' not supported".format(operator))
l_value = self.parse_expression(leftstr)
r_value = self.parse_expression(rightstr)
if l_value.constant:
raise self.PError("can't have a constant as assignment target, perhaps you wanted indirection [...] instead?")
if l_value.datatype in (DataType.BYTE, DataType.WORD, DataType.MATRIX):
# truncate the rvalue if needed
2017-12-31 03:45:27 +00:00
if isinstance(r_value, FloatValue):
2017-12-28 03:20:59 +00:00
truncated, value = self.coerce_value(self.sourceref, l_value.datatype, r_value.value)
if truncated:
2017-12-31 03:45:27 +00:00
r_value = IntegerValue(int(value), self.sourceref, datatype=l_value.datatype, name=r_value.name)
2018-01-01 04:04:04 +00:00
if operator in ("+=", "-="):
2018-01-01 16:56:55 +00:00
# see if we can simplify this to inplace incr/decr statement (only int/float constant values)
2018-01-01 04:04:04 +00:00
if r_value.constant:
2018-01-01 16:56:55 +00:00
if not isinstance(r_value, (IntegerValue, FloatValue)):
raise self.PError("incr/decr requires constant int or float, not " + r_value.__class__.__name__)
2018-01-01 04:04:04 +00:00
if operator == "+=":
2018-01-01 16:56:55 +00:00
if r_value.value > 0:
return InplaceIncrStmt(l_value, r_value, self.sourceref)
elif r_value.value < 0:
return InplaceDecrStmt(l_value, r_value.negative(), self.sourceref)
2018-01-01 04:04:04 +00:00
else:
self.print_warning("incr with zero, ignored")
2017-12-28 03:20:59 +00:00
else:
2018-01-01 16:56:55 +00:00
if r_value.value > 0:
return InplaceDecrStmt(l_value, r_value, self.sourceref)
elif r_value.value < 0:
return InplaceIncrStmt(l_value, r_value.negative(), self.sourceref)
2018-01-01 04:04:04 +00:00
else:
self.print_warning("decr with zero, ignored")
2017-12-31 03:45:27 +00:00
return AugmentedAssignmentStmt(l_value, operator, r_value, self.sourceref)
2017-12-28 03:20:59 +00:00
2017-12-31 03:45:27 +00:00
def parse_return(self, line: str) -> ReturnStmt:
2017-12-21 13:52:30 +00:00
parts = line.split(maxsplit=1)
if parts[0] != "return":
raise self.PError("invalid statement, return expected")
a = x = y = None
values = [] # type: List[str]
if len(parts) > 1:
values = parts[1].split(",")
if len(values) == 0:
2017-12-31 03:45:27 +00:00
return ReturnStmt(self.sourceref)
2017-12-21 13:52:30 +00:00
else:
a = self.parse_expression(values[0]) if values[0] else None
if len(values) > 1:
x = self.parse_expression(values[1]) if values[1] else None
if len(values) > 2:
y = self.parse_expression(values[2]) if values[2] else None
if len(values) > 3:
raise self.PError("too many returnvalues")
2017-12-31 03:45:27 +00:00
return ReturnStmt(self.sourceref, a, x, y)
2017-12-21 13:52:30 +00:00
2017-12-31 03:45:27 +00:00
def parse_asm(self) -> InlineAsm:
2017-12-21 13:52:30 +00:00
line = self.next_line()
aline = line.split()
if not len(aline) == 2 or aline[0] != "asm" or aline[1] != "{":
raise self.PError("invalid asm start")
asmlines = [] # type: List[str]
while True:
line = self.next_line()
if line.strip() == "}":
2017-12-31 03:45:27 +00:00
return InlineAsm(asmlines, self.sourceref)
2017-12-25 18:09:10 +00:00
# asm can refer to other symbols as well, track subroutine usage
2017-12-25 21:22:19 +00:00
splits = line.split(maxsplit=1)
if len(splits) == 2:
for match in re.finditer(r"(?P<symbol>[a-zA-Z_$][a-zA-Z0-9_\.]+)", splits[1]):
name = match.group("symbol")
if name[0] == '$':
continue
try:
if '.' not in name:
name = self.cur_block.symbols.parent.name + '.' + name
_, symbol = self.lookup_with_ppsymbols(name)
except ParseError:
pass
else:
if isinstance(symbol, SubroutineDef):
self.result.sub_used_by(symbol, self.sourceref)
2017-12-21 13:52:30 +00:00
asmlines.append(line)
2017-12-31 03:45:27 +00:00
def parse_asminclude(self, line: str) -> InlineAsm:
2017-12-21 13:52:30 +00:00
aline = line.split()
if len(aline) < 2:
raise self.PError("invalid asminclude or asmbinary statement")
filename = aline[1]
if not filename.startswith('"') or not filename.endswith('"'):
raise self.PError("filename must be between quotes")
filename = filename[1:-1]
if not filename:
raise self.PError("invalid filename")
filename_in_sourcedir = os.path.join(os.path.split(self.sourceref.file)[0], filename)
filename_in_output_location = os.path.join(self.outputdir, filename)
if not os.path.isfile(filename_in_sourcedir):
raise self.PError("included file not found")
print("copying included file to output location:", filename)
shutil.copy(filename_in_sourcedir, filename_in_output_location)
if aline[0] == "asminclude":
if len(aline) == 3:
scopename = aline[2]
lines = ['{:s}\t.binclude "{:s}"'.format(scopename, filename)]
else:
raise self.PError("invalid asminclude statement")
2017-12-31 03:45:27 +00:00
return InlineAsm(lines, self.sourceref)
2017-12-21 13:52:30 +00:00
elif aline[0] == "asmbinary":
if len(aline) == 4:
offset = parse_expr_as_int(aline[2], None, None, self.sourceref)
length = parse_expr_as_int(aline[3], None, None, self.sourceref)
2017-12-21 13:52:30 +00:00
lines = ['\t.binary "{:s}", ${:04x}, ${:04x}'.format(filename, offset, length)]
elif len(aline) == 3:
offset = parse_expr_as_int(aline[2], None, None, self.sourceref)
2017-12-21 13:52:30 +00:00
lines = ['\t.binary "{:s}", ${:04x}'.format(filename, offset)]
elif len(aline) == 2:
lines = ['\t.binary "{:s}"'.format(filename)]
else:
raise self.PError("invalid asmbinary statement")
2017-12-31 03:45:27 +00:00
return InlineAsm(lines, self.sourceref)
2017-12-21 13:52:30 +00:00
else:
raise self.PError("invalid statement")
2017-12-31 03:45:27 +00:00
def parse_expression(self, text: str, is_indirect=False) -> Value:
2017-12-21 13:52:30 +00:00
# parse an expression into whatever it is (primitive value, register, memory, register, etc)
text = text.strip()
if not text:
raise self.PError("value expected")
if text[0] == '#':
2017-12-23 13:36:23 +00:00
if is_indirect:
raise self.PError("using the address-of something in an indirect value makes no sense")
2017-12-21 13:52:30 +00:00
# take the pointer (memory address) from the thing that follows this
expression = self.parse_expression(text[1:])
2017-12-31 03:45:27 +00:00
if isinstance(expression, StringValue):
2017-12-21 13:52:30 +00:00
return expression
2017-12-31 03:45:27 +00:00
elif isinstance(expression, MemMappedValue):
return IntegerValue(expression.address, self.sourceref, datatype=DataType.WORD, name=expression.name)
2017-12-21 13:52:30 +00:00
else:
2018-01-01 04:04:04 +00:00
raise self.PError("cannot take the address of type " + expression.__class__.__name__)
2017-12-27 23:44:17 +00:00
elif text[0] in "-.0123456789$%~":
number = parse_expr_as_number(text, self.cur_block.symbols, self.ppsymbols, self.sourceref)
2017-12-21 13:52:30 +00:00
try:
if type(number) is int:
2017-12-31 03:45:27 +00:00
return IntegerValue(int(number), self.sourceref)
2017-12-21 13:52:30 +00:00
elif type(number) is float:
2017-12-31 03:45:27 +00:00
return FloatValue(number, self.sourceref)
2017-12-21 13:52:30 +00:00
else:
raise TypeError("invalid number type")
except (ValueError, OverflowError) as ex:
raise self.PError(str(ex))
elif text in REGISTER_WORDS:
2017-12-31 03:45:27 +00:00
return RegisterValue(text, DataType.WORD, self.sourceref)
2017-12-28 03:20:59 +00:00
elif text in REGISTER_BYTES | REGISTER_SBITS:
2017-12-31 03:45:27 +00:00
return RegisterValue(text, DataType.BYTE, self.sourceref)
2017-12-21 13:52:30 +00:00
elif (text.startswith("'") and text.endswith("'")) or (text.startswith('"') and text.endswith('"')):
strvalue = parse_expr_as_string(text, self.cur_block.symbols, self.ppsymbols, self.sourceref)
2017-12-21 13:52:30 +00:00
if len(strvalue) == 1:
petscii_code = char_to_bytevalue(strvalue)
2017-12-31 03:45:27 +00:00
return IntegerValue(petscii_code, self.sourceref)
return StringValue(strvalue, self.sourceref)
2017-12-21 13:52:30 +00:00
elif text == "true":
2017-12-31 03:45:27 +00:00
return IntegerValue(1, self.sourceref)
2017-12-21 13:52:30 +00:00
elif text == "false":
2017-12-31 03:45:27 +00:00
return IntegerValue(0, self.sourceref)
2017-12-21 13:52:30 +00:00
elif self.is_identifier(text):
2017-12-25 21:22:19 +00:00
symblock, sym = self.lookup_with_ppsymbols(text)
if isinstance(sym, (VariableDef, ConstantDef)):
2017-12-21 13:52:30 +00:00
constant = isinstance(sym, ConstantDef)
if self.cur_block is symblock:
2017-12-21 13:52:30 +00:00
symbolname = sym.name
else:
symbolname = "{:s}.{:s}".format(sym.blockname, sym.name)
if isinstance(sym, VariableDef) and sym.register:
2017-12-31 03:45:27 +00:00
return RegisterValue(sym.register, sym.type, self.sourceref, name=symbolname)
2017-12-21 13:52:30 +00:00
elif sym.type in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
if isinstance(sym, ConstantDef):
2017-12-23 00:53:48 +00:00
if sym.type == DataType.FLOAT:
2017-12-31 03:45:27 +00:00
return FloatValue(sym.value, self.sourceref, sym.name) # type: ignore
2017-12-23 00:53:48 +00:00
elif sym.type in (DataType.BYTE, DataType.WORD):
2017-12-31 03:45:27 +00:00
return IntegerValue(sym.value, self.sourceref, datatype=sym.type, name=sym.name) # type: ignore
2017-12-23 00:53:48 +00:00
elif sym.type in STRING_DATATYPES:
2017-12-31 03:45:27 +00:00
return StringValue(sym.value, self.sourceref, sym.name, True) # type: ignore
2017-12-23 00:53:48 +00:00
else:
raise TypeError("invalid const type", sym.type)
2017-12-21 13:52:30 +00:00
else:
2017-12-31 03:45:27 +00:00
return MemMappedValue(sym.address, sym.type, sym.length, self.sourceref, name=symbolname, constant=constant)
2017-12-21 13:52:30 +00:00
elif sym.type in STRING_DATATYPES:
2017-12-31 03:45:27 +00:00
return StringValue(sym.value, self.sourceref, name=symbolname, constant=constant) # type: ignore
2017-12-21 13:52:30 +00:00
elif sym.type == DataType.MATRIX:
raise self.PError("cannot manipulate matrix directly, use one of the matrix procedures")
elif sym.type == DataType.BYTEARRAY or sym.type == DataType.WORDARRAY:
raise self.PError("cannot manipulate array directly, use one of the array procedures")
else:
2017-12-23 00:53:48 +00:00
raise self.PError("invalid symbol type")
elif isinstance(sym, LabelDef):
name = sym.name if symblock is self.cur_block else sym.blockname + '.' + sym.name
2017-12-31 03:45:27 +00:00
return MemMappedValue(None, DataType.WORD, 1, self.sourceref, name, True)
2017-12-23 00:53:48 +00:00
elif isinstance(sym, SubroutineDef):
2017-12-25 18:09:10 +00:00
self.result.sub_used_by(sym, self.sourceref)
2017-12-23 00:53:48 +00:00
name = sym.name if symblock is self.cur_block else sym.blockname + '.' + sym.name
2017-12-31 03:45:27 +00:00
return MemMappedValue(sym.address, DataType.WORD, 1, self.sourceref, name, True)
2017-12-21 13:52:30 +00:00
else:
2017-12-23 00:53:48 +00:00
raise self.PError("invalid symbol type")
2017-12-21 13:52:30 +00:00
elif text.startswith('[') and text.endswith(']'):
2017-12-23 00:53:48 +00:00
return self.parse_indirect_value(text)[1]
2017-12-21 13:52:30 +00:00
else:
2017-12-28 03:20:59 +00:00
raise self.PError("invalid single value '" + text + "'") # @todo understand complex expressions
2017-12-21 13:52:30 +00:00
2017-12-31 03:45:27 +00:00
def parse_indirect_value(self, text: str, allow_mmapped_for_call: bool=False) -> Tuple[str, IndirectValue]:
2017-12-23 00:53:48 +00:00
indirect = text[1:-1].strip()
indirect2, sep, typestr = indirect.rpartition('.')
type_modifier = None
if sep:
if typestr in ("byte", "word", "float"):
type_modifier, type_len, _ = self.get_datatype(sep + typestr)
indirect = indirect2
2017-12-23 13:36:23 +00:00
expr = self.parse_expression(indirect, True)
2017-12-31 03:45:27 +00:00
if not isinstance(expr, (IntegerValue, MemMappedValue, RegisterValue)):
2017-12-23 00:53:48 +00:00
raise self.PError("only integers, memmapped vars, and registers can be used in an indirect value")
2017-12-23 13:36:23 +00:00
if type_modifier is None:
2017-12-31 03:45:27 +00:00
if isinstance(expr, (RegisterValue, MemMappedValue)):
2017-12-23 13:36:23 +00:00
type_modifier = expr.datatype
else:
type_modifier = DataType.BYTE
2017-12-31 03:45:27 +00:00
if isinstance(expr, IntegerValue):
2017-12-23 13:36:23 +00:00
if type_modifier not in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
2017-12-23 00:53:48 +00:00
raise self.PError("invalid type modifier for the value's datatype")
2017-12-31 03:45:27 +00:00
elif isinstance(expr, MemMappedValue):
2017-12-24 23:15:04 +00:00
if allow_mmapped_for_call:
if type_modifier and expr.datatype != type_modifier:
raise self.PError("invalid type modifier for the value's datatype, must be " + expr.datatype.name)
else:
raise self.PError("use variable directly instead of using indirect addressing")
2017-12-31 03:45:27 +00:00
return indirect, IndirectValue(expr, type_modifier, self.sourceref)
2017-12-23 00:53:48 +00:00
2017-12-21 13:52:30 +00:00
def is_identifier(self, name: str) -> bool:
if name.isidentifier():
return True
blockname, sep, name = name.partition(".")
if sep:
return blockname.isidentifier() and name.isidentifier()
return False
2017-12-31 03:45:27 +00:00
def lookup_with_ppsymbols(self, dottedname: str) -> Tuple[Block, Union[SymbolDefinition, SymbolTable]]:
2017-12-25 21:22:19 +00:00
# Tries to find a symbol, if it cannot be located, the symbol table from the preprocess parse phase is consulted as well
symblock, sym = self.cur_block.lookup(dottedname)
2017-12-25 21:22:19 +00:00
if sym is None and self.ppsymbols:
# symbol is not (yet) known, see if the symbols from the preprocess parse phase know about it
if '.' not in dottedname:
dottedname = self.cur_block.name + '.' + dottedname
try:
2017-12-25 21:22:19 +00:00
symtable, sym = self.ppsymbols.lookup(dottedname)
assert dottedname.startswith(symtable.name)
symblock = None # the block might not have been parsed yet, so just return this instead
except (LookupError, SymbolError) as x:
raise self.PError(str(x))
return symblock, sym
2017-12-21 13:52:30 +00:00
def _size_from_arraydecl(self, decl: str) -> int:
return parse_expr_as_int(decl[:-1].split("(")[-1], self.cur_block.symbols, self.ppsymbols, self.sourceref)
2017-12-21 13:52:30 +00:00
def _size_from_matrixdecl(self, decl: str) -> Tuple[int, int]:
dimensions = decl[:-1].split("(")[-1]
try:
xs, ys = dimensions.split(",")
except ValueError:
raise self.PError("invalid matrix dimensions")
return (parse_expr_as_int(xs, self.cur_block.symbols, self.ppsymbols, self.sourceref),
parse_expr_as_int(ys, self.cur_block.symbols, self.ppsymbols, self.sourceref))
2017-12-21 13:52:30 +00:00
def coerce_value(self, sourceref: SourceRef, datatype: DataType, value: PrimitiveType) -> Tuple[bool, PrimitiveType]:
# if we're a BYTE type, and the value is a single character, convert it to the numeric value
if datatype in (DataType.BYTE, DataType.BYTEARRAY, DataType.MATRIX) and isinstance(value, str):
if len(value) == 1:
return True, char_to_bytevalue(value)
# if we're an integer value and the passed value is float, truncate it (and give a warning)
2018-01-01 15:41:08 +00:00
if datatype in (DataType.BYTE, DataType.WORD, DataType.MATRIX) and isinstance(value, float):
frac = math.modf(value)
if frac != 0:
2017-12-31 14:50:50 +00:00
self.print_warning("float value truncated ({} to datatype {})".format(value, datatype.name))
return True, int(value)
return False, value
@staticmethod
def to_hex(number: int) -> str:
# 0..255 -> "$00".."$ff"
# 256..65536 -> "$0100".."$ffff"
if 0 <= number < 0x100:
return "${:02x}".format(number)
if number < 0x10000:
return "${:04x}".format(number)
raise OverflowError(number)
2017-12-21 13:52:30 +00:00
def psplit(self, sentence: str, separators: str=" \t", lparen: str="(", rparen: str=")") -> List[str]:
"""split a sentence but not on separators within parenthesis"""
nb_brackets = 0
sentence = sentence.strip(separators) # get rid of leading/trailing seps
indices = [0]
for i, c in enumerate(sentence):
if c == lparen:
nb_brackets += 1
elif c == rparen:
nb_brackets -= 1
elif c in separators and nb_brackets == 0:
indices.append(i)
# handle malformed string
if nb_brackets < 0:
raise self.PError("syntax error")
indices.append(len(sentence))
# handle missing closing parentheses
if nb_brackets > 0:
raise self.PError("syntax error")
result = [sentence[i:j].strip(separators) for i, j in zip(indices, indices[1:])]
return list(filter(None, result)) # remove empty strings
2017-12-31 03:45:27 +00:00
def parse_if_condition(self, ifpart: str, conditionpart: str) -> IfCondition:
2017-12-27 18:01:14 +00:00
if ifpart == "if":
ifstatus = "true"
else:
ifstatus = ifpart[3:]
2017-12-31 03:45:27 +00:00
if ifstatus not in IfCondition.IF_STATUSES:
2017-12-27 18:01:14 +00:00
raise self.PError("invalid if form")
if conditionpart:
2017-12-28 18:08:33 +00:00
if ifstatus not in ("true", "not", "zero"):
raise self.PError("can only have if[_true], if_not or if_zero when using a comparison expression")
2017-12-27 18:01:14 +00:00
left, operator, right = parse_expr_as_comparison(conditionpart, self.sourceref)
leftv = self.parse_expression(left)
2017-12-31 03:45:27 +00:00
if not operator and isinstance(leftv, (IntegerValue, FloatValue, StringValue)):
2017-12-27 18:01:14 +00:00
raise self.PError("condition is a constant value")
2017-12-31 03:45:27 +00:00
if isinstance(leftv, RegisterValue):
2017-12-27 18:01:14 +00:00
if leftv.register in {"SC", "SZ", "SI"}:
raise self.PError("cannot use a status bit register explicitly in a condition")
if operator:
rightv = self.parse_expression(right)
else:
rightv = None
if leftv == rightv:
raise self.PError("left and right values in comparison are identical")
2017-12-31 03:45:27 +00:00
result = IfCondition(ifstatus, leftv, operator, rightv, self.sourceref)
2017-12-27 18:01:14 +00:00
else:
2017-12-31 03:45:27 +00:00
result = IfCondition(ifstatus, None, "", None, self.sourceref)
2017-12-28 18:08:33 +00:00
if result.make_if_true():
self.print_warning("if_not condition inverted to if")
return result
2017-12-27 18:01:14 +00:00
2017-12-30 12:34:52 +00:00
def check_import_okay(self, filename: str) -> bool:
if filename == self.sourceref.file and not filename.endswith("il65lib.ill"):
raise self.PError("can't import itself")
if filename in self.existing_imports:
return False
self.existing_imports.add(filename)
return True
2017-12-21 13:52:30 +00:00
class Optimizer:
def __init__(self, parseresult: ParseResult) -> None:
self.parsed = parseresult
def optimize(self) -> ParseResult:
print("\noptimizing parse tree")
2017-12-28 22:03:59 +00:00
for block in self.parsed.all_blocks():
2017-12-25 12:20:23 +00:00
self.remove_identity_assigns(block)
2017-12-21 13:52:30 +00:00
self.combine_assignments_into_multi(block)
self.optimize_multiassigns(block)
2017-12-25 18:09:10 +00:00
self.remove_unused_subroutines(block)
2017-12-27 18:01:14 +00:00
self.optimize_compare_with_zero(block)
2017-12-21 13:52:30 +00:00
return self.parsed
2017-12-31 03:45:27 +00:00
def optimize_compare_with_zero(self, block: Block) -> None:
2017-12-27 18:01:14 +00:00
# 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:
2017-12-31 03:45:27 +00:00
if isinstance(stmt, CallStmt):
2017-12-27 23:44:17 +00:00
cond = stmt.condition
2017-12-31 03:45:27 +00:00
if cond and isinstance(cond.rvalue, (IntegerValue, FloatValue)) and cond.rvalue.value == 0:
2017-12-27 23:44:17 +00:00
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:
2017-12-31 03:10:27 +00:00
print("{}: simplified comparison with zero".format(stmt.sourceref))
2017-12-27 23:44:17 +00:00
2017-12-31 03:45:27 +00:00
def combine_assignments_into_multi(self, block: Block) -> None:
2017-12-21 13:52:30 +00:00
# fold multiple consecutive assignments with the same rvalue into one multi-assignment
2017-12-31 03:45:27 +00:00
statements = [] # type: List[_AstNode]
2017-12-21 13:52:30 +00:00
multi_assign_statement = None
for stmt in block.statements:
2017-12-31 03:45:27 +00:00
if isinstance(stmt, AssignmentStmt) and not isinstance(stmt, AugmentedAssignmentStmt):
2017-12-21 13:52:30 +00:00
if multi_assign_statement and multi_assign_statement.right == stmt.right:
multi_assign_statement.leftvalues.extend(stmt.leftvalues)
2017-12-31 03:10:27 +00:00
print("{}: joined with previous line into multi-assign statement".format(stmt.sourceref))
2017-12-21 13:52:30 +00:00
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
2017-12-31 03:45:27 +00:00
def optimize_multiassigns(self, block: Block) -> None:
# optimize multi-assign statements.
for stmt in block.statements:
2017-12-31 03:45:27 +00:00
if isinstance(stmt, AssignmentStmt) and len(stmt.leftvalues) > 1:
# remove duplicates
lvalues = list(set(stmt.leftvalues))
if len(lvalues) != len(stmt.leftvalues):
2017-12-31 03:10:27 +00:00
print("{}: removed duplicate assignment targets".format(stmt.sourceref))
# change order: first registers, then zp addresses, then non-zp addresses, then the rest (if any)
2017-12-24 23:15:04 +00:00
stmt.leftvalues = list(sorted(lvalues, key=_value_sortkey))
2017-12-31 03:45:27 +00:00
def remove_identity_assigns(self, block: Block) -> None:
2017-12-25 12:20:23 +00:00
have_removed_stmts = False
for index, stmt in enumerate(list(block.statements)):
2017-12-31 03:45:27 +00:00
if isinstance(stmt, AssignmentStmt):
2017-12-31 03:10:27 +00:00
stmt.remove_identity_lvalues()
2017-12-25 12:20:23 +00:00
if not stmt.leftvalues:
2017-12-31 03:10:27 +00:00
print("{}: removed identity assignment statement".format(stmt.sourceref))
2017-12-25 12:20:23 +00:00
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]
2017-12-31 03:45:27 +00:00
def remove_unused_subroutines(self, block: Block) -> None:
2017-12-25 18:09:10 +00:00
# some symbols are used by the emitted assembly code from the code generator,
# and should never be removed or the assembler will fail
2017-12-31 14:50:50 +00:00
# @todo make this dynamic
2018-01-01 16:56:55 +00:00
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"}
2017-12-25 18:09:10 +00:00
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.discard_sub(sub.name)
discarded.append(sub.name)
if discarded:
2017-12-30 12:34:52 +00:00
print("{}: discarded {:d} unused subroutines from block '{:s}'".format(block.sourceref, len(discarded), block.name))
2017-12-25 18:09:10 +00:00
2017-12-21 13:52:30 +00:00
2017-12-31 03:45:27 +00:00
def _value_sortkey(value: Value) -> int:
if isinstance(value, RegisterValue):
2017-12-21 13:52:30 +00:00
num = 0
for char in value.register:
num *= 100
num += ord(char)
return num
2017-12-31 03:45:27 +00:00
elif isinstance(value, MemMappedValue):
2017-12-24 23:15:04 +00:00
if value.address is None:
return 99999999
2017-12-21 13:52:30 +00:00
if value.address < 0x100:
return 10000 + value.address
else:
return 20000 + value.address
else:
return 99999999