diff --git a/il65/optimize.py b/il65/optimize.py index 3bfd201e1..4e60869ee 100644 --- a/il65/optimize.py +++ b/il65/optimize.py @@ -242,6 +242,7 @@ class Optimizer: new_stmt = IncrDecr(operator="++" if assignment.operator == "+=" else "--", howmuch=howmuch.value, sourceref=assignment.sourceref) new_stmt.target = assignment.left + new_stmt.target.parent = new_stmt assignment.my_scope().replace_node(assignment, new_stmt) self.optimizations_performed = True if assignment.right.value == 1 and assignment.operator in ("/=", "//=", "*="): @@ -265,17 +266,23 @@ class Optimizer: @no_type_check def _make_aug_assign(self, old_assign: Assignment, target: Union[TargetRegisters, Register, SymbolName, Dereference], value: Union[int, float], operator: str) -> AugAssignment: + assert isinstance(target, (TargetRegisters, Register, SymbolName, Dereference)) a = AugAssignment(operator=operator, sourceref=old_assign.sourceref) a.nodes.append(target) - a.nodes.append(LiteralValue(value=value, sourceref=old_assign.sourceref)) + target.parent = a + lv = LiteralValue(value=value, sourceref=old_assign.sourceref) + a.nodes.append(lv) + lv.parent = a a.parent = old_assign.parent return a @no_type_check def _make_incrdecr(self, old_stmt: AstNode, target: Union[TargetRegisters, Register, SymbolName, Dereference], howmuch: Union[int, float], operator: str) -> IncrDecr: + assert isinstance(target, (TargetRegisters, Register, SymbolName, Dereference)) a = IncrDecr(operator=operator, howmuch=howmuch, sourceref=old_stmt.sourceref) a.nodes.append(target) + target.parent = a a.parent = old_stmt.parent return a @@ -341,7 +348,7 @@ class Optimizer: # the comparison operator and rvalue (0) will be removed and the if-status changed accordingly for goto in self.module.all_nodes(Goto): if isinstance(goto.condition, Expression): - print("NOT IMPLEMENTED YET: optimize goto conditionals", goto.condition) # @todo + pass # @todo optimize goto conditionals # if cond and isinstance(cond.rvalue, (int, float)) and cond.rvalue.value == 0: # simplified = False # if cond.ifstatus in ("true", "ne"): @@ -456,7 +463,7 @@ def _process_constant_expression(expr: Expression, sourceref: SourceRef) -> Lite raise ExpressionEvaluationError("can only use math- or builtin function", expr.sourceref) elif isinstance(expr.target, Dereference): # '[...](1,2,3)' raise ExpressionEvaluationError("dereferenced value call is not a constant value", expr.sourceref) - elif type(expr.target) is int: # '64738()' + elif isinstance(expr.target, LiteralValue) and type(expr.target.value) is int: # '64738()' raise ExpressionEvaluationError("immediate address call is not a constant value", expr.sourceref) else: raise NotImplementedError("weird call target", expr.target) @@ -504,15 +511,19 @@ def _process_dynamic_expression(expr: Expression, sourceref: SourceRef) -> Expre if expr.is_compile_constant(): return LiteralValue(value=expr.const_value(), sourceref=sourceref) # type: ignore elif isinstance(expr, SymbolName): - try: - return _process_constant_expression(expr, sourceref) - except ExpressionEvaluationError: - return expr + if expr.is_compile_constant(): + try: + return _process_constant_expression(expr, sourceref) + except ExpressionEvaluationError: + pass + return expr elif isinstance(expr, AddressOf): - try: - return _process_constant_expression(expr, sourceref) - except ExpressionEvaluationError: - return expr + if expr.is_compile_constant(): + try: + return _process_constant_expression(expr, sourceref) + except ExpressionEvaluationError: + pass + return expr elif isinstance(expr, SubCall): try: return _process_constant_expression(expr, sourceref) @@ -520,11 +531,7 @@ def _process_dynamic_expression(expr: Expression, sourceref: SourceRef) -> Expre if isinstance(expr.target, SymbolName): check_symbol_definition(expr.target.name, expr.my_scope(), expr.target.sourceref) return expr - elif isinstance(expr, Register): - return expr - elif isinstance(expr, Dereference): - if isinstance(expr.operand, SymbolName): - check_symbol_definition(expr.operand.name, expr.my_scope(), expr.operand.sourceref) + elif isinstance(expr, (Register, Dereference)): return expr elif isinstance(expr, ExpressionWithOperator): if expr.unary: diff --git a/il65/plyparse.py b/il65/plyparse.py index dbd58adc7..7c8241d02 100644 --- a/il65/plyparse.py +++ b/il65/plyparse.py @@ -159,7 +159,7 @@ class Scope(AstNode): if node.name in self.symbols: raise ParseError("symbol '{}' already defined at {}".format(node.name, self.symbols[node.name].sourceref), node.sourceref) self.symbols[node.name] = node - elif isinstance(node, Block): + elif isinstance(node, (Block, Scope)): if node.name: if node.name != "ZP" and node.name in self.symbols: raise ParseError("symbol '{}' already defined at {}" @@ -608,11 +608,11 @@ class ExpressionWithOperator(Expression): @attr.s(cmp=False, repr=False) class Goto(AstNode): - # one or two subnodes: target (SymbolName, int or Dereference) and optionally: condition (Expression) + # one or two subnodes: target (SymbolName, integer LiteralValue, or Dereference) and optionally: condition (Expression) if_stmt = attr.ib(default=None) @property - def target(self) -> Union[SymbolName, int, Dereference]: + def target(self) -> Union[SymbolName, LiteralValue, Dereference]: return self.nodes[0] # type: ignore @property @@ -1335,7 +1335,10 @@ def p_goto(p): goto : GOTO calltarget """ p[0] = Goto(sourceref=_token_sref(p, 1)) - p[0].nodes.append(p[2]) + target = p[2] + if isinstance(target, int): + target = LiteralValue(value=target, sourceref=p[0].sourceref) + p[0].nodes.append(target) def p_conditional_goto_plain(p): diff --git a/tests/test_parser.py b/tests/test_parser.py index bb7446b44..5fa2d3547 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -322,17 +322,22 @@ def test_symbol_lookup(): scope_inner ], level="block", sourceref=sref) scope_outer.name = "outer" - scope_outer.define_builtin_functions() var1.parent = label1.parent = scope_inner.parent = scope_outer + scope_topmost = Scope(nodes=[scope_outer], level="module", sourceref=sref) + scope_topmost.name = "topmost" + scope_outer.parent = scope_topmost + scope_topmost.define_builtin_functions() assert scope_inner.parent_scope is scope_outer - assert scope_outer.parent_scope is None + assert scope_outer.parent_scope is scope_topmost + assert scope_topmost.parent_scope is None assert label1.my_scope() is scope_outer assert var1.my_scope() is scope_outer assert scope_inner.my_scope() is scope_outer assert label2.my_scope() is scope_inner assert var2.my_scope() is scope_inner + assert scope_outer.my_scope() is scope_topmost with pytest.raises(LookupError): - scope_outer.my_scope() + scope_topmost.my_scope() with pytest.raises(UndefinedSymbolError): scope_inner.lookup("unexisting") with pytest.raises(UndefinedSymbolError): @@ -353,3 +358,12 @@ def test_symbol_lookup(): builtin_func = scope_inner.lookup("max") assert isinstance(builtin_func, BuiltinFunction) assert builtin_func.name == "max" and builtin_func.func is max + # test dotted names: + with pytest.raises(UndefinedSymbolError): + scope_inner.lookup("noscope.nosymbol.nothing") + assert scope_inner.lookup("outer.inner.var2") is var2 + with pytest.raises(UndefinedSymbolError): + scope_inner.lookup("outer.inner.var1") + with pytest.raises(UndefinedSymbolError): + scope_inner.lookup("outer.var2") + assert scope_inner.lookup("outer.var1") is var1