mirror of
https://github.com/irmen/prog8.git
synced 2025-04-06 10:38:48 +00:00
improved const var evaluation, more tests, added const_num_val() on certain nodes
This commit is contained in:
parent
baf3adfa8a
commit
197a4e503e
@ -182,7 +182,7 @@ class PlyParser:
|
||||
encountered_blocks.add(blockname)
|
||||
elif isinstance(node, Expression):
|
||||
try:
|
||||
evaluated = process_expression(node, node.my_scope(), node.sourceref)
|
||||
evaluated = process_expression(node, node.sourceref)
|
||||
if evaluated is not node:
|
||||
# replace the node with the newly evaluated result
|
||||
node.parent.replace_node(node, evaluated)
|
||||
@ -195,7 +195,7 @@ class PlyParser:
|
||||
elif isinstance(node, VarDef):
|
||||
dtype = DataType.WORD if node.vartype == VarType.MEMORY else node.datatype
|
||||
try:
|
||||
_, node.value = coerce_constant_value(dtype, node.value, node.sourceref)
|
||||
_, node.value = coerce_constant_value(dtype, node.value, node.sourceref) # type: ignore
|
||||
except OverflowError as x:
|
||||
raise ParseError(str(x), node.sourceref) from None
|
||||
elif isinstance(node, Assignment):
|
||||
|
@ -20,7 +20,7 @@ def generate_assignment(out: Callable, stmt: Assignment, scope: Scope) -> None:
|
||||
|
||||
|
||||
def generate_aug_assignment(out: Callable, stmt: AugAssignment, scope: Scope) -> None:
|
||||
# for instance: value += 3
|
||||
# for instance: value += 3 (value = 1-255)
|
||||
# left: Register, SymbolName, or Dereference. right: Expression/LiteralValue
|
||||
out("\v\t\t\t; " + stmt.lineref)
|
||||
out("\v; @todo aug-assignment")
|
||||
@ -29,16 +29,21 @@ def generate_aug_assignment(out: Callable, stmt: AugAssignment, scope: Scope) ->
|
||||
if isinstance(lvalue, Register):
|
||||
if isinstance(rvalue, LiteralValue):
|
||||
if type(rvalue.value) is int:
|
||||
_generate_aug_reg_constant_int(out, lvalue, stmt.operator, rvalue.value, "", scope)
|
||||
if 0 < rvalue.value < 256:
|
||||
_generate_aug_reg_constant_int(out, lvalue, stmt.operator, rvalue.value, "", scope)
|
||||
else:
|
||||
raise CodeError("incr/decr value should be 1..255", rvalue)
|
||||
else:
|
||||
raise CodeError("integer literal or variable required for now", rvalue) # XXX
|
||||
raise CodeError("constant integer literal or variable required for now", rvalue) # XXX
|
||||
elif isinstance(rvalue, SymbolName):
|
||||
symdef = scope.lookup(rvalue.name)
|
||||
if isinstance(symdef, VarDef) and symdef.datatype.isinteger():
|
||||
if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST and symdef.datatype.isinteger():
|
||||
# @todo check value range
|
||||
_generate_aug_reg_constant_int(out, lvalue, stmt.operator, 0, symdef.name, scope)
|
||||
else:
|
||||
raise CodeError("integer literal or variable required for now", rvalue) # XXX
|
||||
raise CodeError("constant integer literal or variable required for now", rvalue) # XXX
|
||||
elif isinstance(rvalue, Register):
|
||||
# @todo check value range (single register; 0-255) @todo support combined registers
|
||||
_generate_aug_reg_reg(out, lvalue, stmt.operator, rvalue, scope)
|
||||
else:
|
||||
# @todo Register += symbolname / dereference , _generate_aug_reg_mem?
|
||||
|
@ -7,7 +7,7 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
from typing import List, no_type_check
|
||||
from .plyparse import Module, Subroutine, Block, Directive, Assignment, AugAssignment, Goto, Expression, IncrDecr,\
|
||||
from .plyparse import AstNode, Module, Subroutine, Block, Directive, Assignment, AugAssignment, Goto, Expression, IncrDecr,\
|
||||
datatype_of, coerce_constant_value, AssignmentTargets, LiteralValue, Scope, Register
|
||||
from .plylex import print_warning, print_bold
|
||||
|
||||
@ -25,15 +25,19 @@ class Optimizer:
|
||||
self.optimize_multiassigns()
|
||||
self.remove_unused_subroutines()
|
||||
self.optimize_goto_compare_with_zero()
|
||||
# @todo join multiple incr/decr of same var into one (if value stays < 256)
|
||||
self.join_incrdecrs()
|
||||
# @todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to)
|
||||
self.remove_empty_blocks()
|
||||
|
||||
def join_incrdecrs(self) -> None:
|
||||
# @todo joins multiple incr/decr of same var into one (if value stays < 256 which ...)
|
||||
pass
|
||||
|
||||
def remove_superfluous_assignments(self) -> None:
|
||||
# remove consecutive assignment statements to the same target, only keep the last value (only if its a constant!)
|
||||
# this is NOT done for memory mapped variables because these often represent a volatile register of some sort!
|
||||
for scope in self.module.all_nodes(Scope):
|
||||
prev_node = None
|
||||
prev_node = None # type: AstNode
|
||||
for node in list(scope.nodes):
|
||||
if isinstance(node, Assignment) and isinstance(prev_node, Assignment):
|
||||
if isinstance(node.right, (LiteralValue, Register)) and node.left.same_targets(prev_node.left):
|
||||
@ -78,7 +82,7 @@ class Optimizer:
|
||||
print("{}: shifting result is always zero".format(assignment.sourceref))
|
||||
new_stmt = Assignment(sourceref=assignment.sourceref)
|
||||
new_stmt.nodes.append(AssignmentTargets(nodes=[assignment.left], sourceref=assignment.sourceref))
|
||||
new_stmt.nodes.append(0) # XXX literalvalue?
|
||||
new_stmt.nodes.append(LiteralValue(value=0, sourceref=assignment.sourceref))
|
||||
assignment.my_scope().replace_node(assignment, new_stmt)
|
||||
if assignment.operator in ("+=", "-=") and 0 < assignment.right.value < 256:
|
||||
howmuch = assignment.right
|
||||
|
@ -393,18 +393,41 @@ class LiteralValue(AstNode):
|
||||
def __repr__(self) -> str:
|
||||
return "<LiteralValue value={!r} at {}>".format(self.value, self.sourceref)
|
||||
|
||||
def const_num_val(self) -> Union[int, float]:
|
||||
if isinstance(self.value, (int, float)):
|
||||
return self.value
|
||||
raise TypeError("literal value is not numeric", self)
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
class AddressOf(AstNode):
|
||||
# no subnodes.
|
||||
name = attr.ib(type=str)
|
||||
|
||||
def const_num_val(self) -> Union[int, float]:
|
||||
symdef = self.my_scope().lookup(self.name)
|
||||
if isinstance(symdef, VarDef):
|
||||
if symdef.zp_address is not None:
|
||||
return symdef.zp_address
|
||||
if symdef.vartype == VarType.MEMORY:
|
||||
return symdef.value.const_num_val()
|
||||
raise TypeError("can only take constant address of a memory mapped variable", self)
|
||||
raise TypeError("should be a vardef to be able to take its address", self)
|
||||
|
||||
|
||||
@attr.s(cmp=False, slots=True)
|
||||
class SymbolName(AstNode):
|
||||
# no subnodes.
|
||||
name = attr.ib(type=str)
|
||||
|
||||
def const_num_val(self) -> Union[int, float]:
|
||||
symdef = self.my_scope().lookup(self.name)
|
||||
if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST:
|
||||
if symdef.datatype.isnumeric():
|
||||
return symdef.const_num_val()
|
||||
raise TypeError("not a constant value", self)
|
||||
raise TypeError("should be a vardef to be able to take its constant numeric value", self)
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
class Dereference(AstNode):
|
||||
@ -473,7 +496,10 @@ class Expression(AstNode):
|
||||
if self.operator == "mod":
|
||||
self.operator = "%" # change it back to the more common '%'
|
||||
|
||||
def evaluate_primitive_constants(self, scope: Scope, sourceref: SourceRef) -> LiteralValue:
|
||||
def const_num_val(self) -> Union[int, float]:
|
||||
raise TypeError("an expression is not a constant", self)
|
||||
|
||||
def evaluate_primitive_constants(self, sourceref: SourceRef) -> LiteralValue:
|
||||
# make sure the lvalue and rvalue are primitives, and the operator is allowed
|
||||
assert isinstance(self.left, LiteralValue)
|
||||
assert isinstance(self.right, LiteralValue)
|
||||
@ -553,7 +579,7 @@ class SubCall(AstNode):
|
||||
|
||||
@attr.s(cmp=False, slots=True, repr=False)
|
||||
class VarDef(AstNode):
|
||||
# zero or one subnode: value (an Expression).
|
||||
# zero or one subnode: value (an Expression, LiteralValue, AddressOf or SymbolName.).
|
||||
name = attr.ib(type=str)
|
||||
vartype = attr.ib()
|
||||
datatype = attr.ib()
|
||||
@ -575,6 +601,16 @@ class VarDef(AstNode):
|
||||
if isinstance(value, Expression):
|
||||
value.must_be_constant = True
|
||||
|
||||
def const_num_val(self) -> Union[int, float]:
|
||||
if self.vartype != VarType.CONST:
|
||||
raise TypeError("not a constant value", self)
|
||||
if self.datatype.isnumeric():
|
||||
if self.nodes:
|
||||
return self.nodes[0].const_num_val() # type: ignore
|
||||
raise ValueError("no value", self)
|
||||
else:
|
||||
raise TypeError("not numeric", self)
|
||||
|
||||
def __attrs_post_init__(self):
|
||||
# convert vartype to enum
|
||||
if self.vartype == "const":
|
||||
@ -594,8 +630,10 @@ class VarDef(AstNode):
|
||||
assert self.size is None
|
||||
self.size = self.datatype.dimensions or [1]
|
||||
self.datatype = self.datatype.to_enum()
|
||||
if self.datatype == DataType.MATRIX and len(self.size) not in (2, 3):
|
||||
raise ValueError("matrix size should be 2 dimensions with optional interleave", self)
|
||||
if self.datatype.isarray() and sum(self.size) in (0, 1):
|
||||
print("warning: {}: array/matrix with size 1, use normal byte/word instead for efficiency".format(self.sourceref))
|
||||
print("warning: {}: array/matrix with size 1, use normal byte/word instead".format(self.sourceref))
|
||||
if self.value is None and (self.datatype.isnumeric() or self.datatype.isarray()):
|
||||
self.value = LiteralValue(value=0, sourceref=self.sourceref)
|
||||
# if it's a matrix with interleave, it must be memory mapped
|
||||
@ -758,7 +796,11 @@ def coerce_constant_value(datatype: DataType, value: AstNode,
|
||||
if isinstance(symboldef, VarDef) and symboldef.vartype == VarType.CONST:
|
||||
return True, symboldef.value
|
||||
elif isinstance(value, AddressOf):
|
||||
raise NotImplementedError("addressof const coerce", value) # XXX implement this
|
||||
try:
|
||||
address = value.const_num_val()
|
||||
return True, LiteralValue(value=address, sourceref=value.sourceref) # type: ignore
|
||||
except TypeError:
|
||||
return False, value
|
||||
if datatype == DataType.WORD and not isinstance(value, (LiteralValue, Dereference, Register, SymbolName, AddressOf)):
|
||||
raise TypeError("cannot assign '{:s}' to {:s}".format(type(value).__name__, datatype.name.lower()), sourceref)
|
||||
elif datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT) \
|
||||
@ -767,22 +809,22 @@ def coerce_constant_value(datatype: DataType, value: AstNode,
|
||||
return False, value
|
||||
|
||||
|
||||
def process_expression(expr: Expression, scope: Scope, sourceref: SourceRef) -> Any:
|
||||
def process_expression(expr: Expression, sourceref: SourceRef) -> Any:
|
||||
# process/simplify all expressions (constant folding etc)
|
||||
if expr.must_be_constant:
|
||||
return process_constant_expression(expr, sourceref, scope)
|
||||
return process_constant_expression(expr, sourceref)
|
||||
else:
|
||||
return process_dynamic_expression(expr, sourceref, scope)
|
||||
return process_dynamic_expression(expr, sourceref)
|
||||
|
||||
|
||||
def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Scope) -> LiteralValue:
|
||||
def process_constant_expression(expr: Any, sourceref: SourceRef) -> LiteralValue:
|
||||
# the expression must result in a single (constant) value (int, float, whatever) wrapped as LiteralValue.
|
||||
if isinstance(expr, (int, float, str, bool)):
|
||||
raise TypeError("expr node should not be a python primitive value", expr, sourceref)
|
||||
elif expr is None or isinstance(expr, LiteralValue):
|
||||
return expr
|
||||
elif isinstance(expr, SymbolName):
|
||||
value = check_symbol_definition(expr.name, symbolscope, expr.sourceref)
|
||||
value = check_symbol_definition(expr.name, expr.my_scope(), expr.sourceref)
|
||||
if isinstance(value, VarDef):
|
||||
if value.vartype == VarType.MEMORY:
|
||||
raise ExpressionEvaluationError("can't take a memory value, must be a constant", expr.sourceref)
|
||||
@ -797,7 +839,7 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
|
||||
raise ExpressionEvaluationError("constant symbol required, not {}".format(value.__class__.__name__), expr.sourceref)
|
||||
elif isinstance(expr, AddressOf):
|
||||
assert isinstance(expr.name, SymbolName)
|
||||
value = check_symbol_definition(expr.name.name, symbolscope, expr.sourceref)
|
||||
value = check_symbol_definition(expr.name.name, expr.my_scope(), expr.sourceref)
|
||||
if isinstance(value, VarDef):
|
||||
if value.vartype == VarType.MEMORY:
|
||||
if isinstance(value.value, LiteralValue):
|
||||
@ -816,7 +858,7 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
|
||||
funcname = expr.target.name
|
||||
if funcname in math_functions or funcname in builtin_functions:
|
||||
func_args = []
|
||||
for a in (process_constant_expression(callarg.value, sourceref, symbolscope) for callarg in expr.arguments.nodes):
|
||||
for a in (process_constant_expression(callarg.value, sourceref) for callarg in expr.arguments.nodes):
|
||||
if isinstance(a, LiteralValue):
|
||||
func_args.append(a.value)
|
||||
else:
|
||||
@ -838,7 +880,7 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
|
||||
raise ExpressionEvaluationError("constant value required, not {}".format(expr.__class__.__name__), expr.sourceref)
|
||||
if expr.unary:
|
||||
left_sourceref = expr.left.sourceref if isinstance(expr.left, AstNode) else sourceref
|
||||
expr.left = process_constant_expression(expr.left, left_sourceref, symbolscope)
|
||||
expr.left = process_constant_expression(expr.left, left_sourceref)
|
||||
if isinstance(expr.left, LiteralValue) and type(expr.left.value) in (int, float):
|
||||
try:
|
||||
if expr.operator == '-':
|
||||
@ -853,12 +895,12 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
|
||||
raise ValueError("invalid operand type for unary operator", expr.left, expr.operator)
|
||||
else:
|
||||
left_sourceref = expr.left.sourceref if isinstance(expr.left, AstNode) else sourceref
|
||||
expr.left = process_constant_expression(expr.left, left_sourceref, symbolscope)
|
||||
expr.left = process_constant_expression(expr.left, left_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)
|
||||
if isinstance(expr.left, LiteralValue):
|
||||
if isinstance(expr.right, LiteralValue):
|
||||
return expr.evaluate_primitive_constants(symbolscope, expr.right.sourceref)
|
||||
return expr.evaluate_primitive_constants(expr.right.sourceref)
|
||||
else:
|
||||
raise ExpressionEvaluationError("constant literal value required on right, not {}"
|
||||
.format(expr.right.__class__.__name__), right_sourceref)
|
||||
@ -867,7 +909,7 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
|
||||
.format(expr.left.__class__.__name__), left_sourceref)
|
||||
|
||||
|
||||
def process_dynamic_expression(expr: Any, sourceref: SourceRef, symbolscope: Scope) -> Any:
|
||||
def process_dynamic_expression(expr: Any, sourceref: SourceRef) -> Any:
|
||||
# constant-fold a dynamic expression
|
||||
if isinstance(expr, (int, float, str, bool)):
|
||||
raise TypeError("expr node should not be a python primitive value", expr, sourceref)
|
||||
@ -875,43 +917,43 @@ def process_dynamic_expression(expr: Any, sourceref: SourceRef, symbolscope: Sco
|
||||
return expr
|
||||
elif isinstance(expr, SymbolName):
|
||||
try:
|
||||
return process_constant_expression(expr, sourceref, symbolscope)
|
||||
return process_constant_expression(expr, sourceref)
|
||||
except ExpressionEvaluationError:
|
||||
return expr
|
||||
elif isinstance(expr, AddressOf):
|
||||
try:
|
||||
return process_constant_expression(expr, sourceref, symbolscope)
|
||||
return process_constant_expression(expr, sourceref)
|
||||
except ExpressionEvaluationError:
|
||||
return expr
|
||||
elif isinstance(expr, SubCall):
|
||||
try:
|
||||
return process_constant_expression(expr, sourceref, symbolscope)
|
||||
return process_constant_expression(expr, sourceref)
|
||||
except ExpressionEvaluationError:
|
||||
if isinstance(expr.target, SymbolName):
|
||||
check_symbol_definition(expr.target.name, symbolscope, expr.target.sourceref)
|
||||
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, symbolscope, expr.operand.sourceref)
|
||||
check_symbol_definition(expr.operand.name, expr.my_scope(), expr.operand.sourceref)
|
||||
return expr
|
||||
elif not isinstance(expr, Expression):
|
||||
raise ParseError("expression required, not {}".format(expr.__class__.__name__), expr.sourceref)
|
||||
if expr.unary:
|
||||
left_sourceref = expr.left.sourceref if isinstance(expr.left, AstNode) else sourceref
|
||||
expr.left = process_dynamic_expression(expr.left, left_sourceref, symbolscope)
|
||||
expr.left = process_dynamic_expression(expr.left, left_sourceref)
|
||||
try:
|
||||
return process_constant_expression(expr, sourceref, symbolscope)
|
||||
return process_constant_expression(expr, sourceref)
|
||||
except ExpressionEvaluationError:
|
||||
return expr
|
||||
else:
|
||||
left_sourceref = expr.left.sourceref if isinstance(expr.left, AstNode) else sourceref
|
||||
expr.left = process_dynamic_expression(expr.left, left_sourceref, symbolscope)
|
||||
expr.left = process_dynamic_expression(expr.left, left_sourceref)
|
||||
right_sourceref = expr.right.sourceref if isinstance(expr.right, AstNode) else sourceref
|
||||
expr.right = process_dynamic_expression(expr.right, right_sourceref, symbolscope)
|
||||
expr.right = process_dynamic_expression(expr.right, right_sourceref)
|
||||
try:
|
||||
return process_constant_expression(expr, sourceref, symbolscope)
|
||||
return process_constant_expression(expr, sourceref)
|
||||
except ExpressionEvaluationError:
|
||||
return expr
|
||||
|
||||
|
@ -1,21 +1,21 @@
|
||||
import pytest
|
||||
from il65 import datatypes
|
||||
from il65.plyparse import coerce_constant_value, LiteralValue
|
||||
from il65.datatypes import DataType, VarType, STRING_DATATYPES, FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE, char_to_bytevalue
|
||||
from il65.plyparse import coerce_constant_value, LiteralValue, Scope, AddressOf, SymbolName, VarDef
|
||||
from il65.compile import ParseError
|
||||
from il65.plylex import SourceRef
|
||||
from il65.emit import to_hex, to_mflpt5
|
||||
|
||||
|
||||
def test_datatypes():
|
||||
assert all(isinstance(s, datatypes.DataType) for s in datatypes.STRING_DATATYPES)
|
||||
assert all(s.isstring() for s in datatypes.STRING_DATATYPES)
|
||||
assert not any(s.isarray() or s.isnumeric() for s in datatypes.STRING_DATATYPES)
|
||||
assert datatypes.DataType.WORDARRAY.isarray()
|
||||
assert not datatypes.DataType.WORDARRAY.isnumeric()
|
||||
assert not datatypes.DataType.WORDARRAY.isstring()
|
||||
assert not datatypes.DataType.WORD.isarray()
|
||||
assert datatypes.DataType.WORD.isnumeric()
|
||||
assert not datatypes.DataType.WORD.isstring()
|
||||
assert all(isinstance(s, DataType) for s in STRING_DATATYPES)
|
||||
assert all(s.isstring() for s in STRING_DATATYPES)
|
||||
assert not any(s.isarray() or s.isnumeric() for s in STRING_DATATYPES)
|
||||
assert DataType.WORDARRAY.isarray()
|
||||
assert not DataType.WORDARRAY.isnumeric()
|
||||
assert not DataType.WORDARRAY.isstring()
|
||||
assert not DataType.WORD.isarray()
|
||||
assert DataType.WORD.isnumeric()
|
||||
assert not DataType.WORD.isstring()
|
||||
|
||||
|
||||
def test_sourceref():
|
||||
@ -72,8 +72,8 @@ def test_float_to_mflpt5():
|
||||
|
||||
|
||||
def test_float_range():
|
||||
assert b"\xff\x7f\xff\xff\xff" == to_mflpt5(datatypes.FLOAT_MAX_POSITIVE)
|
||||
assert b"\xff\xff\xff\xff\xff" == to_mflpt5(datatypes.FLOAT_MAX_NEGATIVE)
|
||||
assert b"\xff\x7f\xff\xff\xff" == to_mflpt5(FLOAT_MAX_POSITIVE)
|
||||
assert b"\xff\xff\xff\xff\xff" == to_mflpt5(FLOAT_MAX_NEGATIVE)
|
||||
with pytest.raises(OverflowError):
|
||||
to_mflpt5(1.7014118346e+38)
|
||||
with pytest.raises(OverflowError):
|
||||
@ -89,54 +89,72 @@ def test_float_range():
|
||||
|
||||
|
||||
def test_char_to_bytevalue():
|
||||
assert datatypes.char_to_bytevalue('a') == 65
|
||||
assert datatypes.char_to_bytevalue('\n') == 13
|
||||
assert datatypes.char_to_bytevalue('π') == 126
|
||||
assert datatypes.char_to_bytevalue('▒') == 230
|
||||
assert datatypes.char_to_bytevalue('\x00') == 0
|
||||
assert datatypes.char_to_bytevalue('\xff') == 255
|
||||
assert char_to_bytevalue('a') == 65
|
||||
assert char_to_bytevalue('\n') == 13
|
||||
assert char_to_bytevalue('π') == 126
|
||||
assert char_to_bytevalue('▒') == 230
|
||||
assert char_to_bytevalue('\x00') == 0
|
||||
assert char_to_bytevalue('\xff') == 255
|
||||
with pytest.raises(AssertionError):
|
||||
datatypes.char_to_bytevalue('<undefined>')
|
||||
char_to_bytevalue('<undefined>')
|
||||
# screencodes not yet implemented: assert datatypes.char_to_bytevalue('a', False) == 65
|
||||
|
||||
|
||||
def test_coerce_value():
|
||||
def test_coerce_value_novars():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
def lv(v) -> LiteralValue:
|
||||
return LiteralValue(value=v, sourceref=SourceRef("test", 1, 1)) # type: ignore
|
||||
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,
|
||||
return LiteralValue(value=v, sourceref=sref) # type: ignore
|
||||
assert coerce_constant_value(DataType.BYTE, lv(0)) == (False, lv(0))
|
||||
assert coerce_constant_value(DataType.BYTE, lv(255)) == (False, lv(255))
|
||||
assert coerce_constant_value(DataType.BYTE, lv('@')) == (True, lv(64))
|
||||
assert coerce_constant_value(DataType.WORD, lv(0)) == (False, lv(0))
|
||||
assert coerce_constant_value(DataType.WORD, lv(65535)) == (False, lv(65535))
|
||||
assert coerce_constant_value(DataType.WORD, lv('@')) == (True, lv(64))
|
||||
assert coerce_constant_value(DataType.FLOAT, lv(-999.22)) == (False, lv(-999.22))
|
||||
assert coerce_constant_value(DataType.FLOAT, lv(123.45)) == (False, lv(123.45))
|
||||
assert coerce_constant_value(DataType.FLOAT, lv('@')) == (True, lv(64))
|
||||
assert coerce_constant_value(DataType.BYTE, lv(5.678)) == (True, lv(5))
|
||||
assert coerce_constant_value(DataType.WORD, lv(5.678)) == (True, lv(5))
|
||||
assert coerce_constant_value(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"))
|
||||
assert coerce_constant_value(DataType.STRING, lv("string")) == (False, lv("string"))
|
||||
assert coerce_constant_value(DataType.STRING_P, lv("string")) == (False, lv("string"))
|
||||
assert coerce_constant_value(DataType.STRING_S, lv("string")) == (False, lv("string"))
|
||||
assert coerce_constant_value(DataType.STRING_PS, lv("string")) == (False, lv("string"))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.BYTE, lv(-1))
|
||||
coerce_constant_value(DataType.BYTE, lv(-1))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.BYTE, lv(256))
|
||||
coerce_constant_value(DataType.BYTE, lv(256))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.BYTE, lv(256.12345))
|
||||
coerce_constant_value(DataType.BYTE, lv(256.12345))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.WORD, lv(-1))
|
||||
coerce_constant_value(DataType.WORD, lv(-1))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.WORD, lv(65536))
|
||||
coerce_constant_value(DataType.WORD, lv(65536))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.WORD, lv(65536.12345))
|
||||
coerce_constant_value(DataType.WORD, lv(65536.12345))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.FLOAT, lv(-1.7014118346e+38))
|
||||
coerce_constant_value(DataType.FLOAT, lv(-1.7014118346e+38))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(datatypes.DataType.FLOAT, lv(1.7014118347e+38))
|
||||
coerce_constant_value(DataType.FLOAT, lv(1.7014118347e+38))
|
||||
with pytest.raises(TypeError):
|
||||
coerce_constant_value(datatypes.DataType.BYTE, lv("string"))
|
||||
coerce_constant_value(DataType.BYTE, lv("string"))
|
||||
with pytest.raises(TypeError):
|
||||
coerce_constant_value(datatypes.DataType.FLOAT, lv("string"))
|
||||
coerce_constant_value(DataType.FLOAT, lv("string"))
|
||||
|
||||
|
||||
def test_coerce_value_vars():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
scope = Scope(nodes=[], level="block", sourceref=sref)
|
||||
vardef = VarDef(name="constantvar", vartype="const", datatype=None, sourceref=sref)
|
||||
vardef.value = LiteralValue(value=99, sourceref=sref)
|
||||
scope.add_node(vardef)
|
||||
vardef = VarDef(name="varvar", vartype="var", datatype=None, sourceref=sref)
|
||||
vardef.value = LiteralValue(value=42, sourceref=sref)
|
||||
scope.add_node(vardef)
|
||||
vardef = VarDef(name="memvar", vartype="memory", datatype=None, sourceref=sref)
|
||||
vardef.value = LiteralValue(value=0xc000, sourceref=sref)
|
||||
scope.add_node(vardef)
|
||||
value = SymbolName(name="constantvar", sourceref=sref)
|
||||
value.parent = scope
|
||||
assert coerce_constant_value(DataType.BYTE, value) == (True, LiteralValue(value=99, sourceref=sref))
|
||||
|
118
tests/test_vardef.py
Normal file
118
tests/test_vardef.py
Normal file
@ -0,0 +1,118 @@
|
||||
import pytest
|
||||
from il65.datatypes import DataType
|
||||
from il65.plyparse import LiteralValue, VarDef, VarType, DatatypeNode, Expression, Scope, AddressOf, SymbolName, UndefinedSymbolError
|
||||
from il65.plylex import SourceRef
|
||||
|
||||
# zero or one subnode: value (an Expression, LiteralValue, AddressOf or SymbolName.).
|
||||
# name = attr.ib(type=str)
|
||||
# vartype = attr.ib()
|
||||
# datatype = attr.ib()
|
||||
# size = attr.ib(type=list, default=None)
|
||||
# zp_address = attr.ib(type=int, default=None, init=False) # the address in the zero page if this var is there, will be set later
|
||||
|
||||
|
||||
def test_creation():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
v = VarDef(name="v1", vartype="const", datatype=None, sourceref=sref)
|
||||
assert v.name == "v1"
|
||||
assert v.vartype == VarType.CONST
|
||||
assert v.datatype == DataType.BYTE
|
||||
assert v.size == [1]
|
||||
assert isinstance(v.value, LiteralValue)
|
||||
assert v.value.value == 0
|
||||
assert v.zp_address is None
|
||||
v = VarDef(name="v2", vartype="memory", datatype=None, sourceref=sref)
|
||||
assert v.vartype == VarType.MEMORY
|
||||
assert isinstance(v.value, LiteralValue)
|
||||
assert v.value.value == 0
|
||||
dt = DatatypeNode(name="float", sourceref=sref)
|
||||
v = VarDef(name="v2", vartype="var", datatype=dt, sourceref=sref)
|
||||
assert v.vartype == VarType.VAR
|
||||
assert v.datatype == DataType.FLOAT
|
||||
assert isinstance(v.value, LiteralValue)
|
||||
assert v.value.value == 0
|
||||
dt = DatatypeNode(name="matrix", sourceref=sref)
|
||||
with pytest.raises(ValueError):
|
||||
VarDef(name="v2", vartype="var", datatype=dt, sourceref=sref)
|
||||
dt.dimensions = [2, 3]
|
||||
v = VarDef(name="v2", vartype="var", datatype=dt, sourceref=sref)
|
||||
assert v.vartype == VarType.VAR
|
||||
assert v.datatype == DataType.MATRIX
|
||||
assert v.size == [2, 3]
|
||||
assert isinstance(v.value, LiteralValue)
|
||||
assert v.value.value == 0
|
||||
dt = DatatypeNode(name="text", sourceref=sref)
|
||||
v = VarDef(name="v2", vartype="var", datatype=dt, sourceref=sref)
|
||||
assert v.vartype == VarType.VAR
|
||||
assert v.datatype == DataType.STRING
|
||||
assert v.size == [1]
|
||||
assert v.value is None
|
||||
|
||||
|
||||
def test_set_value():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
v = VarDef(name="v1", vartype="var", datatype=DatatypeNode(name="word", sourceref=sref), sourceref=sref)
|
||||
assert v.datatype == DataType.WORD
|
||||
assert v.value.value == 0
|
||||
v.value = LiteralValue(value=42, sourceref=sref)
|
||||
assert v.value.value == 42
|
||||
v = VarDef(name="v1", vartype="var", datatype=DatatypeNode(name="text", sourceref=sref), sourceref=sref)
|
||||
assert v.datatype == DataType.STRING
|
||||
assert v.value is None
|
||||
v.value = LiteralValue(value="hello", sourceref=sref)
|
||||
assert v.value.value == "hello"
|
||||
e = Expression(left=LiteralValue(value=42, sourceref=sref), operator="-", unary=True, right=None, sourceref=sref)
|
||||
assert not e.must_be_constant
|
||||
v.value = e
|
||||
assert v.value is e
|
||||
assert e.must_be_constant
|
||||
|
||||
|
||||
def test_const_num_val():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
scope = Scope(nodes=[], level="block", sourceref=sref)
|
||||
vardef = VarDef(name="constvar", vartype="const", datatype=None, sourceref=sref)
|
||||
vardef.value = LiteralValue(value=43, sourceref=sref)
|
||||
scope.add_node(vardef)
|
||||
vardef = VarDef(name="varvar", vartype="var", datatype=None, sourceref=sref)
|
||||
vardef.value = LiteralValue(value=44, sourceref=sref)
|
||||
scope.add_node(vardef)
|
||||
vardef = VarDef(name="memvar", vartype="memory", datatype=None, sourceref=sref)
|
||||
vardef.value = LiteralValue(value=45, sourceref=sref)
|
||||
scope.add_node(vardef)
|
||||
v = VarDef(name="v1", vartype="var", datatype=DatatypeNode(name="word", sourceref=sref), sourceref=sref)
|
||||
with pytest.raises(TypeError):
|
||||
v.const_num_val()
|
||||
v = VarDef(name="v1", vartype="memory", datatype=DatatypeNode(name="word", sourceref=sref), sourceref=sref)
|
||||
with pytest.raises(TypeError):
|
||||
v.const_num_val()
|
||||
v = VarDef(name="v1", vartype="const", datatype=DatatypeNode(name="word", sourceref=sref), sourceref=sref)
|
||||
assert v.const_num_val() == 0
|
||||
v.value = LiteralValue(value=42, sourceref=sref)
|
||||
assert v.const_num_val() == 42
|
||||
v = VarDef(name="v1", vartype="const", datatype=DatatypeNode(name="float", sourceref=sref), sourceref=sref)
|
||||
assert v.const_num_val() == 0
|
||||
v.value = LiteralValue(value=42.9988, sourceref=sref)
|
||||
assert v.const_num_val() == 42.9988
|
||||
e = Expression(left=LiteralValue(value=42, sourceref=sref), operator="-", unary=True, right=None, sourceref=sref)
|
||||
v.value = e
|
||||
with pytest.raises(TypeError):
|
||||
v.const_num_val()
|
||||
s = SymbolName(name="unexisting", sourceref=sref)
|
||||
s.parent = scope
|
||||
v.value = s
|
||||
with pytest.raises(UndefinedSymbolError):
|
||||
v.const_num_val()
|
||||
s = SymbolName(name="constvar", sourceref=sref)
|
||||
s.parent = scope
|
||||
v.value = s
|
||||
assert v.const_num_val() == 43
|
||||
a = AddressOf(name="varvar", sourceref=sref)
|
||||
a.parent = scope
|
||||
v.value = a
|
||||
with pytest.raises(TypeError):
|
||||
v.const_num_val()
|
||||
a = AddressOf(name="memvar", sourceref=sref)
|
||||
a.parent = scope
|
||||
v.value = a
|
||||
assert v.const_num_val() == 45
|
101
todo.ill
101
todo.ill
@ -1,95 +1,24 @@
|
||||
%output basic
|
||||
%import c64lib
|
||||
|
||||
|
||||
~ main {
|
||||
var .byte v1t = true
|
||||
var .byte v1f = false
|
||||
var .word v2t = true
|
||||
var .word v2f = false
|
||||
var .float v3t = true
|
||||
var .float v3f = false
|
||||
var .text v4t = true
|
||||
var .text v4f = false
|
||||
var .array(3) v5t = true
|
||||
var .array(3) v5f = false
|
||||
var .array(10) v6t = true
|
||||
var .array(10) v6f = false
|
||||
var .wordarray(3) v7t = true
|
||||
var .wordarray(3) v7f = false
|
||||
var .wordarray(10) v8t = true
|
||||
var .wordarray(10) v8f = false
|
||||
var .matrix(2,2) v9t = true
|
||||
var .matrix(2,2) v9f = false
|
||||
var .matrix(5,5) v10t = true
|
||||
var .matrix(5,5) v10f = false
|
||||
|
||||
const .byte c1t=true
|
||||
const .byte c1f=false
|
||||
const .word c2t=true
|
||||
const .word c2f=false
|
||||
const .float c3t=true
|
||||
const .float c3f=false
|
||||
|
||||
memory border = $d020
|
||||
|
||||
|
||||
start:
|
||||
%breakpoint abc,def
|
||||
XY=0
|
||||
XY+=255
|
||||
XY+=256
|
||||
XY+=257
|
||||
XY-=255
|
||||
XY-=256
|
||||
XY-=257
|
||||
|
||||
v3t++
|
||||
v3t+=1
|
||||
v3t+=1 ; @todo? (optimize) join with previous
|
||||
v3t+=0 ; is removed.
|
||||
v3t+=1 ; @todo? (optimize) join with previous
|
||||
v3t+=1 ; @todo? (optimize) join with previous
|
||||
v3t=2.23424 ; @todo store as constant float with generated name, replace value node
|
||||
v3t=2.23411 ; @todo store as constant float with generated name, replace value node
|
||||
v3t=1.23411 + 1; @todo store as constant float with generated name, replace value node
|
||||
; v3t+=2.23424 ; @todo store as constant float with generated name, replace value node
|
||||
; v3t+=2.23424 ; @todo store as constant float with generated name, replace value node
|
||||
; v3t+=2.23411 ; @todo store as constant float with generated name, replace value node
|
||||
; v3t+=2.23411 ; @todo store as constant float with generated name, replace value node
|
||||
v3t=2.23424 * v3t ; @todo store as constant float with generated name, replace value node
|
||||
XY*=2
|
||||
XY*=3
|
||||
X=3 ; @todo optimize consecutive assignments
|
||||
X=4
|
||||
X=5
|
||||
X=A=6
|
||||
A=X=6
|
||||
X=A=6
|
||||
X=A=9
|
||||
X=A=10
|
||||
v3t=1 ; @todo optimize consecutive assignments
|
||||
v3t=2
|
||||
v3t=3
|
||||
border = 0 ; @todo do not optimize consecucutive assignments to a memory var
|
||||
border = 1
|
||||
border = 2
|
||||
|
||||
XY=XY/0 ; @todo zerodiv (during expression to code generation)
|
||||
XY=XY//0 ; @todo zerodiv (during expression to code generation)
|
||||
XY*=2.23424 ; @todo store as constant float with generated name, replace value node
|
||||
XY*=2.23424 * v3t ; @todo store as constant float with generated name, replace value node
|
||||
;v3t*=2.23424 * v3t ; @todo store as constant float with generated name, replace value node
|
||||
; A++
|
||||
; X--
|
||||
; A+=1
|
||||
; X-=2
|
||||
; [AX]++
|
||||
; [AX .byte]++
|
||||
; [AX .word]++
|
||||
; [AX .float]++
|
||||
; [$ccc0]++
|
||||
; [$ccc0 .byte]++
|
||||
; [$ccc0 .word]++
|
||||
; [$ccc0 .float]++
|
||||
; A+=2
|
||||
; A+=3
|
||||
; XY+=6
|
||||
; XY+=222
|
||||
; XY+=666
|
||||
XY++
|
||||
XY+=1
|
||||
XY+=1 ; @todo? (optimize) join with previous
|
||||
XY+=0 ; is removed.
|
||||
A+=0 ; is removed.
|
||||
Y+=0 ; is removed.
|
||||
XY+=1 ; @todo? (optimize) join with previous
|
||||
XY+=1 ; @todo? (optimize) join with previous
|
||||
|
||||
return 44
|
||||
}
|
||||
|
103
todo2.ill
Normal file
103
todo2.ill
Normal file
@ -0,0 +1,103 @@
|
||||
%output basic
|
||||
%import c64lib
|
||||
|
||||
|
||||
~ main {
|
||||
var .byte v1t = true
|
||||
var .byte v1f = false
|
||||
var .word v2t = true
|
||||
var .word v2f = false
|
||||
var .float v3t = true
|
||||
var .float v3f = false
|
||||
var .text v4t = true
|
||||
var .text v4f = false
|
||||
var .array(3) v5t = true
|
||||
var .array(3) v5f = false
|
||||
var .array(10) v6t = true
|
||||
var .array(10) v6f = false
|
||||
var .wordarray(3) v7t = true
|
||||
var .wordarray(3) v7f = false
|
||||
var .wordarray(10) v8t = true
|
||||
var .wordarray(10) v8f = false
|
||||
var .matrix(2,2) v9t = true
|
||||
var .matrix(2,2) v9f = false
|
||||
var .matrix(5,5) v10t = true
|
||||
var .matrix(5,5) v10f = false
|
||||
|
||||
const .byte c1t=true
|
||||
const .byte c1f=false
|
||||
const .word c2t=true
|
||||
const .word c2f=false
|
||||
const .float c3t=true
|
||||
const .float c3f=false
|
||||
|
||||
memory border = $d020
|
||||
|
||||
|
||||
start:
|
||||
%breakpoint abc,def
|
||||
|
||||
XY+=255
|
||||
XY+=256
|
||||
XY+=257
|
||||
XY-=255
|
||||
XY-=256
|
||||
XY-=257
|
||||
|
||||
v3t++
|
||||
v3t+=1
|
||||
v3t+=1 ; @todo? (optimize) join with previous
|
||||
v3t+=0 ; is removed.
|
||||
v3t+=1 ; @todo? (optimize) join with previous
|
||||
v3t+=1 ; @todo? (optimize) join with previous
|
||||
v3t=2.23424 ; @todo store as constant float with generated name, replace value node
|
||||
v3t=2.23411 ; @todo store as constant float with generated name, replace value node
|
||||
v3t=1.23411 + 1; @todo store as constant float with generated name, replace value node
|
||||
; v3t+=2.23424 ; @todo store as constant float with generated name, replace value node
|
||||
; v3t+=2.23424 ; @todo store as constant float with generated name, replace value node
|
||||
; v3t+=2.23411 ; @todo store as constant float with generated name, replace value node
|
||||
; v3t+=2.23411 ; @todo store as constant float with generated name, replace value node
|
||||
v3t=2.23424 * v3t ; @todo store as constant float with generated name, replace value node
|
||||
XY*=2
|
||||
XY*=3
|
||||
X=3 ; @todo optimize consecutive assignments
|
||||
X=4
|
||||
X=5
|
||||
X=A=6
|
||||
A=X=6
|
||||
X=A=6
|
||||
X=A=9
|
||||
X=A=10
|
||||
v3t=1 ; @todo optimize consecutive assignments
|
||||
v3t=2
|
||||
v3t=3
|
||||
border = 0 ; @todo do not optimize consecucutive assignments to a memory var
|
||||
border = 1
|
||||
border = 2
|
||||
|
||||
XY=XY/0 ; @todo zerodiv (during expression to code generation)
|
||||
XY=XY//0 ; @todo zerodiv (during expression to code generation)
|
||||
XY*=2.23424 ; @todo store as constant float with generated name, replace value node
|
||||
XY*=2.23424 * v3t ; @todo store as constant float with generated name, replace value node
|
||||
;v3t*=2.23424 * v3t ; @todo store as constant float with generated name, replace value node
|
||||
; A++
|
||||
; X--
|
||||
; A+=1
|
||||
; X-=2
|
||||
; [AX]++
|
||||
; [AX .byte]++
|
||||
; [AX .word]++
|
||||
; [AX .float]++
|
||||
; [$ccc0]++
|
||||
; [$ccc0 .byte]++
|
||||
; [$ccc0 .word]++
|
||||
; [$ccc0 .float]++
|
||||
; A+=2
|
||||
; A+=3
|
||||
; XY+=6
|
||||
; XY+=222
|
||||
; XY+=666
|
||||
|
||||
return 44
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user