mirror of
https://github.com/irmen/prog8.git
synced 2024-12-24 16:29:21 +00:00
expressions
This commit is contained in:
parent
861379c4d7
commit
67f1941766
@ -14,7 +14,7 @@ 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, AstNode, datatype_of, coerce_constant_value, \
|
SymbolName, Dereference, AddressOf, IncrDecr, AstNode, datatype_of, coerce_constant_value, \
|
||||||
check_symbol_definition, UndefinedSymbolError
|
check_symbol_definition, UndefinedSymbolError, process_expression
|
||||||
from .plylex import SourceRef, print_bold
|
from .plylex import SourceRef, print_bold
|
||||||
from .datatypes import DataType, VarType
|
from .datatypes import DataType, VarType
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ class PlyParser:
|
|||||||
self.check_all_symbolnames(module)
|
self.check_all_symbolnames(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_and_symbolnames(module)
|
self.process_all_expressions(module)
|
||||||
return module # XXX
|
return module # XXX
|
||||||
# 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:
|
||||||
@ -152,7 +152,7 @@ class PlyParser:
|
|||||||
for node in module.all_nodes([SymbolName]):
|
for node in module.all_nodes([SymbolName]):
|
||||||
check_symbol_definition(node.name, node.my_scope(), node.sourceref)
|
check_symbol_definition(node.name, node.my_scope(), node.sourceref)
|
||||||
|
|
||||||
def process_all_expressions_and_symbolnames(self, module: Module) -> None:
|
def process_all_expressions(self, module: Module) -> None:
|
||||||
# process/simplify all expressions (constant folding etc)
|
# process/simplify all expressions (constant folding etc)
|
||||||
encountered_blocks = set()
|
encountered_blocks = set()
|
||||||
for node in module.all_nodes():
|
for node in module.all_nodes():
|
||||||
@ -163,13 +163,12 @@ class PlyParser:
|
|||||||
raise ValueError("block names not unique:", blockname)
|
raise ValueError("block names not unique:", blockname)
|
||||||
encountered_blocks.add(blockname)
|
encountered_blocks.add(blockname)
|
||||||
elif isinstance(node, Expression):
|
elif isinstance(node, Expression):
|
||||||
print("EXPRESSION", node) # XXX
|
try:
|
||||||
# try:
|
process_expression(node, node.my_scope(), node.sourceref)
|
||||||
# node.process_expressions(block.scope)
|
except ParseError:
|
||||||
# except ParseError:
|
raise
|
||||||
# raise
|
except Exception as x:
|
||||||
# except Exception as x:
|
self.handle_internal_error(x, "process_expressions of node {}".format(node))
|
||||||
# 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):
|
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)
|
_, node.howmuch = coerce_constant_value(datatype_of(node.target, node.my_scope()), node.howmuch, node.sourceref)
|
||||||
elif isinstance(node, Assignment):
|
elif isinstance(node, Assignment):
|
||||||
|
@ -59,10 +59,13 @@ tokens = (
|
|||||||
"BITOR",
|
"BITOR",
|
||||||
"BITXOR",
|
"BITXOR",
|
||||||
"BITINVERT",
|
"BITINVERT",
|
||||||
|
"SHIFTLEFT",
|
||||||
|
"SHIFTRIGHT",
|
||||||
"LOGICAND",
|
"LOGICAND",
|
||||||
"LOGICOR",
|
"LOGICOR",
|
||||||
"LOGICNOT",
|
"LOGICNOT",
|
||||||
"INTEGERDIVIDE",
|
"INTEGERDIVIDE",
|
||||||
|
"MODULO",
|
||||||
"POWER",
|
"POWER",
|
||||||
"LABEL",
|
"LABEL",
|
||||||
"IF",
|
"IF",
|
||||||
@ -75,6 +78,8 @@ literals = ['+', '-', '*', '/', '(', ')', '[', ']', '{', '}', '.', ',', '!', '?'
|
|||||||
|
|
||||||
# regex rules for simple tokens
|
# regex rules for simple tokens
|
||||||
|
|
||||||
|
t_SHIFTLEFT = r"<<"
|
||||||
|
t_SHIFTRIGHT = r">>"
|
||||||
t_INTEGERDIVIDE = r"//"
|
t_INTEGERDIVIDE = r"//"
|
||||||
t_BITAND = r"&"
|
t_BITAND = r"&"
|
||||||
t_BITOR = r"\|"
|
t_BITOR = r"\|"
|
||||||
@ -118,6 +123,7 @@ reserved = {
|
|||||||
"not": "LOGICNOT",
|
"not": "LOGICNOT",
|
||||||
"and": "LOGICAND",
|
"and": "LOGICAND",
|
||||||
"or": "LOGICOR",
|
"or": "LOGICOR",
|
||||||
|
"mod": "MODULO",
|
||||||
"AX": "REGISTER",
|
"AX": "REGISTER",
|
||||||
"AY": "REGISTER",
|
"AY": "REGISTER",
|
||||||
"XY": "REGISTER",
|
"XY": "REGISTER",
|
||||||
|
182
il65/plyparse.py
182
il65/plyparse.py
@ -61,8 +61,6 @@ class AstNode:
|
|||||||
sourceref = attr.ib(type=SourceRef)
|
sourceref = attr.ib(type=SourceRef)
|
||||||
parent = attr.ib(init=False, default=None) # will be hooked up later
|
parent = attr.ib(init=False, default=None) # will be hooked up later
|
||||||
nodes = attr.ib(type=list, init=False, default=attr.Factory(list)) # type: List['AstNode']
|
nodes = attr.ib(type=list, init=False, default=attr.Factory(list)) # type: List['AstNode']
|
||||||
# when evaluating an expression, does it have to be a constant value?:
|
|
||||||
processed_expr_must_be_constant = attr.ib(type=bool, init=False, default=False)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lineref(self) -> str:
|
def lineref(self) -> str:
|
||||||
@ -70,7 +68,7 @@ class AstNode:
|
|||||||
|
|
||||||
def my_scope(self) -> 'Scope':
|
def my_scope(self) -> 'Scope':
|
||||||
# returns the closest Scope in the ancestry of this node, or raises LookupError if no scope is found
|
# returns the closest Scope in the ancestry of this node, or raises LookupError if no scope is found
|
||||||
scope = self
|
scope = self.parent
|
||||||
while scope:
|
while scope:
|
||||||
if isinstance(scope, Scope):
|
if isinstance(scope, Scope):
|
||||||
return scope
|
return scope
|
||||||
@ -104,11 +102,6 @@ class AstNode:
|
|||||||
else:
|
else:
|
||||||
self.nodes.insert(index, newnode)
|
self.nodes.insert(index, newnode)
|
||||||
|
|
||||||
def process_expressions(self, scope: 'Scope') -> None: # XXX remove, use all_nodes
|
|
||||||
# process/simplify all expressions (constant folding etc)
|
|
||||||
# this is implemented in node types that have expression(s) and that should act on this.
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s(cmp=False)
|
||||||
class Directive(AstNode):
|
class Directive(AstNode):
|
||||||
@ -125,7 +118,17 @@ class Scope(AstNode):
|
|||||||
symbols = attr.ib(init=False)
|
symbols = attr.ib(init=False)
|
||||||
name = attr.ib(init=False) # will be set by enclosing block, or subroutine etc.
|
name = attr.ib(init=False) # will be set by enclosing block, or subroutine etc.
|
||||||
parent_scope = attr.ib(init=False, default=None) # will be wired up later
|
parent_scope = attr.ib(init=False, default=None) # will be wired up later
|
||||||
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)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def save_registers(self) -> bool:
|
||||||
|
if self._save_registers is not None:
|
||||||
|
return self._save_registers
|
||||||
|
return self.my_scope().save_registers
|
||||||
|
|
||||||
|
@save_registers.setter
|
||||||
|
def save_registers(self, save: bool) -> None:
|
||||||
|
self._save_registers = save
|
||||||
|
|
||||||
def __attrs_post_init__(self):
|
def __attrs_post_init__(self):
|
||||||
# populate the symbol table for this scope for fast lookups via scope.lookup("name") or scope.lookup("dotted.name")
|
# populate the symbol table for this scope for fast lookups via scope.lookup("name") or scope.lookup("dotted.name")
|
||||||
@ -238,6 +241,7 @@ class Block(AstNode):
|
|||||||
|
|
||||||
@scope.setter
|
@scope.setter
|
||||||
def scope(self, scope: Scope) -> None:
|
def scope(self, scope: Scope) -> None:
|
||||||
|
assert isinstance(scope, Scope)
|
||||||
self.nodes.clear()
|
self.nodes.clear()
|
||||||
self.nodes.append(scope)
|
self.nodes.append(scope)
|
||||||
scope.name = self.name
|
scope.name = self.name
|
||||||
@ -373,6 +377,7 @@ class Subroutine(AstNode):
|
|||||||
|
|
||||||
@scope.setter
|
@scope.setter
|
||||||
def scope(self, scope: Scope) -> None:
|
def scope(self, scope: Scope) -> None:
|
||||||
|
assert isinstance(scope, Scope)
|
||||||
self.nodes.clear()
|
self.nodes.clear()
|
||||||
self.nodes.append(scope)
|
self.nodes.append(scope)
|
||||||
scope.name = self.name
|
scope.name = self.name
|
||||||
@ -385,10 +390,6 @@ class Goto(AstNode):
|
|||||||
# one or two subnodes: target (SymbolName, int or Dereference) and optionally: condition (Expression)
|
# one or two subnodes: target (SymbolName, int or Dereference) and optionally: condition (Expression)
|
||||||
if_stmt = attr.ib(default=None)
|
if_stmt = attr.ib(default=None)
|
||||||
|
|
||||||
def process_expressions(self, scope: Scope) -> None:
|
|
||||||
if len(self.nodes) == 2:
|
|
||||||
self.nodes[1] = process_expression(self.nodes[1], scope, self.nodes[1].sourceref)
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=True, slots=True)
|
@attr.s(cmp=True, slots=True)
|
||||||
class LiteralValue(AstNode):
|
class LiteralValue(AstNode):
|
||||||
@ -450,6 +451,7 @@ class IncrDecr(AstNode):
|
|||||||
raise ParseError("cannot incr/decr that register", self.sourceref)
|
raise ParseError("cannot incr/decr that register", self.sourceref)
|
||||||
if isinstance(target, TargetRegisters):
|
if isinstance(target, TargetRegisters):
|
||||||
raise ParseError("cannot incr/decr multiple registers at once", self.sourceref)
|
raise ParseError("cannot incr/decr multiple registers at once", self.sourceref)
|
||||||
|
assert isinstance(target, (Register, SymbolName, Dereference))
|
||||||
self.nodes.clear()
|
self.nodes.clear()
|
||||||
self.nodes.append(target)
|
self.nodes.append(target)
|
||||||
|
|
||||||
@ -466,22 +468,23 @@ class Expression(AstNode):
|
|||||||
operator = attr.ib(type=str)
|
operator = attr.ib(type=str)
|
||||||
right = attr.ib()
|
right = attr.ib()
|
||||||
unary = attr.ib(type=bool, default=False)
|
unary = attr.ib(type=bool, default=False)
|
||||||
|
# when evaluating an expression, does it have to be a constant value?
|
||||||
|
must_be_constant = attr.ib(type=bool, init=False, default=False)
|
||||||
|
|
||||||
def __attrs_post_init__(self):
|
def __attrs_post_init__(self):
|
||||||
assert self.operator not in ("++", "--"), "incr/decr should not be an expression"
|
assert self.operator not in ("++", "--"), "incr/decr should not be an expression"
|
||||||
|
if self.operator == "mod":
|
||||||
|
self.operator = "%" # change it back to the more common '%'
|
||||||
|
|
||||||
def process_expressions(self, scope: Scope) -> None:
|
def evaluate_primitive_constants(self, scope: Scope, sourceref: SourceRef) -> LiteralValue:
|
||||||
raise RuntimeError("must be done via parent node's process_expressions")
|
|
||||||
|
|
||||||
def evaluate_primitive_constants(self, scope: Scope) -> LiteralValue:
|
|
||||||
# make sure the lvalue and rvalue are primitives, and the operator is allowed
|
# make sure the lvalue and rvalue are primitives, and the operator is allowed
|
||||||
assert isinstance(self.left, LiteralValue)
|
assert isinstance(self.left, LiteralValue)
|
||||||
assert isinstance(self.right, LiteralValue)
|
assert isinstance(self.right, LiteralValue)
|
||||||
if self.operator not in {'+', '-', '*', '/', '//', '~', '<', '>', '<=', '>=', '==', '!='}:
|
if self.operator not in {'+', '-', '*', '/', '//', '~', '|', '&', '%', '<<', '>>', '<', '>', '<=', '>=', '==', '!='}:
|
||||||
raise ValueError("operator", self)
|
raise ValueError("operator", self.operator)
|
||||||
estr = "{} {} {}".format(repr(self.left.value), self.operator, repr(self.right.value))
|
estr = "{} {} {}".format(repr(self.left.value), self.operator, repr(self.right.value))
|
||||||
try:
|
try:
|
||||||
return eval(estr, {}, {}) # safe because of checks above
|
return LiteralValue(value=eval(estr, {}, {}), sourceref=sourceref) # type: ignore # safe because of checks above
|
||||||
except Exception as x:
|
except Exception as x:
|
||||||
raise ExpressionEvaluationError("expression error: " + str(x), self.sourceref) from None
|
raise ExpressionEvaluationError("expression error: " + str(x), self.sourceref) from None
|
||||||
|
|
||||||
@ -508,9 +511,6 @@ class CallArgument(AstNode):
|
|||||||
def value(self) -> Expression:
|
def value(self) -> Expression:
|
||||||
return self.nodes[0] # type: ignore
|
return self.nodes[0] # type: ignore
|
||||||
|
|
||||||
def process_expressions(self, scope: Scope) -> None:
|
|
||||||
self.nodes[0] = process_expression(self.nodes[0], scope, self.sourceref)
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s(cmp=False)
|
||||||
class CallArguments(AstNode):
|
class CallArguments(AstNode):
|
||||||
@ -537,11 +537,6 @@ class SubCall(AstNode):
|
|||||||
def arguments(self) -> CallArguments:
|
def arguments(self) -> CallArguments:
|
||||||
return self.nodes[2] # type: ignore
|
return self.nodes[2] # type: ignore
|
||||||
|
|
||||||
def process_expressions(self, scope: Scope) -> None:
|
|
||||||
for callarg in self.nodes[2].nodes:
|
|
||||||
assert isinstance(callarg, CallArgument)
|
|
||||||
callarg.process_expressions(scope)
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, slots=True, repr=False)
|
@attr.s(cmp=False, slots=True, repr=False)
|
||||||
class VarDef(AstNode):
|
class VarDef(AstNode):
|
||||||
@ -553,18 +548,19 @@ class VarDef(AstNode):
|
|||||||
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
|
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
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> Expression:
|
def value(self) -> Union[LiteralValue, Expression]:
|
||||||
return self.nodes[0] if self.nodes else None # type: ignore
|
return self.nodes[0] if self.nodes else None # type: ignore
|
||||||
|
|
||||||
@value.setter
|
@value.setter
|
||||||
def value(self, newvalue: Expression) -> None:
|
def value(self, value: Union[LiteralValue, Expression]) -> None:
|
||||||
|
assert isinstance(value, (LiteralValue, Expression))
|
||||||
if self.nodes:
|
if self.nodes:
|
||||||
self.nodes[0] = newvalue
|
self.nodes[0] = value
|
||||||
else:
|
else:
|
||||||
self.nodes.append(newvalue)
|
self.nodes.append(value)
|
||||||
# if the value is an expression, mark it as a *constant* expression here
|
# if the value is an expression, mark it as a *constant* expression here
|
||||||
if isinstance(self.nodes[0], AstNode): # XXX expression only?
|
if isinstance(value, Expression):
|
||||||
self.value.processed_expr_must_be_constant = True
|
value.must_be_constant = True
|
||||||
|
|
||||||
def __attrs_post_init__(self):
|
def __attrs_post_init__(self):
|
||||||
# convert vartype to enum
|
# convert vartype to enum
|
||||||
@ -588,24 +584,13 @@ class VarDef(AstNode):
|
|||||||
if self.datatype.isarray() and sum(self.size) in (0, 1):
|
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 for efficiency".format(self.sourceref))
|
||||||
if self.value is None and (self.datatype.isnumeric() or self.datatype.isarray()):
|
if self.value is None and (self.datatype.isnumeric() or self.datatype.isarray()):
|
||||||
self.value = 0
|
self.value = LiteralValue(value=0, sourceref=self.sourceref)
|
||||||
# if it's a matrix with interleave, it must be memory mapped
|
# if it's a matrix with interleave, it must be memory mapped
|
||||||
if self.datatype == DataType.MATRIX and len(self.size) == 3:
|
if self.datatype == DataType.MATRIX and len(self.size) == 3:
|
||||||
if self.vartype != VarType.MEMORY:
|
if self.vartype != VarType.MEMORY:
|
||||||
raise ParseError("matrix with interleave can only be a memory-mapped variable", self.sourceref)
|
raise ParseError("matrix with interleave can only be a memory-mapped variable", self.sourceref)
|
||||||
# note: value coercion is done later, when all expressions are evaluated
|
# note: value coercion is done later, when all expressions are evaluated
|
||||||
|
|
||||||
def process_expressions(self, scope: Scope) -> None:
|
|
||||||
self.value = process_expression(self.value, scope, self.sourceref)
|
|
||||||
assert not isinstance(self.value, Expression), "processed expression for vardef should reduce to a constant value"
|
|
||||||
if self.vartype in (VarType.CONST, VarType.VAR):
|
|
||||||
try:
|
|
||||||
_, self.value = coerce_constant_value(self.datatype, self.value, self.sourceref)
|
|
||||||
except OverflowError as x:
|
|
||||||
raise ParseError(str(x), self.sourceref) from None
|
|
||||||
except TypeError as x:
|
|
||||||
raise ParseError("processed expression vor vardef is not a constant value: " + str(x), self.sourceref) from None
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, repr=False)
|
@attr.s(cmp=False, repr=False)
|
||||||
class Return(AstNode):
|
class Return(AstNode):
|
||||||
@ -622,29 +607,6 @@ class Return(AstNode):
|
|||||||
def value_Y(self) -> Expression:
|
def value_Y(self) -> Expression:
|
||||||
return self.nodes[0] # type: ignore
|
return self.nodes[0] # type: ignore
|
||||||
|
|
||||||
def process_expressions(self, scope: Scope) -> None:
|
|
||||||
if self.nodes[0] is not None:
|
|
||||||
self.nodes[0] = process_expression(self.nodes[0], scope, self.sourceref)
|
|
||||||
if isinstance(self.nodes[0], (int, float, str, bool)):
|
|
||||||
try:
|
|
||||||
_, self.nodes[0] = coerce_constant_value(DataType.BYTE, self.nodes[0], self.sourceref)
|
|
||||||
except (OverflowError, TypeError) as x:
|
|
||||||
raise ParseError("first value (A): " + str(x), self.sourceref) from None
|
|
||||||
if self.nodes[1] is not None:
|
|
||||||
self.nodes[1] = process_expression(self.nodes[1], scope, self.sourceref)
|
|
||||||
if isinstance(self.nodes[1], (int, float, str, bool)):
|
|
||||||
try:
|
|
||||||
_, self.nodes[1] = coerce_constant_value(DataType.BYTE, self.nodes[1], self.sourceref)
|
|
||||||
except (OverflowError, TypeError) as x:
|
|
||||||
raise ParseError("second value (X): " + str(x), self.sourceref) from None
|
|
||||||
if self.nodes[2] is not None:
|
|
||||||
self.nodes[2] = process_expression(self.nodes[2], scope, self.sourceref)
|
|
||||||
if isinstance(self.nodes[2], (int, float, str, bool)):
|
|
||||||
try:
|
|
||||||
_, self.nodes[2] = coerce_constant_value(DataType.BYTE, self.nodes[2], self.sourceref)
|
|
||||||
except (OverflowError, TypeError) as x:
|
|
||||||
raise ParseError("third value (Y): " + str(x), self.sourceref) from None
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, slots=True, repr=False)
|
@attr.s(cmp=False, slots=True, repr=False)
|
||||||
class AssignmentTargets(AstNode):
|
class AssignmentTargets(AstNode):
|
||||||
@ -667,11 +629,9 @@ class Assignment(AstNode):
|
|||||||
|
|
||||||
@right.setter
|
@right.setter
|
||||||
def right(self, rvalue: Union[LiteralValue, Expression]) -> None:
|
def right(self, rvalue: Union[LiteralValue, Expression]) -> None:
|
||||||
|
assert isinstance(rvalue, (LiteralValue, Expression))
|
||||||
self.nodes[1] = rvalue
|
self.nodes[1] = rvalue
|
||||||
|
|
||||||
def process_expressions(self, scope: Scope) -> None:
|
|
||||||
self.nodes[1] = process_expression(self.nodes[1], scope, self.nodes[1].sourceref)
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, slots=True, repr=False)
|
@attr.s(cmp=False, slots=True, repr=False)
|
||||||
class AugAssignment(AstNode):
|
class AugAssignment(AstNode):
|
||||||
@ -686,9 +646,6 @@ class AugAssignment(AstNode):
|
|||||||
def right(self) -> Expression:
|
def right(self) -> Expression:
|
||||||
return self.nodes[1] # type: ignore
|
return self.nodes[1] # type: ignore
|
||||||
|
|
||||||
def process_expressions(self, scope: Scope) -> None:
|
|
||||||
self.nodes[1] = process_expression(self.nodes[1], scope, self.right.sourceref)
|
|
||||||
|
|
||||||
|
|
||||||
def datatype_of(assignmenttarget: AstNode, scope: Scope) -> DataType:
|
def datatype_of(assignmenttarget: AstNode, scope: Scope) -> DataType:
|
||||||
# tries to determine the DataType of an assignment target node
|
# tries to determine the DataType of an assignment target node
|
||||||
@ -749,26 +706,20 @@ def coerce_constant_value(datatype: DataType, value: AstNode,
|
|||||||
return False, value
|
return False, value
|
||||||
|
|
||||||
|
|
||||||
def process_expression(value: Any, scope: Scope, sourceref: SourceRef) -> Any:
|
def process_expression(expr: Expression, scope: Scope, sourceref: SourceRef) -> Any:
|
||||||
# process/simplify all expressions (constant folding etc)
|
# process/simplify all expressions (constant folding etc)
|
||||||
if isinstance(value, AstNode):
|
if expr.must_be_constant:
|
||||||
must_be_constant = value.processed_expr_must_be_constant
|
return process_constant_expression(expr, sourceref, scope)
|
||||||
else:
|
else:
|
||||||
must_be_constant = False
|
return process_dynamic_expression(expr, sourceref, scope)
|
||||||
if must_be_constant:
|
|
||||||
return process_constant_expression(value, sourceref, scope)
|
|
||||||
else:
|
|
||||||
return process_dynamic_expression(value, sourceref, scope)
|
|
||||||
|
|
||||||
|
|
||||||
def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Scope) -> LiteralValue:
|
def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Scope) -> LiteralValue:
|
||||||
# the expression must result in a single (constant) value (int, float, whatever)
|
# the expression must result in a single (constant) value (int, float, whatever) wrapped as LiteralValue.
|
||||||
if isinstance(expr, (int, float, str, bool)):
|
if isinstance(expr, (int, float, str, bool)):
|
||||||
raise TypeError("expr node should not be a python primitive value", expr)
|
raise TypeError("expr node should not be a python primitive value", expr, sourceref)
|
||||||
elif expr is None or isinstance(expr, LiteralValue):
|
elif expr is None or isinstance(expr, LiteralValue):
|
||||||
return expr
|
return expr
|
||||||
elif isinstance(expr, LiteralValue):
|
|
||||||
return expr.value
|
|
||||||
elif isinstance(expr, SymbolName):
|
elif isinstance(expr, SymbolName):
|
||||||
value = check_symbol_definition(expr.name, symbolscope, expr.sourceref)
|
value = check_symbol_definition(expr.name, symbolscope, expr.sourceref)
|
||||||
if isinstance(value, VarDef):
|
if isinstance(value, VarDef):
|
||||||
@ -809,7 +760,7 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
|
|||||||
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 LiteralValue(value=func(*func_args), sourceref=expr.arguments.sourceref) # type: ignore
|
||||||
except Exception as x:
|
except Exception as x:
|
||||||
raise ExpressionEvaluationError(str(x), expr.sourceref)
|
raise ExpressionEvaluationError(str(x), expr.sourceref)
|
||||||
else:
|
else:
|
||||||
@ -844,7 +795,7 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
|
|||||||
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):
|
if isinstance(expr.left, LiteralValue):
|
||||||
if isinstance(expr.right, LiteralValue):
|
if isinstance(expr.right, LiteralValue):
|
||||||
return expr.evaluate_primitive_constants(symbolscope)
|
return expr.evaluate_primitive_constants(symbolscope, expr.right.sourceref)
|
||||||
else:
|
else:
|
||||||
raise ExpressionEvaluationError("constant literal value required on right, not {}"
|
raise ExpressionEvaluationError("constant literal value required on right, not {}"
|
||||||
.format(expr.right.__class__.__name__), right_sourceref)
|
.format(expr.right.__class__.__name__), right_sourceref)
|
||||||
@ -853,19 +804,12 @@ 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 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
|
return expr
|
||||||
elif isinstance(expr, LiteralValue):
|
|
||||||
return expr.value
|
|
||||||
elif isinstance(expr, SymbolName):
|
elif isinstance(expr, SymbolName):
|
||||||
try:
|
try:
|
||||||
return process_constant_expression(expr, sourceref, symbolscope)
|
return process_constant_expression(expr, sourceref, symbolscope)
|
||||||
@ -909,6 +853,13 @@ def process_dynamic_expression(expr: Any, sourceref: SourceRef, symbolscope: Sco
|
|||||||
return expr
|
return expr
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
# ----------------- PLY parser definition follows ----------------------
|
# ----------------- PLY parser definition follows ----------------------
|
||||||
|
|
||||||
def p_start(p):
|
def p_start(p):
|
||||||
@ -1401,14 +1352,23 @@ def p_aug_assignment(p):
|
|||||||
"""
|
"""
|
||||||
p[0] = AugAssignment(operator=p[2], sourceref=_token_sref(p, 2))
|
p[0] = AugAssignment(operator=p[2], sourceref=_token_sref(p, 2))
|
||||||
p[0].nodes.append(p[1])
|
p[0].nodes.append(p[1])
|
||||||
p[0].nodes.append(p[2])
|
p[0].nodes.append(p[3])
|
||||||
|
|
||||||
|
|
||||||
precedence = (
|
precedence = (
|
||||||
('left', '+', '-'),
|
# following the python operator precedence rules mostly; https://docs.python.org/3/reference/expressions.html#operator-precedence
|
||||||
('left', '*', '/', 'INTEGERDIVIDE'),
|
('left', 'LOGICOR'),
|
||||||
('right', 'UNARY_MINUS', 'BITINVERT', "UNARY_ADDRESSOF"),
|
('left', 'LOGICAND'),
|
||||||
|
('right', 'LOGICNOT'),
|
||||||
('left', "LT", "GT", "LE", "GE", "EQUALS", "NOTEQUALS"),
|
('left', "LT", "GT", "LE", "GE", "EQUALS", "NOTEQUALS"),
|
||||||
|
('left', 'BITOR'),
|
||||||
|
('left', 'BITXOR'),
|
||||||
|
('left', 'BITAND'),
|
||||||
|
('left', 'SHIFTLEFT', 'SHIFTRIGHT'),
|
||||||
|
('left', '+', '-'),
|
||||||
|
('left', '*', '/', 'INTEGERDIVIDE', 'MODULO'),
|
||||||
|
('right', 'UNARY_MINUS', 'BITINVERT', "UNARY_ADDRESSOF"),
|
||||||
|
('left', 'POWER'),
|
||||||
('nonassoc', "COMMENT"),
|
('nonassoc', "COMMENT"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1419,6 +1379,15 @@ def p_expression(p):
|
|||||||
| expression '-' expression
|
| expression '-' expression
|
||||||
| expression '*' expression
|
| expression '*' expression
|
||||||
| expression '/' expression
|
| expression '/' expression
|
||||||
|
| expression MODULO expression
|
||||||
|
| expression BITOR expression
|
||||||
|
| expression BITXOR expression
|
||||||
|
| expression BITAND expression
|
||||||
|
| expression SHIFTLEFT expression
|
||||||
|
| expression SHIFTRIGHT expression
|
||||||
|
| expression LOGICOR expression
|
||||||
|
| expression LOGICAND expression
|
||||||
|
| expression POWER expression
|
||||||
| expression INTEGERDIVIDE expression
|
| expression INTEGERDIVIDE expression
|
||||||
| expression LT expression
|
| expression LT expression
|
||||||
| expression GT expression
|
| expression GT expression
|
||||||
@ -1451,6 +1420,13 @@ def p_unary_expression_bitinvert(p):
|
|||||||
p[0] = Expression(left=p[2], operator=p[1], right=None, unary=True, sourceref=_token_sref(p, 1))
|
p[0] = Expression(left=p[2], operator=p[1], right=None, unary=True, sourceref=_token_sref(p, 1))
|
||||||
|
|
||||||
|
|
||||||
|
def p_unary_expression_logicnot(p):
|
||||||
|
"""
|
||||||
|
expression : LOGICNOT expression
|
||||||
|
"""
|
||||||
|
p[0] = Expression(left=p[2], operator=p[1], right=None, unary=True, sourceref=_token_sref(p, 1))
|
||||||
|
|
||||||
|
|
||||||
def p_expression_group(p):
|
def p_expression_group(p):
|
||||||
"""
|
"""
|
||||||
expression : '(' expression ')'
|
expression : '(' expression ')'
|
||||||
|
Loading…
Reference in New Issue
Block a user