|
|
@@ -1,7 +1,7 @@
|
|
|
from abc import *
|
|
|
from typing import *
|
|
|
from dataclasses import *
|
|
|
-from sccd.syntax.datamodel import *
|
|
|
+from sccd.syntax.scope import *
|
|
|
from sccd.util.duration import *
|
|
|
|
|
|
# to inspect types in Python 3.7
|
|
|
@@ -9,169 +9,190 @@ from sccd.util.duration import *
|
|
|
import typing_inspect
|
|
|
|
|
|
class Expression(ABC):
|
|
|
+ # Must be called exactly once on each expression. May throw.
|
|
|
+ # Returns static type of expression.
|
|
|
+ @abstractmethod
|
|
|
+ def init_rvalue(self, scope) -> type:
|
|
|
+ pass
|
|
|
+
|
|
|
# Evaluation should NOT have side effects.
|
|
|
# Motivation is that the evaluation of a guard condition cannot have side effects.
|
|
|
@abstractmethod
|
|
|
- def eval(self, events, datamodel):
|
|
|
+ def eval(self, current_state, events, memory):
|
|
|
pass
|
|
|
|
|
|
- # Types of expressions are statically checked in SCCD.
|
|
|
+class LValue(Expression):
|
|
|
@abstractmethod
|
|
|
- def get_static_type(self) -> type:
|
|
|
+ def init_lvalue(self, scope, expected_type: type):
|
|
|
pass
|
|
|
|
|
|
-class LHS(Expression):
|
|
|
@abstractmethod
|
|
|
- def lhs(self, events, datamodel) -> Variable:
|
|
|
+ def eval_lvalue(self, current_state, events, memory) -> Variable:
|
|
|
pass
|
|
|
|
|
|
- # LHS types are expressions too!
|
|
|
- def eval(self, events, datamodel):
|
|
|
- return self.lhs(events, datamodel).value
|
|
|
+ # LValues are expressions too!
|
|
|
+ def eval(self, current_state, events, memory):
|
|
|
+ variable = self.eval_lvalue(current_state, events, memory)
|
|
|
+ return memory.load(variable.offset)
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
-class Identifier(LHS):
|
|
|
+class Identifier(LValue):
|
|
|
name: str
|
|
|
- offset: int # offset in datamodel storage
|
|
|
- type: type
|
|
|
|
|
|
- def lhs(self, events, datamodel) -> Variable:
|
|
|
- return datamodel.storage[self.offset]
|
|
|
+ variable: Optional[Variable] = None
|
|
|
+
|
|
|
+ def init_rvalue(self, scope) -> type:
|
|
|
+ assert self.variable is None
|
|
|
+ self.variable = scope.get_rvalue(self.name)
|
|
|
+ # print("init rvalue", self.name, "as", self.variable)
|
|
|
+ return self.variable.type
|
|
|
+
|
|
|
+ def init_lvalue(self, scope, expected_type):
|
|
|
+ assert self.variable is None
|
|
|
+ self.variable = scope.put_lvalue(self.name, expected_type)
|
|
|
+ # print("init lvalue", self.name, "as", self.variable)
|
|
|
+
|
|
|
+ def eval_lvalue(self, current_state, events, memory) -> Variable:
|
|
|
+ return self.variable
|
|
|
|
|
|
def render(self):
|
|
|
return self.name
|
|
|
|
|
|
- def get_static_type(self) -> type:
|
|
|
- return self.type
|
|
|
|
|
|
@dataclass
|
|
|
class FunctionCall(Expression):
|
|
|
function: Expression
|
|
|
parameters: List[Expression]
|
|
|
|
|
|
- def __post_init__(self):
|
|
|
- formal_types, return_type = typing_inspect.get_args(self.function.get_static_type())
|
|
|
+ type: Optional[type] = None
|
|
|
+
|
|
|
+ def init_rvalue(self, scope) -> type:
|
|
|
+ function_type = self.function.init_rvalue(scope)
|
|
|
+ if not isinstance(function_type, Callable):
|
|
|
+ raise Exception("Function call: Expression '%s' is not callable" % self.function.render())
|
|
|
+ formal_types, return_type = typing_inspect.get_args(function_type)
|
|
|
self.type = return_type
|
|
|
|
|
|
- actual_types = [p.get_static_type() for p in self.parameters]
|
|
|
+ actual_types = [p.init_rvalue(scope) for p in self.parameters]
|
|
|
for formal, actual in zip(formal_types, actual_types):
|
|
|
if formal != actual:
|
|
|
raise Exception("Function call: Actual types '%s' differ from formal types '%s'" % (actual_types, formal_types))
|
|
|
+ return self.type
|
|
|
|
|
|
- def eval(self, events, datamodel):
|
|
|
+ def eval(self, current_state, events, memory):
|
|
|
# print(self.function)
|
|
|
- f = self.function.eval(events, datamodel)
|
|
|
- p = [p.eval(events, datamodel) for p in self.parameters]
|
|
|
- return f(*p)
|
|
|
+ f = self.function.eval(current_state, events, memory)
|
|
|
+ p = [p.eval(current_state, events, memory) for p in self.parameters]
|
|
|
+ return f(current_state, events, memory, *p)
|
|
|
|
|
|
def render(self):
|
|
|
return self.function.render()+'('+','.join([p.render() for p in self.parameters])+')'
|
|
|
|
|
|
- def get_static_type(self) -> type:
|
|
|
- return self.type
|
|
|
|
|
|
@dataclass
|
|
|
class StringLiteral(Expression):
|
|
|
string: str
|
|
|
|
|
|
- def eval(self, events, datamodel):
|
|
|
+ def init_rvalue(self, scope) -> type:
|
|
|
+ return str
|
|
|
+
|
|
|
+ def eval(self, current_state, events, memory):
|
|
|
return self.string
|
|
|
|
|
|
def render(self):
|
|
|
return '"'+self.string+'"'
|
|
|
|
|
|
- def get_static_type(self) -> type:
|
|
|
- return str
|
|
|
|
|
|
@dataclass
|
|
|
class IntLiteral(Expression):
|
|
|
i: int
|
|
|
|
|
|
- def eval(self, events, datamodel):
|
|
|
+ def init_rvalue(self, scope) -> type:
|
|
|
+ return int
|
|
|
+
|
|
|
+ def eval(self, current_state, events, memory):
|
|
|
return self.i
|
|
|
|
|
|
def render(self):
|
|
|
return str(self.i)
|
|
|
|
|
|
- def get_static_type(self) -> type:
|
|
|
- return int
|
|
|
-
|
|
|
@dataclass
|
|
|
class BoolLiteral(Expression):
|
|
|
b: bool
|
|
|
|
|
|
- def eval(self, events, datamodel):
|
|
|
+ def init_rvalue(self, scope) -> type:
|
|
|
+ return bool
|
|
|
+
|
|
|
+ def eval(self, current_state, events, memory):
|
|
|
return self.b
|
|
|
|
|
|
def render(self):
|
|
|
return "true" if self.b else "false"
|
|
|
|
|
|
- def get_static_type(self) -> type:
|
|
|
- return bool
|
|
|
-
|
|
|
@dataclass
|
|
|
class DurationLiteral(Expression):
|
|
|
d: Duration
|
|
|
|
|
|
- def eval(self, events, datamodel):
|
|
|
+ def init_rvalue(self, scope) -> type:
|
|
|
+ return Duration
|
|
|
+
|
|
|
+ def eval(self, current_state, events, memory):
|
|
|
return self.d
|
|
|
|
|
|
def render(self):
|
|
|
return str(self.d)
|
|
|
|
|
|
- def get_static_type(self) -> type:
|
|
|
- return int
|
|
|
-
|
|
|
@dataclass
|
|
|
class Array(Expression):
|
|
|
elements: List[Any]
|
|
|
- t: type = None
|
|
|
|
|
|
- def __post_init__(self):
|
|
|
+ type: Optional[type] = None
|
|
|
+
|
|
|
+ def init_rvalue(self, scope) -> type:
|
|
|
for e in self.elements:
|
|
|
- t = e.get_static_type()
|
|
|
- if self.t and self.t != t:
|
|
|
- raise Exception("Mixed element types in Array expression: %s and %s" % (str(self.t), str(t)))
|
|
|
- self.t = t
|
|
|
+ t = e.init_rvalue(scope)
|
|
|
+ if self.type and self.type != t:
|
|
|
+ raise Exception("Mixed element types in Array expression: %s and %s" % (str(self.type), str(t)))
|
|
|
+ self.type = t
|
|
|
+
|
|
|
+ return List[self.type]
|
|
|
|
|
|
- def eval(self, events, datamodel):
|
|
|
- return [e.eval(events, datamodel) for e in self.elements]
|
|
|
+ def eval(self, current_state, events, memory):
|
|
|
+ return [e.eval(current_state, events, memory) for e in self.elements]
|
|
|
|
|
|
def render(self):
|
|
|
return '['+','.join([e.render() for e in self.elements])+']'
|
|
|
|
|
|
- def get_static_type(self) -> type:
|
|
|
- return List[self.t]
|
|
|
-
|
|
|
# Does not add anything semantically, but ensures that when rendering an expression,
|
|
|
# the parenthesis are not lost
|
|
|
@dataclass
|
|
|
class Group(Expression):
|
|
|
subexpr: Expression
|
|
|
|
|
|
- def eval(self, events, datamodel):
|
|
|
- return self.subexpr.eval(events, datamodel)
|
|
|
+ def init_rvalue(self, scope) -> type:
|
|
|
+ return self.subexpr.init_rvalue(scope)
|
|
|
+
|
|
|
+ def eval(self, current_state, events, memory):
|
|
|
+ return self.subexpr.eval(current_state, events, memory)
|
|
|
|
|
|
def render(self):
|
|
|
return '('+self.subexpr.render()+')'
|
|
|
|
|
|
- def get_static_type(self) -> type:
|
|
|
- return subexpr.get_static_type()
|
|
|
-
|
|
|
@dataclass
|
|
|
class BinaryExpression(Expression):
|
|
|
lhs: Expression
|
|
|
operator: str # token name from the grammar.
|
|
|
rhs: Expression
|
|
|
|
|
|
- def __post_init__(self):
|
|
|
- lhs_t = self.lhs.get_static_type()
|
|
|
- rhs_t = self.rhs.get_static_type()
|
|
|
+ def init_rvalue(self, scope) -> type:
|
|
|
+ lhs_t = self.lhs.init_rvalue(scope)
|
|
|
+ rhs_t = self.rhs.init_rvalue(scope)
|
|
|
if lhs_t != rhs_t:
|
|
|
raise Exception("Mixed LHS and RHS types in '%s' expression: %s and %s" % (self.operator, str(lhs_t), str(rhs_t)))
|
|
|
+ return lhs_t
|
|
|
|
|
|
- def eval(self, events, datamodel):
|
|
|
+ def eval(self, current_state, events, memory):
|
|
|
|
|
|
return {
|
|
|
# "AND": lambda x,y: x and y,
|
|
|
@@ -190,42 +211,39 @@ class BinaryExpression(Expression):
|
|
|
# "MOD": lambda x,y: x % y,
|
|
|
# "EXP": lambda x,y: x ** y,
|
|
|
|
|
|
- "and": lambda x,y: x.eval(events, datamodel) and y.eval(events, datamodel),
|
|
|
- "or": lambda x,y: x.eval(events, datamodel) or y.eval(events, datamodel),
|
|
|
- "==": lambda x,y: x.eval(events, datamodel) == y.eval(events, datamodel),
|
|
|
- "!=": lambda x,y: x.eval(events, datamodel) != y.eval(events, datamodel),
|
|
|
- ">": lambda x,y: x.eval(events, datamodel) > y.eval(events, datamodel),
|
|
|
- ">=": lambda x,y: x.eval(events, datamodel) >= y.eval(events, datamodel),
|
|
|
- "<": lambda x,y: x.eval(events, datamodel) < y.eval(events, datamodel),
|
|
|
- "<=": lambda x,y: x.eval(events, datamodel) <= y.eval(events, datamodel),
|
|
|
- "+": lambda x,y: x.eval(events, datamodel) + y.eval(events, datamodel),
|
|
|
- "-": lambda x,y: x.eval(events, datamodel) - y.eval(events, datamodel),
|
|
|
- "*": lambda x,y: x.eval(events, datamodel) * y.eval(events, datamodel),
|
|
|
- "/": lambda x,y: x.eval(events, datamodel) / y.eval(events, datamodel),
|
|
|
- "//": lambda x,y: x.eval(events, datamodel) // y.eval(events, datamodel),
|
|
|
- "%": lambda x,y: x.eval(events, datamodel) % y.eval(events, datamodel),
|
|
|
- "**": lambda x,y: x.eval(events, datamodel) ** y.eval(events, datamodel),
|
|
|
+ "and": lambda x,y: x.eval(current_state, events, memory) and y.eval(current_state, events, memory),
|
|
|
+ "or": lambda x,y: x.eval(current_state, events, memory) or y.eval(current_state, events, memory),
|
|
|
+ "==": lambda x,y: x.eval(current_state, events, memory) == y.eval(current_state, events, memory),
|
|
|
+ "!=": lambda x,y: x.eval(current_state, events, memory) != y.eval(current_state, events, memory),
|
|
|
+ ">": lambda x,y: x.eval(current_state, events, memory) > y.eval(current_state, events, memory),
|
|
|
+ ">=": lambda x,y: x.eval(current_state, events, memory) >= y.eval(current_state, events, memory),
|
|
|
+ "<": lambda x,y: x.eval(current_state, events, memory) < y.eval(current_state, events, memory),
|
|
|
+ "<=": lambda x,y: x.eval(current_state, events, memory) <= y.eval(current_state, events, memory),
|
|
|
+ "+": lambda x,y: x.eval(current_state, events, memory) + y.eval(current_state, events, memory),
|
|
|
+ "-": lambda x,y: x.eval(current_state, events, memory) - y.eval(current_state, events, memory),
|
|
|
+ "*": lambda x,y: x.eval(current_state, events, memory) * y.eval(current_state, events, memory),
|
|
|
+ "/": lambda x,y: x.eval(current_state, events, memory) / y.eval(current_state, events, memory),
|
|
|
+ "//": lambda x,y: x.eval(current_state, events, memory) // y.eval(current_state, events, memory),
|
|
|
+ "%": lambda x,y: x.eval(current_state, events, memory) % y.eval(current_state, events, memory),
|
|
|
+ "**": lambda x,y: x.eval(current_state, events, memory) ** y.eval(current_state, events, memory),
|
|
|
}[self.operator](self.lhs, self.rhs) # Borrow Python's lazy evaluation
|
|
|
|
|
|
def render(self):
|
|
|
return self.lhs.render() + ' ' + self.operator + ' ' + self.rhs.render()
|
|
|
|
|
|
- def get_static_type(self) -> type:
|
|
|
- return self.lhs.get_static_type()
|
|
|
-
|
|
|
@dataclass
|
|
|
class UnaryExpression(Expression):
|
|
|
operator: str # token value from the grammar.
|
|
|
expr: Expression
|
|
|
|
|
|
- def eval(self, events, datamodel):
|
|
|
+ def init_rvalue(self, scope) -> type:
|
|
|
+ return self.expr.init_rvalue(scope)
|
|
|
+
|
|
|
+ def eval(self, current_state, events, memory):
|
|
|
return {
|
|
|
- "not": lambda x: not x.eval(events, datamodel),
|
|
|
- "-": lambda x: - x.eval(events, datamodel),
|
|
|
+ "not": lambda x: not x.eval(current_state, events, memory),
|
|
|
+ "-": lambda x: - x.eval(current_state, events, memory),
|
|
|
}[self.operator](self.expr)
|
|
|
|
|
|
def render(self):
|
|
|
return self.operator + ' ' + self.expr.render()
|
|
|
-
|
|
|
- def get_static_type(self) -> type:
|
|
|
- return self.expr.get_static_type()
|