mirror of
https://github.com/irmen/prog8.git
synced 2024-07-08 10:29:09 +00:00
more semantic code checks
This commit is contained in:
parent
db97be69fe
commit
9b77dcc6b8
@ -13,7 +13,7 @@ from typing import Optional, Tuple, Set, Dict, List, Any, no_type_check
|
|||||||
import attr
|
import attr
|
||||||
from .plyparse import parse_file, ParseError, Module, Directive, Block, Subroutine, Scope, VarDef, LiteralValue, \
|
from .plyparse import parse_file, ParseError, Module, Directive, Block, Subroutine, Scope, VarDef, LiteralValue, \
|
||||||
SubCall, Goto, Return, Assignment, InlineAssembly, Register, Expression, ProgramFormat, ZpOptions,\
|
SubCall, Goto, Return, Assignment, InlineAssembly, Register, Expression, ProgramFormat, ZpOptions,\
|
||||||
SymbolName, Dereference, AddressOf, IncrDecr, Label, AstNode, datatype_of, coerce_constant_value
|
SymbolName, Dereference, AddressOf, IncrDecr, Label, AstNode, datatype_of, coerce_constant_value, UndefinedSymbolError
|
||||||
from .plylex import SourceRef, print_bold
|
from .plylex import SourceRef, print_bold
|
||||||
from .datatypes import DataType, VarType
|
from .datatypes import DataType, VarType
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ class PlyParser:
|
|||||||
self.process_imports(module)
|
self.process_imports(module)
|
||||||
self.create_multiassigns(module)
|
self.create_multiassigns(module)
|
||||||
self.check_and_merge_zeropages(module)
|
self.check_and_merge_zeropages(module)
|
||||||
self.process_all_expressions(module)
|
self.process_all_expressions_and_symbolnames(module)
|
||||||
if not self.parsing_import:
|
if not self.parsing_import:
|
||||||
# these shall only be done on the main module after all imports have been done:
|
# these shall only be done on the main module after all imports have been done:
|
||||||
self.apply_directive_options(module)
|
self.apply_directive_options(module)
|
||||||
@ -54,28 +54,33 @@ class PlyParser:
|
|||||||
self.parse_errors += 1
|
self.parse_errors += 1
|
||||||
print_bold("ERROR: {}: {}".format(sourceref, fmtstring.format(*args)))
|
print_bold("ERROR: {}: {}".format(sourceref, fmtstring.format(*args)))
|
||||||
|
|
||||||
|
def _check_last_statement_is_return(self, last_stmt: AstNode) -> None:
|
||||||
|
if isinstance(last_stmt, Subroutine):
|
||||||
|
return
|
||||||
|
if isinstance(last_stmt, Directive) and last_stmt.name == "noreturn":
|
||||||
|
return
|
||||||
|
if isinstance(last_stmt, InlineAssembly):
|
||||||
|
for line in reversed(last_stmt.assembly.splitlines()):
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith(";"):
|
||||||
|
continue
|
||||||
|
if "jmp " in line or "jmp\t" in line or "rts" in line or "rti" in line:
|
||||||
|
return
|
||||||
|
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:
|
def semantic_check(self, module: Module) -> None:
|
||||||
# perform semantic analysis / checks on the syntactic parse tree we have so far
|
# perform semantic analysis / checks on the syntactic parse tree we have so far
|
||||||
def check_last_statement_is_return(last_stmt: AstNode) -> None:
|
# (note: symbol names have already been checked to exist when we start this)
|
||||||
if isinstance(last_stmt, Subroutine):
|
|
||||||
return
|
|
||||||
if isinstance(last_stmt, Directive) and last_stmt.name == "noreturn":
|
|
||||||
return
|
|
||||||
if isinstance(last_stmt, InlineAssembly):
|
|
||||||
for line in reversed(last_stmt.assembly.splitlines()):
|
|
||||||
line = line.strip()
|
|
||||||
if line.startswith(";"):
|
|
||||||
continue
|
|
||||||
if "jmp " in line or "jmp\t" in line or "rts" in line or "rti" in line:
|
|
||||||
return
|
|
||||||
raise ParseError("last statement in a block/subroutine must be a return or goto, "
|
|
||||||
"(or %noreturn directive to silence this error)", last_stmt.sourceref)
|
|
||||||
|
|
||||||
for block, parent in module.all_scopes():
|
for block, parent in module.all_scopes():
|
||||||
assert isinstance(block, (Module, Block, Subroutine))
|
assert isinstance(block, (Module, Block, Subroutine))
|
||||||
assert parent is None or isinstance(parent, (Module, Block, Subroutine))
|
assert parent is None or isinstance(parent, (Module, Block, Subroutine))
|
||||||
previous_stmt = None
|
previous_stmt = None
|
||||||
for stmt in block.nodes:
|
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):
|
if isinstance(stmt, Subroutine):
|
||||||
# the previous statement (if any) must be a Goto or Return
|
# the previous statement (if any) must be a Goto or Return
|
||||||
if previous_stmt and not isinstance(previous_stmt, (Goto, Return, VarDef, Subroutine)):
|
if previous_stmt and not isinstance(previous_stmt, (Goto, Return, VarDef, Subroutine)):
|
||||||
@ -88,11 +93,20 @@ class PlyParser:
|
|||||||
previous_stmt = stmt
|
previous_stmt = stmt
|
||||||
if isinstance(stmt, IncrDecr):
|
if isinstance(stmt, IncrDecr):
|
||||||
if isinstance(stmt.target, SymbolName):
|
if isinstance(stmt.target, SymbolName):
|
||||||
symdef = block.scope[stmt.target.name]
|
symdef = block.scope.lookup(stmt.target.name)
|
||||||
if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST:
|
if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST:
|
||||||
raise ParseError("cannot modify a constant", stmt.sourceref)
|
raise ParseError("cannot modify a constant", stmt.sourceref)
|
||||||
if parent and block.name != "ZP" and not isinstance(stmt, (Return, Goto)):
|
if parent and block.name != "ZP" and not isinstance(stmt, (Return, Goto)):
|
||||||
check_last_statement_is_return(stmt)
|
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
|
||||||
|
if len(call.arguments) != len(subdef.param_spec):
|
||||||
|
raise ParseError("invalid number of arguments ({:d}, required: {:d})"
|
||||||
|
.format(len(call.arguments), len(subdef.param_spec)), call.sourceref)
|
||||||
|
for arg, param in zip(call.arguments, subdef.param_spec):
|
||||||
|
if arg.name and arg.name != param[0]:
|
||||||
|
raise ParseError("parameter name mismatch", arg.sourceref)
|
||||||
|
|
||||||
def check_and_merge_zeropages(self, module: Module) -> None:
|
def check_and_merge_zeropages(self, module: Module) -> None:
|
||||||
# merge all ZP blocks into one
|
# merge all ZP blocks into one
|
||||||
@ -133,8 +147,8 @@ class PlyParser:
|
|||||||
raise ParseError(str(x), vardef.sourceref)
|
raise ParseError(str(x), vardef.sourceref)
|
||||||
|
|
||||||
@no_type_check
|
@no_type_check
|
||||||
def process_all_expressions(self, module: Module) -> None:
|
def process_all_expressions_and_symbolnames(self, module: Module) -> None:
|
||||||
# process/simplify all expressions (constant folding etc)
|
# process/simplify all expressions (constant folding etc), and check all symbol names
|
||||||
encountered_blocks = set()
|
encountered_blocks = set()
|
||||||
for block, parent in module.all_scopes():
|
for block, parent in module.all_scopes():
|
||||||
parentname = (parent.name + ".") if parent else ""
|
parentname = (parent.name + ".") if parent else ""
|
||||||
@ -144,6 +158,7 @@ class PlyParser:
|
|||||||
encountered_blocks.add(blockname)
|
encountered_blocks.add(blockname)
|
||||||
for node in block.nodes:
|
for node in block.nodes:
|
||||||
try:
|
try:
|
||||||
|
node.verify_symbol_names(block.scope)
|
||||||
node.process_expressions(block.scope)
|
node.process_expressions(block.scope)
|
||||||
except ParseError:
|
except ParseError:
|
||||||
raise
|
raise
|
||||||
@ -297,10 +312,10 @@ class PlyParser:
|
|||||||
return self._get_subroutine_usages_from_expression(usages, expr.name, parent_scope)
|
return self._get_subroutine_usages_from_expression(usages, expr.name, parent_scope)
|
||||||
elif isinstance(expr, SymbolName):
|
elif isinstance(expr, SymbolName):
|
||||||
try:
|
try:
|
||||||
symbol = parent_scope[expr.name]
|
symbol = parent_scope.lookup(expr.name)
|
||||||
if isinstance(symbol, Subroutine):
|
if isinstance(symbol, Subroutine):
|
||||||
usages[(parent_scope.name, expr.name)].add(str(expr.sourceref))
|
usages[(parent_scope.name, expr.name)].add(str(expr.sourceref))
|
||||||
except LookupError:
|
except UndefinedSymbolError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise TypeError("unknown expr type to scan for sub usages", expr, expr.sourceref)
|
raise TypeError("unknown expr type to scan for sub usages", expr, expr.sourceref)
|
||||||
@ -338,8 +353,8 @@ class PlyParser:
|
|||||||
if name[0] == '$':
|
if name[0] == '$':
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
symbol = parent_scope[name]
|
symbol = parent_scope.lookup(name)
|
||||||
except LookupError:
|
except UndefinedSymbolError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if isinstance(symbol, Subroutine):
|
if isinstance(symbol, Subroutine):
|
||||||
@ -439,6 +454,7 @@ class PlyParser:
|
|||||||
print(' ' * (1+exc.sourceref.column) + '^', file=out)
|
print(' ' * (1+exc.sourceref.column) + '^', file=out)
|
||||||
if out.isatty():
|
if out.isatty():
|
||||||
print("\x1b[0m", file=out, end="", flush=True)
|
print("\x1b[0m", file=out, end="", flush=True)
|
||||||
|
raise exc # XXX temporary to see where the error occurred
|
||||||
|
|
||||||
def handle_internal_error(self, exc: Exception, msg: str="") -> None:
|
def handle_internal_error(self, exc: Exception, msg: str="") -> None:
|
||||||
out = sys.stdout
|
out = sys.stdout
|
||||||
|
@ -18,7 +18,7 @@ def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope) -> None:
|
|||||||
assert stmt.operator in ("++", "--")
|
assert stmt.operator in ("++", "--")
|
||||||
target = stmt.target # one of Register/SymbolName/Dereference
|
target = stmt.target # one of Register/SymbolName/Dereference
|
||||||
if isinstance(target, SymbolName):
|
if isinstance(target, SymbolName):
|
||||||
symdef = scope[target.name]
|
symdef = scope.lookup(target.name)
|
||||||
if isinstance(symdef, VarDef):
|
if isinstance(symdef, VarDef):
|
||||||
target = symdef
|
target = symdef
|
||||||
else:
|
else:
|
||||||
|
149
il65/plyparse.py
149
il65/plyparse.py
@ -48,6 +48,10 @@ class ExpressionEvaluationError(ParseError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UndefinedSymbolError(LookupError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
start = "start"
|
start = "start"
|
||||||
|
|
||||||
|
|
||||||
@ -89,6 +93,11 @@ class AstNode:
|
|||||||
# this is implemented in node types that have expression(s) and that should act on this.
|
# this is implemented in node types that have expression(s) and that should act on this.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def verify_symbol_names(self, scope: 'Scope') -> None:
|
||||||
|
# check all SymbolNames to see if they exist.
|
||||||
|
# this is implemented in node types that have expression(s) and that should act on this.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, repr=False)
|
@attr.s(cmp=False, repr=False)
|
||||||
class Directive(AstNode):
|
class Directive(AstNode):
|
||||||
@ -105,7 +114,7 @@ class Scope(AstNode):
|
|||||||
save_registers = attr.ib(type=bool, default=None, init=False) # None = look in parent scope's setting @todo property that does that
|
save_registers = attr.ib(type=bool, default=None, init=False) # None = look in parent scope's setting @todo property that does that
|
||||||
|
|
||||||
def __attrs_post_init__(self):
|
def __attrs_post_init__(self):
|
||||||
# populate the symbol table for this scope for fast lookups via scope["name"] or scope["dotted.name"]
|
# populate the symbol table for this scope for fast lookups via scope.lookup("name") or scope.lookup("dotted.name")
|
||||||
self.symbols = {}
|
self.symbols = {}
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
assert isinstance(node, AstNode)
|
assert isinstance(node, AstNode)
|
||||||
@ -129,7 +138,7 @@ class Scope(AstNode):
|
|||||||
self.symbols[node.name] = node
|
self.symbols[node.name] = node
|
||||||
node.scope.parent_scope = self
|
node.scope.parent_scope = self
|
||||||
|
|
||||||
def __getitem__(self, name: str) -> AstNode:
|
def lookup(self, name: str) -> AstNode:
|
||||||
assert isinstance(name, str)
|
assert isinstance(name, str)
|
||||||
if '.' in name:
|
if '.' in name:
|
||||||
# look up the dotted name starting from the topmost scope
|
# look up the dotted name starting from the topmost scope
|
||||||
@ -140,18 +149,18 @@ class Scope(AstNode):
|
|||||||
if isinstance(scope, (Block, Subroutine)):
|
if isinstance(scope, (Block, Subroutine)):
|
||||||
scope = scope.scope
|
scope = scope.scope
|
||||||
if not isinstance(scope, Scope):
|
if not isinstance(scope, Scope):
|
||||||
raise LookupError("undefined symbol: " + name)
|
raise UndefinedSymbolError("undefined symbol: " + name)
|
||||||
scope = scope.symbols.get(namepart, None)
|
scope = scope.symbols.get(namepart, None)
|
||||||
if not scope:
|
if not scope:
|
||||||
raise LookupError("undefined symbol: " + name)
|
raise UndefinedSymbolError("undefined symbol: " + name)
|
||||||
return scope
|
return scope
|
||||||
else:
|
else:
|
||||||
# find the name in nested scope hierarchy
|
# find the name in nested scope hierarchy
|
||||||
if name in self.symbols:
|
if name in self.symbols:
|
||||||
return self.symbols[name]
|
return self.symbols[name]
|
||||||
if self.parent_scope:
|
if self.parent_scope:
|
||||||
return self.parent_scope[name]
|
return self.parent_scope.lookup(name)
|
||||||
raise LookupError("undefined symbol: " + name)
|
raise UndefinedSymbolError("undefined symbol: " + name)
|
||||||
|
|
||||||
def filter_nodes(self, nodetype) -> Generator[AstNode, None, None]:
|
def filter_nodes(self, nodetype) -> Generator[AstNode, None, None]:
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
@ -325,6 +334,15 @@ class Assignment(AstNode):
|
|||||||
def process_expressions(self, scope: Scope) -> None:
|
def process_expressions(self, scope: Scope) -> None:
|
||||||
self.right = process_expression(self.right, scope, self.right.sourceref)
|
self.right = process_expression(self.right, scope, self.right.sourceref)
|
||||||
|
|
||||||
|
def verify_symbol_names(self, scope: Scope) -> None:
|
||||||
|
for lv in self.left:
|
||||||
|
if isinstance(lv, SymbolName):
|
||||||
|
check_symbol_definition(lv.name, scope, lv.sourceref)
|
||||||
|
elif isinstance(lv, Dereference):
|
||||||
|
if isinstance(lv.location, SymbolName):
|
||||||
|
check_symbol_definition(lv.location.name, scope, lv.location.sourceref)
|
||||||
|
# the symbols in the assignment rvalue are checked when its expression is processed.
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, repr=False)
|
@attr.s(cmp=False, repr=False)
|
||||||
class AugAssignment(AstNode):
|
class AugAssignment(AstNode):
|
||||||
@ -335,6 +353,11 @@ class AugAssignment(AstNode):
|
|||||||
def process_expressions(self, scope: Scope) -> None:
|
def process_expressions(self, scope: Scope) -> None:
|
||||||
self.right = process_expression(self.right, scope, self.right.sourceref)
|
self.right = process_expression(self.right, scope, self.right.sourceref)
|
||||||
|
|
||||||
|
def verify_symbol_names(self, scope: Scope) -> None:
|
||||||
|
if isinstance(self.left, SymbolName):
|
||||||
|
check_symbol_definition(self.left.name, scope, self.left.sourceref)
|
||||||
|
# the symbols in the assignment rvalue are checked when its expression is processed.
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, repr=False)
|
@attr.s(cmp=False, repr=False)
|
||||||
class SubCall(AstNode):
|
class SubCall(AstNode):
|
||||||
@ -350,6 +373,11 @@ class SubCall(AstNode):
|
|||||||
assert isinstance(callarg, CallArgument)
|
assert isinstance(callarg, CallArgument)
|
||||||
callarg.process_expressions(scope)
|
callarg.process_expressions(scope)
|
||||||
|
|
||||||
|
def verify_symbol_names(self, scope: Scope) -> None:
|
||||||
|
if isinstance(self.target.target, SymbolName):
|
||||||
|
check_symbol_definition(self.target.target.name, scope, self.target.target.sourceref)
|
||||||
|
# the symbols in the subroutine's arguments are checked when their expression is processed.
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, repr=False)
|
@attr.s(cmp=False, repr=False)
|
||||||
class Return(AstNode):
|
class Return(AstNode):
|
||||||
@ -505,6 +533,10 @@ class Goto(AstNode):
|
|||||||
if self.condition is not None:
|
if self.condition is not None:
|
||||||
self.condition = process_expression(self.condition, scope, self.condition.sourceref)
|
self.condition = process_expression(self.condition, scope, self.condition.sourceref)
|
||||||
|
|
||||||
|
def verify_symbol_names(self, scope: Scope) -> None:
|
||||||
|
if isinstance(self.target.target, SymbolName):
|
||||||
|
check_symbol_definition(self.target.target.name, scope, self.target.target.sourceref)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, repr=False)
|
@attr.s(cmp=False, repr=False)
|
||||||
class Dereference(AstNode):
|
class Dereference(AstNode):
|
||||||
@ -525,6 +557,11 @@ class Dereference(AstNode):
|
|||||||
raise ParseError("dereference target value must be byte, word, float", self.datatype.sourceref)
|
raise ParseError("dereference target value must be byte, word, float", self.datatype.sourceref)
|
||||||
self.datatype = self.datatype.to_enum()
|
self.datatype = self.datatype.to_enum()
|
||||||
|
|
||||||
|
def verify_symbol_names(self, scope: Scope) -> None:
|
||||||
|
print("DEREF", self.location) # XXX not called?????
|
||||||
|
if isinstance(self.location, SymbolName):
|
||||||
|
check_symbol_definition(self.location.name, scope, self.location.sourceref)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, repr=False)
|
@attr.s(cmp=False, repr=False)
|
||||||
class LiteralValue(AstNode):
|
class LiteralValue(AstNode):
|
||||||
@ -554,6 +591,10 @@ class IncrDecr(AstNode):
|
|||||||
if isinstance(self.target, TargetRegisters):
|
if isinstance(self.target, TargetRegisters):
|
||||||
raise ParseError("cannot incr/decr multiple registers at once", self.sourceref)
|
raise ParseError("cannot incr/decr multiple registers at once", self.sourceref)
|
||||||
|
|
||||||
|
def verify_symbol_names(self, scope: Scope) -> None:
|
||||||
|
if isinstance(self.target, SymbolName):
|
||||||
|
check_symbol_definition(self.target.name, scope, self.target.sourceref)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, repr=False)
|
@attr.s(cmp=False, repr=False)
|
||||||
class SymbolName(AstNode):
|
class SymbolName(AstNode):
|
||||||
@ -621,7 +662,7 @@ def datatype_of(assignmenttarget: AstNode, scope: Scope) -> DataType:
|
|||||||
if isinstance(assignmenttarget, (VarDef, Dereference, Register)):
|
if isinstance(assignmenttarget, (VarDef, Dereference, Register)):
|
||||||
return assignmenttarget.datatype
|
return assignmenttarget.datatype
|
||||||
elif isinstance(assignmenttarget, SymbolName):
|
elif isinstance(assignmenttarget, SymbolName):
|
||||||
symdef = scope[assignmenttarget.name]
|
symdef = scope.lookup(assignmenttarget.name)
|
||||||
if isinstance(symdef, VarDef):
|
if isinstance(symdef, VarDef):
|
||||||
return symdef.datatype
|
return symdef.datatype
|
||||||
elif isinstance(assignmenttarget, TargetRegisters):
|
elif isinstance(assignmenttarget, TargetRegisters):
|
||||||
@ -630,8 +671,8 @@ def datatype_of(assignmenttarget: AstNode, scope: Scope) -> DataType:
|
|||||||
raise TypeError("cannot determine datatype", assignmenttarget)
|
raise TypeError("cannot determine datatype", assignmenttarget)
|
||||||
|
|
||||||
|
|
||||||
def coerce_constant_value(datatype: DataType, value: Union[int, float, str],
|
def coerce_constant_value(datatype: DataType, value: Any,
|
||||||
sourceref: SourceRef=None) -> Tuple[bool, Union[int, float, str]]:
|
sourceref: SourceRef=None) -> Tuple[bool, Any]:
|
||||||
# if we're a BYTE type, and the value is a single character, convert it to the numeric value
|
# if we're a BYTE type, and the value is a single character, convert it to the numeric value
|
||||||
def verify_bounds(value: Union[int, float, str]) -> None:
|
def verify_bounds(value: Union[int, float, str]) -> None:
|
||||||
# if the value is out of bounds, raise an overflow exception
|
# if the value is out of bounds, raise an overflow exception
|
||||||
@ -684,56 +725,47 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
|
|||||||
elif isinstance(expr, LiteralValue):
|
elif isinstance(expr, LiteralValue):
|
||||||
return expr.value
|
return expr.value
|
||||||
elif isinstance(expr, SymbolName):
|
elif isinstance(expr, SymbolName):
|
||||||
try:
|
value = check_symbol_definition(expr.name, symbolscope, expr.sourceref)
|
||||||
value = symbolscope[expr.name]
|
if isinstance(value, VarDef):
|
||||||
if isinstance(value, VarDef):
|
if value.vartype == VarType.MEMORY:
|
||||||
if value.vartype == VarType.MEMORY:
|
raise ExpressionEvaluationError("can't take a memory value, must be a constant", expr.sourceref)
|
||||||
raise ExpressionEvaluationError("can't take a memory value, must be a constant", expr.sourceref)
|
value = value.value
|
||||||
value = value.value
|
if isinstance(value, Expression):
|
||||||
if isinstance(value, Expression):
|
raise ExpressionEvaluationError("circular reference?", expr.sourceref)
|
||||||
raise ExpressionEvaluationError("circular reference?", expr.sourceref)
|
elif isinstance(value, (int, float, str, bool)):
|
||||||
elif isinstance(value, (int, float, str, bool)):
|
return value
|
||||||
return value
|
else:
|
||||||
else:
|
raise ExpressionEvaluationError("constant symbol required, not {}".format(value.__class__.__name__), expr.sourceref)
|
||||||
raise ExpressionEvaluationError("constant symbol required, not {}".format(value.__class__.__name__), expr.sourceref)
|
|
||||||
except LookupError as x:
|
|
||||||
raise ExpressionEvaluationError(str(x), expr.sourceref) from None
|
|
||||||
elif isinstance(expr, AddressOf):
|
elif isinstance(expr, AddressOf):
|
||||||
assert isinstance(expr.name, SymbolName)
|
assert isinstance(expr.name, SymbolName)
|
||||||
try:
|
value = check_symbol_definition(expr.name.name, symbolscope, expr.sourceref)
|
||||||
value = symbolscope[expr.name.name]
|
if isinstance(value, VarDef):
|
||||||
if isinstance(value, VarDef):
|
if value.vartype == VarType.MEMORY:
|
||||||
if value.vartype == VarType.MEMORY:
|
return value.value
|
||||||
return value.value
|
if value.vartype == VarType.CONST:
|
||||||
if value.vartype == VarType.CONST:
|
raise ExpressionEvaluationError("can't take the address of a constant", expr.name.sourceref)
|
||||||
raise ExpressionEvaluationError("can't take the address of a constant", expr.name.sourceref)
|
raise ExpressionEvaluationError("address-of this {} isn't a compile-time constant"
|
||||||
raise ExpressionEvaluationError("address-of this {} isn't a compile-time constant"
|
.format(value.__class__.__name__), expr.name.sourceref)
|
||||||
.format(value.__class__.__name__), expr.name.sourceref)
|
else:
|
||||||
else:
|
raise ExpressionEvaluationError("constant address required, not {}"
|
||||||
raise ExpressionEvaluationError("constant address required, not {}"
|
.format(value.__class__.__name__), expr.name.sourceref)
|
||||||
.format(value.__class__.__name__), expr.name.sourceref)
|
|
||||||
except LookupError as x:
|
|
||||||
raise ParseError(str(x), expr.sourceref) from None
|
|
||||||
elif isinstance(expr, SubCall):
|
elif isinstance(expr, SubCall):
|
||||||
if isinstance(expr.target, CallTarget):
|
if isinstance(expr.target, CallTarget):
|
||||||
target = expr.target.target
|
target = expr.target.target
|
||||||
if isinstance(target, SymbolName): # 'function(1,2,3)'
|
if isinstance(target, SymbolName): # 'function(1,2,3)'
|
||||||
funcname = target.name
|
funcname = target.name
|
||||||
if funcname in math_functions or funcname in builtin_functions:
|
if funcname in math_functions or funcname in builtin_functions:
|
||||||
if isinstance(expr.target.target, SymbolName):
|
func_args = []
|
||||||
func_args = []
|
for a in (process_constant_expression(callarg.value, sourceref, symbolscope) for callarg in expr.arguments):
|
||||||
for a in (process_constant_expression(callarg.value, sourceref, symbolscope) for callarg in expr.arguments):
|
if isinstance(a, LiteralValue):
|
||||||
if isinstance(a, LiteralValue):
|
func_args.append(a.value)
|
||||||
func_args.append(a.value)
|
else:
|
||||||
else:
|
func_args.append(a)
|
||||||
func_args.append(a)
|
func = math_functions.get(funcname, builtin_functions.get(funcname))
|
||||||
func = math_functions.get(funcname, builtin_functions.get(funcname))
|
try:
|
||||||
try:
|
return func(*func_args)
|
||||||
return func(*func_args)
|
except Exception as x:
|
||||||
except Exception as x:
|
raise ExpressionEvaluationError(str(x), expr.sourceref)
|
||||||
raise ExpressionEvaluationError(str(x), expr.sourceref)
|
|
||||||
else:
|
|
||||||
raise ParseError("symbol name required, not {}".format(expr.target.__class__.__name__), expr.sourceref)
|
|
||||||
else:
|
else:
|
||||||
raise ExpressionEvaluationError("can only use math- or builtin function", expr.sourceref)
|
raise ExpressionEvaluationError("can only use math- or builtin function", expr.sourceref)
|
||||||
elif isinstance(target, Dereference): # '[...](1,2,3)'
|
elif isinstance(target, Dereference): # '[...](1,2,3)'
|
||||||
@ -766,8 +798,8 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
|
|||||||
expr.left = process_constant_expression(expr.left, left_sourceref, symbolscope)
|
expr.left = process_constant_expression(expr.left, left_sourceref, symbolscope)
|
||||||
right_sourceref = expr.right.sourceref if isinstance(expr.right, AstNode) else sourceref
|
right_sourceref = expr.right.sourceref if isinstance(expr.right, AstNode) else sourceref
|
||||||
expr.right = process_constant_expression(expr.right, right_sourceref, symbolscope)
|
expr.right = process_constant_expression(expr.right, right_sourceref, symbolscope)
|
||||||
if isinstance(expr.left, (LiteralValue, SymbolName, int, float, str, bool)):
|
if isinstance(expr.left, (LiteralValue, int, float, str, bool)):
|
||||||
if isinstance(expr.right, (LiteralValue, SymbolName, int, float, str, bool)):
|
if isinstance(expr.right, (LiteralValue, int, float, str, bool)):
|
||||||
return expr.evaluate_primitive_constants(symbolscope)
|
return expr.evaluate_primitive_constants(symbolscope)
|
||||||
else:
|
else:
|
||||||
raise ExpressionEvaluationError("constant value required on right, not {}"
|
raise ExpressionEvaluationError("constant value required on right, not {}"
|
||||||
@ -777,6 +809,13 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
|
|||||||
.format(expr.left.__class__.__name__), left_sourceref)
|
.format(expr.left.__class__.__name__), left_sourceref)
|
||||||
|
|
||||||
|
|
||||||
|
def check_symbol_definition(name: str, scope: Scope, sref: SourceRef) -> Any:
|
||||||
|
try:
|
||||||
|
return scope.lookup(name)
|
||||||
|
except UndefinedSymbolError as x:
|
||||||
|
raise ParseError(str(x), sref)
|
||||||
|
|
||||||
|
|
||||||
def process_dynamic_expression(expr: Any, sourceref: SourceRef, symbolscope: Scope) -> Any:
|
def process_dynamic_expression(expr: Any, sourceref: SourceRef, symbolscope: Scope) -> Any:
|
||||||
# constant-fold a dynamic expression
|
# constant-fold a dynamic expression
|
||||||
if expr is None or isinstance(expr, (int, float, str, bool)):
|
if expr is None or isinstance(expr, (int, float, str, bool)):
|
||||||
@ -797,10 +836,14 @@ def process_dynamic_expression(expr: Any, sourceref: SourceRef, symbolscope: Sco
|
|||||||
try:
|
try:
|
||||||
return process_constant_expression(expr, sourceref, symbolscope)
|
return process_constant_expression(expr, sourceref, symbolscope)
|
||||||
except ExpressionEvaluationError:
|
except ExpressionEvaluationError:
|
||||||
|
if isinstance(expr.target.target, SymbolName):
|
||||||
|
check_symbol_definition(expr.target.target.name, symbolscope, expr.target.target.sourceref)
|
||||||
return expr
|
return expr
|
||||||
elif isinstance(expr, Register):
|
elif isinstance(expr, Register):
|
||||||
return expr
|
return expr
|
||||||
elif isinstance(expr, Dereference):
|
elif isinstance(expr, Dereference):
|
||||||
|
if isinstance(expr.location, SymbolName):
|
||||||
|
check_symbol_definition(expr.location.name, symbolscope, expr.location.sourceref)
|
||||||
return expr
|
return expr
|
||||||
elif not isinstance(expr, Expression):
|
elif not isinstance(expr, Expression):
|
||||||
raise ParseError("expression required, not {}".format(expr.__class__.__name__), expr.sourceref)
|
raise ParseError("expression required, not {}".format(expr.__class__.__name__), expr.sourceref)
|
||||||
|
@ -112,6 +112,7 @@ def test_coerce_value():
|
|||||||
assert coerce_constant_value(datatypes.DataType.FLOAT, '@') == (True, 64)
|
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.BYTE, 5.678) == (True, 5)
|
||||||
assert coerce_constant_value(datatypes.DataType.WORD, 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, "string") == (False, "string")
|
||||||
assert coerce_constant_value(datatypes.DataType.STRING_P, "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_S, "string") == (False, "string")
|
||||||
@ -134,7 +135,5 @@ def test_coerce_value():
|
|||||||
coerce_constant_value(datatypes.DataType.FLOAT, 1.7014118347e+38)
|
coerce_constant_value(datatypes.DataType.FLOAT, 1.7014118347e+38)
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
coerce_constant_value(datatypes.DataType.BYTE, "string")
|
coerce_constant_value(datatypes.DataType.BYTE, "string")
|
||||||
with pytest.raises(TypeError):
|
|
||||||
coerce_constant_value(datatypes.DataType.WORD, "string")
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
coerce_constant_value(datatypes.DataType.FLOAT, "string")
|
coerce_constant_value(datatypes.DataType.FLOAT, "string")
|
||||||
|
@ -117,10 +117,10 @@ def test_parser():
|
|||||||
assert result.scope.name == "<sourcefile global scope>"
|
assert result.scope.name == "<sourcefile global scope>"
|
||||||
assert result.subroutine_usage == {}
|
assert result.subroutine_usage == {}
|
||||||
assert result.scope.parent_scope is None
|
assert result.scope.parent_scope is None
|
||||||
sub = result.scope["block.calculate"]
|
sub = result.scope.lookup("block.calculate")
|
||||||
assert isinstance(sub, Subroutine)
|
assert isinstance(sub, Subroutine)
|
||||||
assert sub.name == "calculate"
|
assert sub.name == "calculate"
|
||||||
block = result.scope["block"]
|
block = result.scope.lookup("block")
|
||||||
assert isinstance(block, Block)
|
assert isinstance(block, Block)
|
||||||
assert block.name == "block"
|
assert block.name == "block"
|
||||||
assert block.nodes is block.scope.nodes
|
assert block.nodes is block.scope.nodes
|
||||||
@ -131,7 +131,7 @@ def test_parser():
|
|||||||
assert isinstance(bool_vdef.value.right.value, int)
|
assert isinstance(bool_vdef.value.right.value, int)
|
||||||
assert bool_vdef.value.right.value == 1
|
assert bool_vdef.value.right.value == 1
|
||||||
assert block.address == 49152
|
assert block.address == 49152
|
||||||
sub2 = block.scope["calculate"]
|
sub2 = block.scope.lookup("calculate")
|
||||||
assert sub2 is sub
|
assert sub2 is sub
|
||||||
assert sub2.lineref == "src l. 19"
|
assert sub2.lineref == "src l. 19"
|
||||||
all_scopes = list(result.all_scopes())
|
all_scopes = list(result.all_scopes())
|
||||||
@ -163,7 +163,7 @@ def test_block_nodes():
|
|||||||
test_source_2 = """
|
test_source_2 = """
|
||||||
~ {
|
~ {
|
||||||
999(1,2)
|
999(1,2)
|
||||||
&zz()
|
[zz]()
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -184,9 +184,9 @@ def test_parser_2():
|
|||||||
assert isinstance(call, SubCall)
|
assert isinstance(call, SubCall)
|
||||||
assert len(call.arguments) == 0
|
assert len(call.arguments) == 0
|
||||||
assert isinstance(call.target, CallTarget)
|
assert isinstance(call.target, CallTarget)
|
||||||
assert isinstance(call.target.target, SymbolName)
|
assert isinstance(call.target.target, Dereference)
|
||||||
assert call.target.target.name == "zz"
|
assert call.target.target.location.name == "zz"
|
||||||
assert call.target.address_of is True
|
assert call.target.address_of is False
|
||||||
|
|
||||||
|
|
||||||
test_source_3 = """
|
test_source_3 = """
|
||||||
|
@ -85,6 +85,7 @@ game_over:
|
|||||||
c64scr.print_string("\nToo bad! It was: ")
|
c64scr.print_string("\nToo bad! It was: ")
|
||||||
c64scr.print_byte_decimal(secretnumber)
|
c64scr.print_byte_decimal(secretnumber)
|
||||||
c64.CHROUT('\n')
|
c64.CHROUT('\n')
|
||||||
|
return goodbye()
|
||||||
goodbye() ; @todo fix subroutine usage tracking, it doesn't register this one
|
goodbye() ; @todo fix subroutine usage tracking, it doesn't register this one
|
||||||
return
|
return
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user