mirror of
https://github.com/irmen/prog8.git
synced 2025-02-16 22:30:46 +00:00
generalized Ast node tree by giving all classes .nodes attribute
This commit is contained in:
parent
9b77dcc6b8
commit
861379c4d7
211
il65/compile.py
211
il65/compile.py
@ -13,7 +13,8 @@ from typing import Optional, Tuple, Set, Dict, List, Any, no_type_check
|
||||
import attr
|
||||
from .plyparse import parse_file, ParseError, Module, Directive, Block, Subroutine, Scope, VarDef, LiteralValue, \
|
||||
SubCall, Goto, Return, Assignment, InlineAssembly, Register, Expression, ProgramFormat, ZpOptions,\
|
||||
SymbolName, Dereference, AddressOf, IncrDecr, Label, AstNode, datatype_of, coerce_constant_value, UndefinedSymbolError
|
||||
SymbolName, Dereference, AddressOf, IncrDecr, AstNode, datatype_of, coerce_constant_value, \
|
||||
check_symbol_definition, UndefinedSymbolError
|
||||
from .plylex import SourceRef, print_bold
|
||||
from .datatypes import DataType, VarType
|
||||
|
||||
@ -23,9 +24,9 @@ class CompileError(Exception):
|
||||
|
||||
|
||||
class PlyParser:
|
||||
def __init__(self, parsing_import: bool=False) -> None:
|
||||
def __init__(self, imported_module: bool=False) -> None:
|
||||
self.parse_errors = 0
|
||||
self.parsing_import = parsing_import
|
||||
self.imported_module = imported_module
|
||||
|
||||
def parse_file(self, filename: str) -> Module:
|
||||
print("parsing:", filename)
|
||||
@ -34,15 +35,17 @@ class PlyParser:
|
||||
module = parse_file(filename, self.lexer_error)
|
||||
self.check_directives(module)
|
||||
self.process_imports(module)
|
||||
self.check_all_symbolnames(module)
|
||||
self.create_multiassigns(module)
|
||||
self.check_and_merge_zeropages(module)
|
||||
self.process_all_expressions_and_symbolnames(module)
|
||||
if not self.parsing_import:
|
||||
# these shall only be done on the main module after all imports have been done:
|
||||
self.apply_directive_options(module)
|
||||
self.determine_subroutine_usage(module)
|
||||
self.semantic_check(module)
|
||||
self.allocate_zeropage_vars(module)
|
||||
return module # XXX
|
||||
# if not self.parsing_import:
|
||||
# # these shall only be done on the main module after all imports have been done:
|
||||
# self.apply_directive_options(module)
|
||||
# self.determine_subroutine_usage(module)
|
||||
# self.semantic_check(module)
|
||||
# self.allocate_zeropage_vars(module)
|
||||
except ParseError as x:
|
||||
self.handle_parse_error(x)
|
||||
if self.parse_errors:
|
||||
@ -69,35 +72,33 @@ class PlyParser:
|
||||
raise ParseError("last statement in a block/subroutine must be a return or goto, "
|
||||
"(or %noreturn directive to silence this error)", last_stmt.sourceref)
|
||||
|
||||
def semantic_check(self, module: Module) -> None:
|
||||
# perform semantic analysis / checks on the syntactic parse tree we have so far
|
||||
# (note: symbol names have already been checked to exist when we start this)
|
||||
for block, parent in module.all_scopes():
|
||||
assert isinstance(block, (Module, Block, Subroutine))
|
||||
assert parent is None or isinstance(parent, (Module, Block, Subroutine))
|
||||
previous_stmt = None
|
||||
for stmt in block.nodes:
|
||||
if isinstance(stmt, SubCall):
|
||||
if isinstance(stmt.target.target, SymbolName):
|
||||
subdef = block.scope.lookup(stmt.target.target.name)
|
||||
self.check_subroutine_arguments(stmt, subdef)
|
||||
if isinstance(stmt, Subroutine):
|
||||
# the previous statement (if any) must be a Goto or Return
|
||||
if previous_stmt and not isinstance(previous_stmt, (Goto, Return, VarDef, Subroutine)):
|
||||
raise ParseError("statement preceding subroutine must be a goto or return or another subroutine", stmt.sourceref)
|
||||
if isinstance(previous_stmt, Subroutine):
|
||||
# the statement after a subroutine can not be some random executable instruction because it could not be reached
|
||||
if not isinstance(stmt, (Subroutine, Label, Directive, InlineAssembly, VarDef)):
|
||||
raise ParseError("statement following a subroutine can't be runnable code, "
|
||||
"at least use a label first", stmt.sourceref)
|
||||
previous_stmt = stmt
|
||||
if isinstance(stmt, IncrDecr):
|
||||
if isinstance(stmt.target, SymbolName):
|
||||
symdef = block.scope.lookup(stmt.target.name)
|
||||
if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST:
|
||||
raise ParseError("cannot modify a constant", stmt.sourceref)
|
||||
if parent and block.name != "ZP" and not isinstance(stmt, (Return, Goto)):
|
||||
self._check_last_statement_is_return(stmt)
|
||||
# def semantic_check(self, module: Module) -> None:
|
||||
# # perform semantic analysis / checks on the syntactic parse tree we have so far
|
||||
# # (note: symbol names have already been checked to exist when we start this)
|
||||
# for node, parent in module.all_nodes():
|
||||
# previous_stmt = None
|
||||
# if isinstance(node, SubCall):
|
||||
# if isinstance(node.target, SymbolName):
|
||||
# subdef = block.scope.lookup(stmt.target.target.name)
|
||||
# self.check_subroutine_arguments(stmt, subdef)
|
||||
# if isinstance(stmt, Subroutine):
|
||||
# # the previous statement (if any) must be a Goto or Return
|
||||
# if previous_stmt and not isinstance(previous_stmt, (Goto, Return, VarDef, Subroutine)):
|
||||
# raise ParseError("statement preceding subroutine must be a goto or return or another subroutine", stmt.sourceref)
|
||||
# if isinstance(previous_stmt, Subroutine):
|
||||
# # the statement after a subroutine can not be some random executable instruction because it could not be reached
|
||||
# if not isinstance(stmt, (Subroutine, Label, Directive, InlineAssembly, VarDef)):
|
||||
# raise ParseError("statement following a subroutine can't be runnable code, "
|
||||
# "at least use a label first", stmt.sourceref)
|
||||
# previous_stmt = stmt
|
||||
# if isinstance(stmt, IncrDecr):
|
||||
# if isinstance(stmt.target, SymbolName):
|
||||
# symdef = block.scope.lookup(stmt.target.name)
|
||||
# if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST:
|
||||
# raise ParseError("cannot modify a constant", stmt.sourceref)
|
||||
#
|
||||
# if parent and block.name != "ZP" and not isinstance(stmt, (Return, Goto)):
|
||||
# self._check_last_statement_is_return(stmt)
|
||||
|
||||
def check_subroutine_arguments(self, call: SubCall, subdef: Subroutine) -> None:
|
||||
# @todo must be moved to expression processing, or, restructure whole AST tree walking to make it easier to walk over everything
|
||||
@ -110,8 +111,9 @@ class PlyParser:
|
||||
|
||||
def check_and_merge_zeropages(self, module: Module) -> None:
|
||||
# merge all ZP blocks into one
|
||||
# XXX done: converted to new nodes
|
||||
zeropage = None
|
||||
for block in list(module.scope.filter_nodes(Block)):
|
||||
for block in module.all_nodes([Block]):
|
||||
if block.name == "ZP":
|
||||
if zeropage:
|
||||
# merge other ZP block into first ZP block
|
||||
@ -124,7 +126,7 @@ class PlyParser:
|
||||
raise ParseError("only variables and directives allowed in zeropage block", node.sourceref)
|
||||
else:
|
||||
zeropage = block
|
||||
module.scope.remove_node(block)
|
||||
block.parent.remove_node(block)
|
||||
if zeropage:
|
||||
# add the zero page again, as the very first block
|
||||
module.scope.add_node(zeropage, 0)
|
||||
@ -146,37 +148,42 @@ class PlyParser:
|
||||
except CompileError as x:
|
||||
raise ParseError(str(x), vardef.sourceref)
|
||||
|
||||
@no_type_check
|
||||
def check_all_symbolnames(self, module: Module) -> None:
|
||||
for node in module.all_nodes([SymbolName]):
|
||||
check_symbol_definition(node.name, node.my_scope(), node.sourceref)
|
||||
|
||||
def process_all_expressions_and_symbolnames(self, module: Module) -> None:
|
||||
# process/simplify all expressions (constant folding etc), and check all symbol names
|
||||
# process/simplify all expressions (constant folding etc)
|
||||
encountered_blocks = set()
|
||||
for block, parent in module.all_scopes():
|
||||
parentname = (parent.name + ".") if parent else ""
|
||||
blockname = parentname + block.name
|
||||
if blockname in encountered_blocks:
|
||||
raise ValueError("block names not unique:", blockname)
|
||||
encountered_blocks.add(blockname)
|
||||
for node in block.nodes:
|
||||
try:
|
||||
node.verify_symbol_names(block.scope)
|
||||
node.process_expressions(block.scope)
|
||||
except ParseError:
|
||||
raise
|
||||
except Exception as x:
|
||||
self.handle_internal_error(x, "process_expressions of node {} in block {}".format(node, block.name))
|
||||
if isinstance(node, IncrDecr) and node.howmuch not in (0, 1):
|
||||
_, node.howmuch = coerce_constant_value(datatype_of(node.target, block.scope), node.howmuch, node.sourceref)
|
||||
elif isinstance(node, Assignment):
|
||||
lvalue_types = set(datatype_of(lv, block.scope) for lv in node.left)
|
||||
if len(lvalue_types) == 1:
|
||||
_, node.right = coerce_constant_value(lvalue_types.pop(), node.right, node.sourceref)
|
||||
else:
|
||||
for lv_dt in lvalue_types:
|
||||
coerce_constant_value(lv_dt, node.right, node.sourceref)
|
||||
for node in module.all_nodes():
|
||||
if isinstance(node, Block):
|
||||
parentname = (node.parent.name + ".") if node.parent else ""
|
||||
blockname = parentname + node.name
|
||||
if blockname in encountered_blocks:
|
||||
raise ValueError("block names not unique:", blockname)
|
||||
encountered_blocks.add(blockname)
|
||||
elif isinstance(node, Expression):
|
||||
print("EXPRESSION", node) # XXX
|
||||
# try:
|
||||
# node.process_expressions(block.scope)
|
||||
# except ParseError:
|
||||
# raise
|
||||
# except Exception as x:
|
||||
# self.handle_internal_error(x, "process_expressions of node {} in block {}".format(node, block.name))
|
||||
elif isinstance(node, IncrDecr) and node.howmuch not in (0, 1):
|
||||
_, node.howmuch = coerce_constant_value(datatype_of(node.target, node.my_scope()), node.howmuch, node.sourceref)
|
||||
elif isinstance(node, Assignment):
|
||||
lvalue_types = set(datatype_of(lv, node.my_scope()) for lv in node.left.nodes)
|
||||
if len(lvalue_types) == 1:
|
||||
_, node.right = coerce_constant_value(lvalue_types.pop(), node.right, node.sourceref)
|
||||
else:
|
||||
for lv_dt in lvalue_types:
|
||||
coerce_constant_value(lv_dt, node.right, node.sourceref)
|
||||
|
||||
def create_multiassigns(self, module: Module) -> None:
|
||||
# create multi-assign statements from nested assignments (A=B=C=5),
|
||||
# and optimize TargetRegisters down to single Register if it's just one register.
|
||||
# XXX done: converted to new nodes
|
||||
def reduce_right(assign: Assignment) -> Assignment:
|
||||
if isinstance(assign.right, Assignment):
|
||||
right = reduce_right(assign.right)
|
||||
@ -184,12 +191,10 @@ class PlyParser:
|
||||
assign.right = right.right
|
||||
return assign
|
||||
|
||||
for block, parent in module.all_scopes():
|
||||
for node in block.nodes: # type: ignore
|
||||
if isinstance(node, Assignment):
|
||||
if isinstance(node.right, Assignment):
|
||||
multi = reduce_right(node)
|
||||
assert multi is node and len(multi.left) > 1 and not isinstance(multi.right, Assignment)
|
||||
for node in module.all_nodes([Assignment]):
|
||||
if isinstance(node.right, Assignment):
|
||||
multi = reduce_right(node)
|
||||
assert multi is node and len(multi.left) > 1 and not isinstance(multi.right, Assignment)
|
||||
|
||||
def apply_directive_options(self, module: Module) -> None:
|
||||
def set_save_registers(scope: Scope, save_dir: Directive) -> None:
|
||||
@ -284,7 +289,7 @@ class PlyParser:
|
||||
self._get_subroutine_usages_from_assignment(module.subroutine_usage, node, block.scope)
|
||||
print("----------SUBROUTINES IN USE-------------") # XXX
|
||||
import pprint
|
||||
pprint.pprint(module.subroutine_usage) # XXX
|
||||
pprint.pprint(module.subroutine_usage) # XXX
|
||||
print("----------/SUBROUTINES IN USE-------------") # XXX
|
||||
|
||||
def _get_subroutine_usages_from_subcall(self, usages: Dict[Tuple[str, str], Set[str]],
|
||||
@ -307,7 +312,7 @@ class PlyParser:
|
||||
elif isinstance(expr, LiteralValue):
|
||||
return
|
||||
elif isinstance(expr, Dereference):
|
||||
return self._get_subroutine_usages_from_expression(usages, expr.location, parent_scope)
|
||||
return self._get_subroutine_usages_from_expression(usages, expr.operand, parent_scope)
|
||||
elif isinstance(expr, AddressOf):
|
||||
return self._get_subroutine_usages_from_expression(usages, expr.name, parent_scope)
|
||||
elif isinstance(expr, SymbolName):
|
||||
@ -365,34 +370,31 @@ class PlyParser:
|
||||
usages[(namespace, symbol.name)].add(str(asmnode.sourceref))
|
||||
|
||||
def check_directives(self, module: Module) -> None:
|
||||
for node, parent in module.all_scopes():
|
||||
if isinstance(node, Module):
|
||||
# check module-level directives
|
||||
imports = set() # type: Set[str]
|
||||
for directive in node.scope.filter_nodes(Directive):
|
||||
if directive.name not in {"output", "zp", "address", "import", "saveregisters", "noreturn"}:
|
||||
raise ParseError("invalid directive in module", directive.sourceref)
|
||||
if directive.name == "import":
|
||||
if imports & set(directive.args):
|
||||
raise ParseError("duplicate import", directive.sourceref)
|
||||
imports |= set(directive.args)
|
||||
if isinstance(node, (Block, Subroutine)):
|
||||
# check block and subroutine-level directives
|
||||
first_node = True
|
||||
if not node.scope:
|
||||
continue
|
||||
for sub_node in node.scope.nodes:
|
||||
if isinstance(sub_node, Directive):
|
||||
if sub_node.name not in {"asmbinary", "asminclude", "breakpoint", "saveregisters", "noreturn"}:
|
||||
raise ParseError("invalid directive in " + node.__class__.__name__.lower(), sub_node.sourceref)
|
||||
if sub_node.name == "saveregisters" and not first_node:
|
||||
raise ParseError("saveregisters directive must be the first", sub_node.sourceref)
|
||||
first_node = False
|
||||
# XXX done: converted to new nodes
|
||||
imports = set() # type: Set[str]
|
||||
for node in module.all_nodes():
|
||||
if isinstance(node, Directive):
|
||||
assert isinstance(node.parent, Scope)
|
||||
if node.parent.level == "module":
|
||||
if node.name not in {"output", "zp", "address", "import", "saveregisters", "noreturn"}:
|
||||
raise ParseError("invalid directive in module", node.sourceref)
|
||||
if node.name == "import":
|
||||
if imports & set(node.args):
|
||||
raise ParseError("duplicate import", node.sourceref)
|
||||
imports |= set(node.args)
|
||||
else:
|
||||
if node.name not in {"asmbinary", "asminclude", "breakpoint", "saveregisters", "noreturn"}:
|
||||
raise ParseError("invalid directive in " + node.parent.__class__.__name__.lower(), node.sourceref)
|
||||
if node.name == "saveregisters":
|
||||
# it should be the first node in the scope
|
||||
if node.parent.nodes[0] is not node:
|
||||
raise ParseError("saveregisters directive must be first in this scope", node.sourceref)
|
||||
|
||||
def process_imports(self, module: Module) -> None:
|
||||
# (recursively) imports the modules
|
||||
# XXX done: converted to new nodes
|
||||
imported = []
|
||||
for directive in module.scope.filter_nodes(Directive):
|
||||
for directive in module.all_nodes([Directive]):
|
||||
if directive.name == "import":
|
||||
if len(directive.args) < 1:
|
||||
raise ParseError("missing argument(s) for import directive", directive.sourceref)
|
||||
@ -404,7 +406,7 @@ class PlyParser:
|
||||
imported_module.scope.parent_scope = module.scope
|
||||
imported.append(imported_module)
|
||||
self.parse_errors += import_parse_errors
|
||||
if not self.parsing_import:
|
||||
if not self.imported_module:
|
||||
# compiler support library is always imported (in main parser)
|
||||
filename = self.find_import_file("il65lib", module.sourceref.file)
|
||||
if filename:
|
||||
@ -414,13 +416,14 @@ class PlyParser:
|
||||
self.parse_errors += import_parse_errors
|
||||
else:
|
||||
raise FileNotFoundError("missing il65lib")
|
||||
# append the imported module's contents (blocks) at the end of the current module
|
||||
for imported_module in imported:
|
||||
for block in imported_module.scope.filter_nodes(Block):
|
||||
module.scope.add_node(block)
|
||||
# XXX append the imported module's contents (blocks) at the end of the current module
|
||||
# for block in (node for imported_module in imported
|
||||
# for node in imported_module.scope.nodes
|
||||
# if isinstance(node, Block)):
|
||||
# module.scope.add_node(block)
|
||||
|
||||
def import_file(self, filename: str) -> Tuple[Module, int]:
|
||||
sub_parser = PlyParser(parsing_import=True)
|
||||
sub_parser = PlyParser(imported_module=True)
|
||||
return sub_parser.parse_file(filename), sub_parser.parse_errors
|
||||
|
||||
def find_import_file(self, modulename: str, sourcefile: str) -> Optional[str]:
|
||||
@ -443,7 +446,7 @@ class PlyParser:
|
||||
out = sys.stdout
|
||||
if out.isatty():
|
||||
print("\x1b[1m", file=out)
|
||||
if self.parsing_import:
|
||||
if self.imported_module:
|
||||
print("Error (in imported file):", str(exc), file=out)
|
||||
else:
|
||||
print("Error:", str(exc), file=out)
|
||||
|
@ -10,7 +10,7 @@ import datetime
|
||||
from typing import TextIO, Callable
|
||||
from ..plylex import print_bold
|
||||
from ..plyparse import Module, Scope, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, AstNode, ZpOptions, \
|
||||
InlineAssembly, Return, Register, Goto, SubCall, Assignment, AugAssignment, IncrDecr
|
||||
InlineAssembly, Return, Register, Goto, SubCall, Assignment, AugAssignment, IncrDecr, AssignmentTargets
|
||||
from . import CodeError, to_hex
|
||||
from .variables import generate_block_init, generate_block_vars
|
||||
from .assignment import generate_assignment, generate_aug_assignment
|
||||
@ -190,15 +190,21 @@ class AssemblyGenerator:
|
||||
elif isinstance(stmt, Return):
|
||||
if stmt.value_A:
|
||||
reg = Register(name="A", sourceref=stmt.sourceref) # type: ignore
|
||||
assignment = Assignment(left=[reg], right=stmt.value_A, sourceref=stmt.sourceref) # type: ignore
|
||||
assignment = Assignment(sourceref=stmt.sourceref) # type: ignore
|
||||
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
|
||||
assignment.nodes.append(stmt.value_A)
|
||||
generate_assignment(out, assignment)
|
||||
if stmt.value_X:
|
||||
reg = Register(name="X", sourceref=stmt.sourceref) # type: ignore
|
||||
assignment = Assignment(left=[reg], right=stmt.value_X, sourceref=stmt.sourceref) # type: ignore
|
||||
assignment = Assignment(sourceref=stmt.sourceref) # type: ignore
|
||||
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
|
||||
assignment.nodes.append(stmt.value_X)
|
||||
generate_assignment(out, assignment)
|
||||
if stmt.value_Y:
|
||||
reg = Register(name="Y", sourceref=stmt.sourceref) # type: ignore
|
||||
assignment = Assignment(left=[reg], right=stmt.value_Y, sourceref=stmt.sourceref) # type: ignore
|
||||
assignment = Assignment(sourceref=stmt.sourceref) # type: ignore
|
||||
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
|
||||
assignment.nodes.append(stmt.value_Y)
|
||||
generate_assignment(out, assignment)
|
||||
out("\vrts")
|
||||
elif isinstance(stmt, InlineAssembly):
|
||||
|
@ -81,6 +81,7 @@ def main() -> None:
|
||||
print("\nParsing program source code.")
|
||||
parser = PlyParser()
|
||||
parsed_module = parser.parse_file(args.sourcefile)
|
||||
raise SystemExit("First fix the parser to iterate all nodes in the new way.") # XXX
|
||||
if parsed_module:
|
||||
if args.nooptimize:
|
||||
print_bold("not optimizing the parse tree!")
|
||||
|
@ -6,7 +6,7 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
from .plyparse import Module, Subroutine, Block, Directive, Assignment, AugAssignment, Goto, Expression, IncrDecr,\
|
||||
datatype_of, coerce_constant_value
|
||||
datatype_of, coerce_constant_value, AssignmentTargets
|
||||
from .plylex import print_warning, print_bold
|
||||
|
||||
|
||||
@ -47,14 +47,17 @@ class Optimizer:
|
||||
block.scope.remove_node(assignment)
|
||||
if assignment.right >= 8 and assignment.operator in ("<<=", ">>="):
|
||||
print("{}: shifting result is always zero".format(assignment.sourceref))
|
||||
new_stmt = Assignment(left=[assignment.left], right=0, sourceref=assignment.sourceref)
|
||||
new_stmt = Assignment(sourceref=assignment.sourceref)
|
||||
new_stmt.nodes.append(AssignmentTargets(nodes=[assignment.left], sourceref=assignment.sourceref))
|
||||
new_stmt.nodes.append(0)
|
||||
block.scope.replace_node(assignment, new_stmt)
|
||||
if assignment.operator in ("+=", "-=") and 0 < assignment.right < 256:
|
||||
howmuch = assignment.right
|
||||
if howmuch not in (0, 1):
|
||||
_, howmuch = coerce_constant_value(datatype_of(assignment.left, block.scope), howmuch, assignment.sourceref)
|
||||
new_stmt = IncrDecr(target=assignment.left, operator="++" if assignment.operator == "+=" else "--",
|
||||
new_stmt = IncrDecr(operator="++" if assignment.operator == "+=" else "--",
|
||||
howmuch=howmuch, sourceref=assignment.sourceref)
|
||||
new_stmt.target = assignment.left
|
||||
block.scope.replace_node(assignment, new_stmt)
|
||||
|
||||
def combine_assignments_into_multi(self):
|
||||
|
913
il65/plyparse.py
913
il65/plyparse.py
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
from il65 import datatypes
|
||||
from il65.plyparse import coerce_constant_value
|
||||
from il65.plyparse import coerce_constant_value, LiteralValue
|
||||
from il65.compile import ParseError
|
||||
from il65.plylex import SourceRef
|
||||
from il65.emit import to_hex, to_mflpt5
|
||||
@ -101,39 +101,42 @@ def test_char_to_bytevalue():
|
||||
|
||||
|
||||
def test_coerce_value():
|
||||
assert coerce_constant_value(datatypes.DataType.BYTE, 0) == (False, 0)
|
||||
assert coerce_constant_value(datatypes.DataType.BYTE, 255) == (False, 255)
|
||||
assert coerce_constant_value(datatypes.DataType.BYTE, '@') == (True, 64)
|
||||
assert coerce_constant_value(datatypes.DataType.WORD, 0) == (False, 0)
|
||||
assert coerce_constant_value(datatypes.DataType.WORD, 65535) == (False, 65535)
|
||||
assert coerce_constant_value(datatypes.DataType.WORD, '@') == (True, 64)
|
||||
assert coerce_constant_value(datatypes.DataType.FLOAT, -999.22) == (False, -999.22)
|
||||
assert coerce_constant_value(datatypes.DataType.FLOAT, 123.45) == (False, 123.45)
|
||||
assert coerce_constant_value(datatypes.DataType.FLOAT, '@') == (True, 64)
|
||||
assert coerce_constant_value(datatypes.DataType.BYTE, 5.678) == (True, 5)
|
||||
assert coerce_constant_value(datatypes.DataType.WORD, 5.678) == (True, 5)
|
||||
assert coerce_constant_value(datatypes.DataType.WORD, "string") == (False, "string"), "string (address) can be assigned to a word"
|
||||
assert coerce_constant_value(datatypes.DataType.STRING, "string") == (False, "string")
|
||||
assert coerce_constant_value(datatypes.DataType.STRING_P, "string") == (False, "string")
|
||||
assert coerce_constant_value(datatypes.DataType.STRING_S, "string") == (False, "string")
|
||||
assert coerce_constant_value(datatypes.DataType.STRING_PS, "string") == (False, "string")
|
||||
def lv(v) -> LiteralValue:
|
||||
return LiteralValue(value=v, sourceref=SourceRef("test", 1, 1))
|
||||
assert coerce_constant_value(datatypes.DataType.BYTE, lv(0)) == (False, lv(0))
|
||||
assert coerce_constant_value(datatypes.DataType.BYTE, lv(255)) == (False, lv(255))
|
||||
assert coerce_constant_value(datatypes.DataType.BYTE, lv('@')) == (True, lv(64))
|
||||
assert coerce_constant_value(datatypes.DataType.WORD, lv(0)) == (False, lv(0))
|
||||
assert coerce_constant_value(datatypes.DataType.WORD, lv(65535)) == (False, lv(65535))
|
||||
assert coerce_constant_value(datatypes.DataType.WORD, lv('@')) == (True, lv(64))
|
||||
assert coerce_constant_value(datatypes.DataType.FLOAT, lv(-999.22)) == (False, lv(-999.22))
|
||||
assert coerce_constant_value(datatypes.DataType.FLOAT, lv(123.45)) == (False, lv(123.45))
|
||||
assert coerce_constant_value(datatypes.DataType.FLOAT, lv('@')) == (True, lv(64))
|
||||
assert coerce_constant_value(datatypes.DataType.BYTE, lv(5.678)) == (True, lv(5))
|
||||
assert coerce_constant_value(datatypes.DataType.WORD, lv(5.678)) == (True, lv(5))
|
||||
assert coerce_constant_value(datatypes.DataType.WORD,
|
||||
lv("string")) == (False, lv("string")), "string (address) can be assigned to a word"
|
||||
assert coerce_constant_value(datatypes.DataType.STRING, lv("string")) == (False, lv("string"))
|
||||
assert coerce_constant_value(datatypes.DataType.STRING_P, lv("string")) == (False, lv("string"))
|
||||
assert coerce_constant_value(datatypes.DataType.STRING_S, lv("string")) == (False, lv("string"))
|
||||
assert coerce_constant_value(datatypes.DataType.STRING_PS, lv("string")) == (False, lv("string"))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.BYTE, -1)
|
||||
coerce_constant_value(datatypes.DataType.BYTE, lv(-1))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.BYTE, 256)
|
||||
coerce_constant_value(datatypes.DataType.BYTE, lv(256))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.BYTE, 256.12345)
|
||||
coerce_constant_value(datatypes.DataType.BYTE, lv(256.12345))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.WORD, -1)
|
||||
coerce_constant_value(datatypes.DataType.WORD, lv(-1))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.WORD, 65536)
|
||||
coerce_constant_value(datatypes.DataType.WORD, lv(65536))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.WORD, 65536.12345)
|
||||
coerce_constant_value(datatypes.DataType.WORD, lv(65536.12345))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.FLOAT, -1.7014118346e+38)
|
||||
coerce_constant_value(datatypes.DataType.FLOAT, lv(-1.7014118346e+38))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.FLOAT, 1.7014118347e+38)
|
||||
coerce_constant_value(datatypes.DataType.FLOAT, lv(1.7014118347e+38))
|
||||
with pytest.raises(TypeError):
|
||||
coerce_constant_value(datatypes.DataType.BYTE, "string")
|
||||
coerce_constant_value(datatypes.DataType.BYTE, lv("string"))
|
||||
with pytest.raises(TypeError):
|
||||
coerce_constant_value(datatypes.DataType.FLOAT, "string")
|
||||
coerce_constant_value(datatypes.DataType.FLOAT, lv("string"))
|
||||
|
@ -1,12 +1,13 @@
|
||||
from il65.plylex import lexer, tokens, find_tok_column, literals, reserved, SourceRef
|
||||
from il65.plyparse import parser, TokenFilter, Module, Subroutine, Block, Return, Scope, \
|
||||
VarDef, Expression, LiteralValue, Label, SubCall, CallTarget, SymbolName, Dereference
|
||||
from il65.datatypes import DataType, char_to_bytevalue
|
||||
from il65.plyparse import parser, connect_parents, TokenFilter, Module, Subroutine, Block, Return, Scope, \
|
||||
VarDef, Expression, LiteralValue, Label, SubCall, Dereference
|
||||
from il65.datatypes import DataType
|
||||
|
||||
|
||||
def lexer_error(sourceref: SourceRef, fmtstring: str, *args: str) -> None:
|
||||
print("ERROR: {}: {}".format(sourceref, fmtstring.format(*args)))
|
||||
|
||||
|
||||
lexer.error_function = lexer_error
|
||||
|
||||
|
||||
@ -112,6 +113,7 @@ def test_parser():
|
||||
lexer.source_filename = "sourcefile"
|
||||
filter = TokenFilter(lexer)
|
||||
result = parser.parse(input=test_source_1, tokenfunc=filter.token)
|
||||
connect_parents(result, None)
|
||||
assert isinstance(result, Module)
|
||||
assert result.name == "sourcefile"
|
||||
assert result.scope.name == "<sourcefile global scope>"
|
||||
@ -123,7 +125,6 @@ def test_parser():
|
||||
block = result.scope.lookup("block")
|
||||
assert isinstance(block, Block)
|
||||
assert block.name == "block"
|
||||
assert block.nodes is block.scope.nodes
|
||||
bool_vdef = block.scope.nodes[1]
|
||||
assert isinstance(bool_vdef, VarDef)
|
||||
assert isinstance(bool_vdef.value, Expression)
|
||||
@ -134,30 +135,26 @@ def test_parser():
|
||||
sub2 = block.scope.lookup("calculate")
|
||||
assert sub2 is sub
|
||||
assert sub2.lineref == "src l. 19"
|
||||
all_scopes = list(result.all_scopes())
|
||||
assert len(all_scopes) == 3
|
||||
assert isinstance(all_scopes[0][0], Module)
|
||||
assert all_scopes[0][1] is None
|
||||
assert isinstance(all_scopes[1][0], Block)
|
||||
assert isinstance(all_scopes[1][1], Module)
|
||||
assert isinstance(all_scopes[2][0], Subroutine)
|
||||
assert isinstance(all_scopes[2][1], Block)
|
||||
stmt = list(all_scopes[2][0].scope.filter_nodes(Return))
|
||||
assert len(stmt) == 1
|
||||
assert isinstance(stmt[0], Return)
|
||||
assert stmt[0].lineref == "src l. 20"
|
||||
all_nodes = list(result.all_nodes())
|
||||
assert len(all_nodes) == 12
|
||||
all_nodes = list(result.all_nodes([Subroutine]))
|
||||
assert len(all_nodes) == 1
|
||||
assert isinstance(all_nodes[0], Subroutine)
|
||||
assert isinstance(all_nodes[0].parent, Scope)
|
||||
assert all_nodes[0] in all_nodes[0].parent.nodes
|
||||
assert all_nodes[0].lineref == "src l. 19"
|
||||
assert all_nodes[0].parent.lineref == "src l. 8"
|
||||
|
||||
|
||||
def test_block_nodes():
|
||||
sref = SourceRef("file", 1, 1)
|
||||
sub1 = Subroutine(name="subaddr", param_spec=[], result_spec=[], address=0xc000, sourceref=sref)
|
||||
sub2 = Subroutine(name="subblock", param_spec=[], result_spec=[],
|
||||
scope=Scope(nodes=[Label(name="start", sourceref=sref)], sourceref=sref), sourceref=sref)
|
||||
sub2 = Subroutine(name="subblock", param_spec=[], result_spec=[], sourceref=sref)
|
||||
sub2.scope = Scope(nodes=[Label(name="start", sourceref=sref)], level="block", sourceref=sref)
|
||||
assert sub1.scope is None
|
||||
assert sub1.nodes == []
|
||||
assert sub2.scope is not None
|
||||
assert len(sub2.scope.nodes) > 0
|
||||
assert sub2.nodes is sub2.scope.nodes
|
||||
|
||||
|
||||
test_source_2 = """
|
||||
@ -173,20 +170,18 @@ def test_parser_2():
|
||||
lexer.source_filename = "sourcefile"
|
||||
filter = TokenFilter(lexer)
|
||||
result = parser.parse(input=test_source_2, tokenfunc=filter.token)
|
||||
block = result.nodes[0]
|
||||
call = block.nodes[0]
|
||||
connect_parents(result, None)
|
||||
block = result.scope.nodes[0]
|
||||
call = block.scope.nodes[0]
|
||||
assert isinstance(call, SubCall)
|
||||
assert len(call.arguments) == 2
|
||||
assert isinstance(call.target, CallTarget)
|
||||
assert call.target.target == 999
|
||||
assert call.target.address_of is False
|
||||
call = block.nodes[1]
|
||||
assert len(call.arguments.nodes) == 2
|
||||
assert isinstance(call.target, int)
|
||||
assert call.target == 999
|
||||
call = block.scope.nodes[1]
|
||||
assert isinstance(call, SubCall)
|
||||
assert len(call.arguments) == 0
|
||||
assert isinstance(call.target, CallTarget)
|
||||
assert isinstance(call.target.target, Dereference)
|
||||
assert call.target.target.location.name == "zz"
|
||||
assert call.target.address_of is False
|
||||
assert len(call.arguments.nodes) == 0
|
||||
assert isinstance(call.target, Dereference)
|
||||
assert call.target.operand.name == "zz"
|
||||
|
||||
|
||||
test_source_3 = """
|
||||
@ -198,33 +193,35 @@ test_source_3 = """
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def test_typespec():
|
||||
lexer.lineno = 1
|
||||
lexer.source_filename = "sourcefile"
|
||||
filter = TokenFilter(lexer)
|
||||
result = parser.parse(input=test_source_3, tokenfunc=filter.token)
|
||||
nodes = result.nodes[0].nodes
|
||||
assignment1, assignment2, assignment3, assignment4 = nodes
|
||||
connect_parents(result, None)
|
||||
block = result.scope.nodes[0]
|
||||
assignment1, assignment2, assignment3, assignment4 = block.scope.nodes
|
||||
assert assignment1.right.value == 5
|
||||
assert assignment2.right.value == 5
|
||||
assert assignment3.right.value == 5
|
||||
assert assignment4.right.value == 5
|
||||
assert len(assignment1.left) == 1
|
||||
assert len(assignment2.left) == 1
|
||||
assert len(assignment3.left) == 1
|
||||
assert len(assignment4.left) == 1
|
||||
t1 = assignment1.left[0]
|
||||
t2 = assignment2.left[0]
|
||||
t3 = assignment3.left[0]
|
||||
t4 = assignment4.left[0]
|
||||
assert len(assignment1.left.nodes) == 1
|
||||
assert len(assignment2.left.nodes) == 1
|
||||
assert len(assignment3.left.nodes) == 1
|
||||
assert len(assignment4.left.nodes) == 1
|
||||
t1 = assignment1.left.nodes[0]
|
||||
t2 = assignment2.left.nodes[0]
|
||||
t3 = assignment3.left.nodes[0]
|
||||
t4 = assignment4.left.nodes[0]
|
||||
assert isinstance(t1, Dereference)
|
||||
assert isinstance(t2, Dereference)
|
||||
assert isinstance(t3, Dereference)
|
||||
assert isinstance(t4, Dereference)
|
||||
assert t1.location == 0xc000
|
||||
assert t2.location == 0xc000
|
||||
assert t3.location == "AX"
|
||||
assert t4.location == "AX"
|
||||
assert t1.operand == 0xc000
|
||||
assert t2.operand == 0xc000
|
||||
assert t3.operand == "AX"
|
||||
assert t4.operand == "AX"
|
||||
assert t1.datatype == DataType.WORD
|
||||
assert t2.datatype == DataType.BYTE
|
||||
assert t3.datatype == DataType.WORD
|
||||
@ -252,8 +249,9 @@ def test_char_string():
|
||||
lexer.source_filename = "sourcefile"
|
||||
filter = TokenFilter(lexer)
|
||||
result = parser.parse(input=test_source_4, tokenfunc=filter.token)
|
||||
nodes = result.nodes[0].nodes
|
||||
var1, var2, var3, assgn1, assgn2, assgn3, = nodes
|
||||
connect_parents(result, None)
|
||||
block = result.scope.nodes[0]
|
||||
var1, var2, var3, assgn1, assgn2, assgn3, = block.scope.nodes
|
||||
assert var1.value.value == 64
|
||||
assert var2.value.value == 126
|
||||
assert var3.value.value == "abc"
|
||||
@ -278,8 +276,9 @@ def test_boolean_int():
|
||||
lexer.source_filename = "sourcefile"
|
||||
filter = TokenFilter(lexer)
|
||||
result = parser.parse(input=test_source_5, tokenfunc=filter.token)
|
||||
nodes = result.nodes[0].nodes
|
||||
var1, var2, assgn1, assgn2, = nodes
|
||||
connect_parents(result, None)
|
||||
block = result.scope.nodes[0]
|
||||
var1, var2, assgn1, assgn2, = block.scope.nodes
|
||||
assert type(var1.value.value) is int and var1.value.value == 1
|
||||
assert type(var2.value.value) is int and var2.value.value == 0
|
||||
assert type(assgn1.right.value) is int and assgn1.right.value == 1
|
||||
|
Loading…
x
Reference in New Issue
Block a user