Browse Source

Action language components almost completely independent of statechart components. Reorganize project directory structure.

Joeri Exelmans 5 years ago
parent
commit
5c90ba0c93
39 changed files with 381 additions and 374 deletions
  1. 0 0
      src/sccd/action_lang/__init__.py
  2. 0 0
      src/sccd/action_lang/dynamic/__init__.py
  3. 2 36
      src/sccd/execution/memory.py
  4. 4 1
      src/sccd/parser/grammar/action_language.g
  5. 6 6
      src/sccd/parser/expression_parser.py
  6. 0 0
      src/sccd/action_lang/static/__init__.py
  7. 3 0
      src/sccd/action_lang/static/exceptions.py
  8. 101 72
      src/sccd/syntax/expression.py
  9. 17 10
      src/sccd/syntax/scope.py
  10. 20 20
      src/sccd/syntax/statement.py
  11. 2 2
      src/sccd/syntax/types.py
  12. 1 1
      src/sccd/controller/controller.py
  13. 1 1
      src/sccd/controller/object_manager.py
  14. 0 41
      src/sccd/execution/builtin_scope.py
  15. 1 1
      src/sccd/execution/instance.py
  16. 1 1
      src/sccd/legacy/xml_loader.py
  17. 1 1
      src/sccd/model/globals.py
  18. 1 1
      src/sccd/model/model.py
  19. 0 0
      src/sccd/statechart/__init__.py
  20. 0 0
      src/sccd/statechart/dynamic/__init__.py
  21. 38 0
      src/sccd/statechart/dynamic/builtin_scope.py
  22. 0 0
      src/sccd/statechart/dynamic/event.py
  23. 2 2
      src/sccd/execution/round.py
  24. 24 44
      src/sccd/execution/statechart_state.py
  25. 19 15
      src/sccd/execution/statechart_instance.py
  26. 58 50
      src/sccd/parser/statechart_parser.py
  27. 0 0
      src/sccd/statechart/static/__init__.py
  28. 15 8
      src/sccd/syntax/action.py
  29. 2 2
      src/sccd/syntax/statechart.py
  30. 28 4
      src/sccd/syntax/tree.py
  31. 0 36
      src/sccd/syntax/test_scope.py
  32. 10 2
      src/sccd/parser/xml_parser.py
  33. 1 1
      test/legacy_render.py
  34. 8 5
      test/lib/test_parser.py
  35. 5 1
      test/render.py
  36. 4 4
      test/test_files/day_atlee/statechart_fig7_dialer.svg
  37. 4 4
      test/test_files/features/datamodel/test_cond.svg
  38. 0 2
      test/test_files/features/functions/test_closure.xml
  39. 2 0
      test/test_files/features/functions/test_functions.xml

src/sccd/syntax/__init__.py → src/sccd/action_lang/__init__.py


+ 0 - 0
src/sccd/action_lang/dynamic/__init__.py


+ 2 - 36
src/sccd/execution/memory.py

@@ -2,8 +2,9 @@ from typing import *
 from dataclasses import *
 from dataclasses import *
 from sccd.util.bitmap import *
 from sccd.util.bitmap import *
 from sccd.util.debug import *
 from sccd.util.debug import *
-from sccd.syntax.scope import *
+from sccd.action_lang.static.scope import *
 from sccd.execution.exceptions import *
 from sccd.execution.exceptions import *
+from sccd.action_lang.static.expression import *
 
 
 @dataclass(frozen=True)
 @dataclass(frozen=True)
 class StackFrame:
 class StackFrame:
@@ -29,41 +30,6 @@ class StackFrame:
 
 
     return "StackFrame(%s, len=%d, parent=%s, context=%s)" % (self.scope.name, len(self.storage), short_descr(self.parent), "parent" if self.context is self.parent else short_descr(self.context))
     return "StackFrame(%s, len=%d, parent=%s, context=%s)" % (self.scope.name, len(self.storage), short_descr(self.parent), "parent" if self.context is self.parent else short_descr(self.context))
 
 
-
-class MemoryInterface(ABC):
-
-  @abstractmethod
-  def current_frame(self) -> StackFrame:
-    pass
-
-  @abstractmethod
-  def push_frame(self, scope: Scope):
-    pass
-
-  @abstractmethod
-  def push_frame_w_context(self, scope: Scope, context: StackFrame):
-    pass
-
-  @abstractmethod
-  def pop_frame(self):
-    pass
-
-  @abstractmethod
-  def load(self, offset: int) -> Any:
-    pass
-
-  @abstractmethod
-  def store(self, offset: int, value: Any):
-    pass
-
-  @abstractmethod
-  def flush_transition(self, read_only: bool = False):
-    pass
-
-  @abstractmethod
-  def flush_round(self):
-    pass
-
 class Memory(MemoryInterface):
 class Memory(MemoryInterface):
 
 
   def __init__(self):
   def __init__(self):

+ 4 - 1
src/sccd/parser/grammar/action_language.g

@@ -11,7 +11,7 @@ IDENTIFIER: /[A-Za-z_][A-Za-z_0-9]*/
 
 
 _PATH_SEP: "/" 
 _PATH_SEP: "/" 
 PARENT_NODE: ".." 
 PARENT_NODE: ".." 
-CURRENT_NODE: "." 
+CURRENT_NODE: "."
 
 
 // target of a transition
 // target of a transition
 state_ref: path | "(" path ("," path)+ ")" 
 state_ref: path | "(" path ("," path)+ ")" 
@@ -156,6 +156,9 @@ MULTIPLY: "*="
 DIVIDE: "/="
 DIVIDE: "/="
 FLOORDIVIDE: "//="
 FLOORDIVIDE: "//="
 
 
+COMMENT: "#" /(.)*/ "\n"
+%ignore COMMENT
+
 
 
 // Semantic option parsing
 // Semantic option parsing
 
 

+ 6 - 6
src/sccd/parser/expression_parser.py

@@ -1,18 +1,18 @@
 import os
 import os
 from lark import Lark, Transformer
 from lark import Lark, Transformer
-from sccd.syntax.statement import *
+from sccd.action_lang.static.statement import *
 from sccd.model.globals import *
 from sccd.model.globals import *
-from sccd.syntax.scope import *
-from sccd.syntax.tree import *
+from sccd.action_lang.static.scope import *
+from sccd.statechart.static.tree import *
 
 
-_grammar_dir = os.path.join(os.path.dirname(__file__), "grammar")
+_grammar_dir = os.path.dirname(__file__)
 
 
 with open(os.path.join(_grammar_dir,"action_language.g")) as file:
 with open(os.path.join(_grammar_dir,"action_language.g")) as file:
   _action_lang_grammar = file.read()
   _action_lang_grammar = file.read()
 
 
 
 
 # Lark transformer for parsetree-less parsing of expressions
 # Lark transformer for parsetree-less parsing of expressions
-class _ExpressionTransformer(Transformer):
+class ExpressionTransformer(Transformer):
   def __init__(self):
   def __init__(self):
     super().__init__()
     super().__init__()
     self.globals: Globals = None
     self.globals: Globals = None
@@ -131,7 +131,7 @@ class _ExpressionTransformer(Transformer):
 
 
 # Global variables so we don't have to rebuild our parser every time
 # Global variables so we don't have to rebuild our parser every time
 # Obviously not thread-safe
 # Obviously not thread-safe
-_transformer = _ExpressionTransformer()
+_transformer = ExpressionTransformer()
 _parser = Lark(_action_lang_grammar, parser="lalr", start=["expr", "block", "duration", "event_decl_list", "func_decl", "state_ref", "semantic_choice"], transformer=_transformer)
 _parser = Lark(_action_lang_grammar, parser="lalr", start=["expr", "block", "duration", "event_decl_list", "func_decl", "state_ref", "semantic_choice"], transformer=_transformer)
 
 
 # Exported functions:
 # Exported functions:

+ 0 - 0
src/sccd/action_lang/static/__init__.py


+ 3 - 0
src/sccd/action_lang/static/exceptions.py

@@ -0,0 +1,3 @@
+# Superclass for all "user errors", errors in the model being loaded.
+class ModelError(Exception):
+  pass

+ 101 - 72
src/sccd/syntax/expression.py

@@ -2,13 +2,42 @@ from abc import *
 from typing import *
 from typing import *
 from dataclasses import *
 from dataclasses import *
 from sccd.util.duration import *
 from sccd.util.duration import *
-from sccd.syntax.scope import *
+from sccd.action_lang.static.scope import *
+
+class MemoryInterface(ABC):
+
+  @abstractmethod
+  def current_frame(self) -> 'StackFrame':
+    pass
+
+  @abstractmethod
+  def push_frame(self, scope: Scope):
+    pass
+
+  @abstractmethod
+  def push_frame_w_context(self, scope: Scope, context: 'StackFrame'):
+    pass
+
+  @abstractmethod
+  def pop_frame(self):
+    pass
+
+  @abstractmethod
+  def load(self, offset: int) -> Any:
+    pass
+
+  @abstractmethod
+  def store(self, offset: int, value: Any):
+    pass
+
+  @abstractmethod
+  def flush_transition(self, read_only: bool = False):
+    pass
+
+  @abstractmethod
+  def flush_round(self):
+    pass
 
 
-@dataclass
-class EvalContext:
-    current_state: 'StatechartState'
-    events: List['Event']
-    memory: 'MemoryInterface'
 
 
 # Thrown if the type checker encountered something illegal.
 # Thrown if the type checker encountered something illegal.
 # Not to be confused with Python's TypeError exception.
 # Not to be confused with Python's TypeError exception.
@@ -20,17 +49,17 @@ class Expression(ABC):
     # Determines the static type of the expression. May throw if there is a type error.
     # Determines the static type of the expression. May throw if there is a type error.
     # Returns static type of expression.
     # Returns static type of expression.
     @abstractmethod
     @abstractmethod
-    def init_rvalue(self, scope: Scope) -> SCCDType:
+    def init_expr(self, scope: Scope) -> SCCDType:
         pass
         pass
 
 
     # Evaluation should NOT have side effects.
     # Evaluation should NOT have side effects.
     # Motivation is that the evaluation of a guard condition cannot have side effects.
     # Motivation is that the evaluation of a guard condition cannot have side effects.
     @abstractmethod
     @abstractmethod
-    def eval(self, ctx: EvalContext):
+    def eval(self, memory: MemoryInterface):
         pass
         pass
 
 
 # The LValue type is any type that can serve as an expression OR an LValue (left hand of assignment)
 # The LValue type is any type that can serve as an expression OR an LValue (left hand of assignment)
-# Either 'init_rvalue' or 'init_lvalue' is called to initialize the LValue.
+# Either 'init_expr' or 'init_lvalue' is called to initialize the LValue.
 # Then either 'eval' or 'eval_lvalue' can be called any number of times.
 # Then either 'eval' or 'eval_lvalue' can be called any number of times.
 class LValue(Expression):
 class LValue(Expression):
     # Initialize the LValue as an LValue. 
     # Initialize the LValue as an LValue. 
@@ -42,27 +71,27 @@ class LValue(Expression):
     #   offset ∈ [0, +∞[ : variable's memory address is within current scope
     #   offset ∈ [0, +∞[ : variable's memory address is within current scope
     #   offset ∈ ]-∞, 0[ : variable's memory address is in a parent scope (or better: 'context scope')
     #   offset ∈ ]-∞, 0[ : variable's memory address is in a parent scope (or better: 'context scope')
     @abstractmethod
     @abstractmethod
-    def eval_lvalue(self, ctx: EvalContext) -> int:
+    def eval_lvalue(self) -> int:
         pass
         pass
 
 
-    # LValues can also serve as expressions!
-    def eval(self, ctx: EvalContext):
-        offset = self.eval_lvalue(ctx)
-        return ctx.memory.load(offset)
+    # Any type that is an LValue can also serve as an expression!
+    def eval(self, memory: MemoryInterface):
+        offset = self.eval_lvalue()
+        return memory.load(offset)
 
 
 @dataclass
 @dataclass
 class Identifier(LValue):
 class Identifier(LValue):
     name: str
     name: str
     offset: Optional[int] = None
     offset: Optional[int] = None
 
 
-    def init_rvalue(self, scope: Scope) -> SCCDType:
+    def init_expr(self, scope: Scope) -> SCCDType:
         self.offset, type = scope.get_rvalue(self.name)
         self.offset, type = scope.get_rvalue(self.name)
         return type
         return type
 
 
     def init_lvalue(self, scope: Scope, type):
     def init_lvalue(self, scope: Scope, type):
         self.offset = scope.put_lvalue(self.name, type)
         self.offset = scope.put_lvalue(self.name, type)
 
 
-    def eval_lvalue(self, ctx: EvalContext) -> int:
+    def eval_lvalue(self) -> int:
         return self.offset
         return self.offset
 
 
     def render(self):
     def render(self):
@@ -75,24 +104,24 @@ class FunctionCall(Expression):
 
 
     type: Optional[type] = None
     type: Optional[type] = None
 
 
-    def init_rvalue(self, scope: Scope) -> SCCDType:
-        function_type = self.function.init_rvalue(scope)
+    def init_expr(self, scope: Scope) -> SCCDType:
+        function_type = self.function.init_expr(scope)
         if not isinstance(function_type, SCCDFunction):
         if not isinstance(function_type, SCCDFunction):
             raise StaticTypeError("Function call: Expression '%s' is not a function" % self.function.render())
             raise StaticTypeError("Function call: Expression '%s' is not a function" % self.function.render())
 
 
         formal_types = function_type.param_types
         formal_types = function_type.param_types
         return_type = function_type.return_type
         return_type = function_type.return_type
 
 
-        actual_types = [p.init_rvalue(scope) for p in self.params]
+        actual_types = [p.init_expr(scope) for p in self.params]
         for i, (formal, actual) in enumerate(zip(formal_types, actual_types)):
         for i, (formal, actual) in enumerate(zip(formal_types, actual_types)):
             if formal != actual:
             if formal != actual:
                 raise StaticTypeError("Function call, argument %d: %s is not expected type %s, instead is %s" % (i, self.params[i].render(), str(formal), str(actual)))
                 raise StaticTypeError("Function call, argument %d: %s is not expected type %s, instead is %s" % (i, self.params[i].render(), str(formal), str(actual)))
         return return_type
         return return_type
 
 
-    def eval(self, ctx: EvalContext):
-        f = self.function.eval(ctx)
-        p = [p.eval(ctx) for p in self.params]
-        return f(ctx, *p)
+    def eval(self, memory: MemoryInterface):
+        f = self.function.eval(memory)
+        p = [p.eval(memory) for p in self.params]
+        return f(memory, *p)
 
 
     def render(self):
     def render(self):
         return self.function.render()+'('+','.join([p.render() for p in self.params])+')'
         return self.function.render()+'('+','.join([p.render() for p in self.params])+')'
@@ -116,7 +145,7 @@ class FunctionDeclaration(Expression):
     body: 'Statement'
     body: 'Statement'
     scope: Optional[Scope] = None
     scope: Optional[Scope] = None
 
 
-    def init_rvalue(self, scope: Scope) -> SCCDType:
+    def init_expr(self, scope: Scope) -> SCCDType:
         self.scope = Scope("function", scope)
         self.scope = Scope("function", scope)
         # Reserve space for arguments on stack
         # Reserve space for arguments on stack
         for p in self.params_decl:
         for p in self.params_decl:
@@ -125,30 +154,30 @@ class FunctionDeclaration(Expression):
         return_type = ret.get_return_type()
         return_type = ret.get_return_type()
         return SCCDFunction([p.formal_type for p in self.params_decl], return_type)
         return SCCDFunction([p.formal_type for p in self.params_decl], return_type)
 
 
-    def eval(self, ctx: EvalContext):
-        context: StackFrame = ctx.memory.current_frame()
-        def FUNCTION(ctx: EvalContext, *params):
-            ctx.memory.push_frame_w_context(self.scope, context)
+    def eval(self, memory: MemoryInterface):
+        context: 'StackFrame' = memory.current_frame()
+        def FUNCTION(memory: MemoryInterface, *params):
+            memory.push_frame_w_context(self.scope, context)
             # Copy arguments to stack
             # Copy arguments to stack
             for val, p in zip(params, self.params_decl):
             for val, p in zip(params, self.params_decl):
-                ctx.memory.store(p.offset, val)
-            ret = self.body.exec(ctx)
-            ctx.memory.pop_frame()
+                memory.store(p.offset, val)
+            ret = self.body.exec(memory)
+            memory.pop_frame()
             return ret.val
             return ret.val
         return FUNCTION
         return FUNCTION
 
 
     def render(self) -> str:
     def render(self) -> str:
-        return "<func_decl>" # todo
+        return "func(%s) [...]" % ", ".join(p.render() for p in self.params_decl) # todo
         
         
 
 
 @dataclass
 @dataclass
 class StringLiteral(Expression):
 class StringLiteral(Expression):
     string: str
     string: str
 
 
-    def init_rvalue(self, scope: Scope) -> SCCDType:
+    def init_expr(self, scope: Scope) -> SCCDType:
         return SCCDString
         return SCCDString
 
 
-    def eval(self, ctx: EvalContext):
+    def eval(self, memory: MemoryInterface):
         return self.string
         return self.string
 
 
     def render(self):
     def render(self):
@@ -159,10 +188,10 @@ class StringLiteral(Expression):
 class IntLiteral(Expression):
 class IntLiteral(Expression):
     i: int 
     i: int 
 
 
-    def init_rvalue(self, scope: Scope) -> SCCDType:
+    def init_expr(self, scope: Scope) -> SCCDType:
         return SCCDInt
         return SCCDInt
 
 
-    def eval(self, ctx: EvalContext):
+    def eval(self, memory: MemoryInterface):
         return self.i
         return self.i
 
 
     def render(self):
     def render(self):
@@ -172,10 +201,10 @@ class IntLiteral(Expression):
 class BoolLiteral(Expression):
 class BoolLiteral(Expression):
     b: bool 
     b: bool 
 
 
-    def init_rvalue(self, scope: Scope) -> SCCDType:
+    def init_expr(self, scope: Scope) -> SCCDType:
         return SCCDBool
         return SCCDBool
 
 
-    def eval(self, ctx: EvalContext):
+    def eval(self, memory: MemoryInterface):
         return self.b
         return self.b
 
 
     def render(self):
     def render(self):
@@ -185,10 +214,10 @@ class BoolLiteral(Expression):
 class DurationLiteral(Expression):
 class DurationLiteral(Expression):
     d: Duration
     d: Duration
 
 
-    def init_rvalue(self, scope: Scope) -> SCCDType:
+    def init_expr(self, scope: Scope) -> SCCDType:
         return SCCDDuration
         return SCCDDuration
 
 
-    def eval(self, ctx: EvalContext):
+    def eval(self, memory: MemoryInterface):
         return self.d
         return self.d
 
 
     def render(self):
     def render(self):
@@ -200,17 +229,17 @@ class Array(Expression):
 
 
     element_type: Optional[SCCDType] = None
     element_type: Optional[SCCDType] = None
 
 
-    def init_rvalue(self, scope: Scope) -> SCCDType:
+    def init_expr(self, scope: Scope) -> SCCDType:
         for e in self.elements:
         for e in self.elements:
-            t = e.init_rvalue(scope)
+            t = e.init_expr(scope)
             if self.element_type and self.element_type != t:
             if self.element_type and self.element_type != t:
                 raise StaticTypeError("Mixed element types in Array expression: %s and %s" % (str(self.element_type), str(t)))
                 raise StaticTypeError("Mixed element types in Array expression: %s and %s" % (str(self.element_type), str(t)))
             self.element_type = t
             self.element_type = t
 
 
         return SCCDArray(self.element_type)
         return SCCDArray(self.element_type)
 
 
-    def eval(self, ctx: EvalContext):
-        return [e.eval(ctx) for e in self.elements]
+    def eval(self, memory: MemoryInterface):
+        return [e.eval(memory) for e in self.elements]
 
 
     def render(self):
     def render(self):
         return '['+','.join([e.render() for e in self.elements])+']'
         return '['+','.join([e.render() for e in self.elements])+']'
@@ -221,11 +250,11 @@ class Array(Expression):
 class Group(Expression):
 class Group(Expression):
     subexpr: Expression
     subexpr: Expression
 
 
-    def init_rvalue(self, scope: Scope) -> SCCDType:
-        return self.subexpr.init_rvalue(scope)
+    def init_expr(self, scope: Scope) -> SCCDType:
+        return self.subexpr.init_expr(scope)
 
 
-    def eval(self, ctx: EvalContext):
-        return self.subexpr.eval(ctx)
+    def eval(self, memory: MemoryInterface):
+        return self.subexpr.eval(memory)
 
 
     def render(self):
     def render(self):
         return '('+self.subexpr.render()+')'
         return '('+self.subexpr.render()+')'
@@ -236,9 +265,9 @@ class BinaryExpression(Expression):
     operator: str # token name from the grammar.
     operator: str # token name from the grammar.
     rhs: Expression
     rhs: Expression
 
 
-    def init_rvalue(self, scope: Scope) -> SCCDType:
-        lhs_t = self.lhs.init_rvalue(scope)
-        rhs_t = self.rhs.init_rvalue(scope)
+    def init_expr(self, scope: Scope) -> SCCDType:
+        lhs_t = self.lhs.init_expr(scope)
+        rhs_t = self.rhs.init_expr(scope)
 
 
         def comparison_type():
         def comparison_type():
             same_type()
             same_type()
@@ -278,24 +307,24 @@ class BinaryExpression(Expression):
             "**":  same_type(),
             "**":  same_type(),
         }[self.operator]
         }[self.operator]
 
 
-    def eval(self, ctx: EvalContext):
+    def eval(self, memory: MemoryInterface):
         
         
         return {
         return {
-            "and": lambda x,y: x.eval(ctx) and y.eval(ctx),
-            "or": lambda x,y: x.eval(ctx) or y.eval(ctx),
-            "==": lambda x,y: x.eval(ctx) == y.eval(ctx),
-            "!=": lambda x,y: x.eval(ctx) != y.eval(ctx),
-            ">": lambda x,y: x.eval(ctx) > y.eval(ctx),
-            ">=": lambda x,y: x.eval(ctx) >= y.eval(ctx),
-            "<": lambda x,y: x.eval(ctx) < y.eval(ctx),
-            "<=": lambda x,y: x.eval(ctx) <= y.eval(ctx),
-            "+": lambda x,y: x.eval(ctx) + y.eval(ctx),
-            "-": lambda x,y: x.eval(ctx) - y.eval(ctx),
-            "*": lambda x,y: x.eval(ctx) * y.eval(ctx),
-            "/": lambda x,y: x.eval(ctx) / y.eval(ctx),
-            "//": lambda x,y: x.eval(ctx) // y.eval(ctx),
-            "%": lambda x,y: x.eval(ctx) % y.eval(ctx),
-            "**": lambda x,y: x.eval(ctx) ** y.eval(ctx),
+            "and": lambda x,y: x.eval(memory) and y.eval(memory),
+            "or": lambda x,y: x.eval(memory) or y.eval(memory),
+            "==": lambda x,y: x.eval(memory) == y.eval(memory),
+            "!=": lambda x,y: x.eval(memory) != y.eval(memory),
+            ">": lambda x,y: x.eval(memory) > y.eval(memory),
+            ">=": lambda x,y: x.eval(memory) >= y.eval(memory),
+            "<": lambda x,y: x.eval(memory) < y.eval(memory),
+            "<=": lambda x,y: x.eval(memory) <= y.eval(memory),
+            "+": lambda x,y: x.eval(memory) + y.eval(memory),
+            "-": lambda x,y: x.eval(memory) - y.eval(memory),
+            "*": lambda x,y: x.eval(memory) * y.eval(memory),
+            "/": lambda x,y: x.eval(memory) / y.eval(memory),
+            "//": lambda x,y: x.eval(memory) // y.eval(memory),
+            "%": lambda x,y: x.eval(memory) % y.eval(memory),
+            "**": lambda x,y: x.eval(memory) ** y.eval(memory),
         }[self.operator](self.lhs, self.rhs) # Borrow Python's lazy evaluation
         }[self.operator](self.lhs, self.rhs) # Borrow Python's lazy evaluation
 
 
     def render(self):
     def render(self):
@@ -306,17 +335,17 @@ class UnaryExpression(Expression):
     operator: str # token value from the grammar.
     operator: str # token value from the grammar.
     expr: Expression
     expr: Expression
 
 
-    def init_rvalue(self, scope: Scope) -> SCCDType:
-        expr_type = self.expr.init_rvalue(scope)
+    def init_expr(self, scope: Scope) -> SCCDType:
+        expr_type = self.expr.init_expr(scope)
         return {
         return {
             "not": SCCDBool,
             "not": SCCDBool,
             "-":   expr_type,
             "-":   expr_type,
         }[self.operator]
         }[self.operator]
 
 
-    def eval(self, ctx: EvalContext):
+    def eval(self, memory: MemoryInterface):
         return {
         return {
-            "not": lambda x: not x.eval(ctx),
-            "-": lambda x: - x.eval(ctx),
+            "not": lambda x: not x.eval(memory),
+            "-": lambda x: - x.eval(memory),
         }[self.operator](self.expr)
         }[self.operator](self.expr)
 
 
     def render(self):
     def render(self):

+ 17 - 10
src/sccd/syntax/scope.py

@@ -2,12 +2,10 @@ from abc import *
 from typing import *
 from typing import *
 from dataclasses import *
 from dataclasses import *
 from inspect import signature
 from inspect import signature
-from sccd.syntax.types import *
+from sccd.action_lang.static.types import *
+from sccd.action_lang.static.exceptions import *
 import itertools
 import itertools
 
 
-# Superclass for all "user errors", errors in the model being loaded.
-class ModelError(Exception):
-  pass
 
 
 class ScopeError(ModelError):
 class ScopeError(ModelError):
   def __init__(self, scope, msg):
   def __init__(self, scope, msg):
@@ -17,11 +15,20 @@ class ScopeError(ModelError):
 # Stateless stuff we know about a variable existing within a scope.
 # Stateless stuff we know about a variable existing within a scope.
 @dataclass(frozen=True)
 @dataclass(frozen=True)
 class _Variable(ABC):
 class _Variable(ABC):
-  name: str # only used to print error messages
+  _name: str # only used to print error messages
   offset: int # Offset within variable's scope. Always >= 0.
   offset: int # Offset within variable's scope. Always >= 0.
   type: SCCDType
   type: SCCDType
   const: bool
   const: bool
 
 
+  @property
+  def name(self):
+    import termcolor
+    return termcolor.colored(self._name, 'yellow')
+
+  def __str__(self):
+    return "+%d: %s%s: %s" % (self.offset, "(const) "if self.const else "", self.name, str(self.type))
+
+
 # Stateless stuff we know about a scope (= set of named values)
 # Stateless stuff we know about a scope (= set of named values)
 class Scope:
 class Scope:
   def __init__(self, name: str, parent: 'Scope'):
   def __init__(self, name: str, parent: 'Scope'):
@@ -48,11 +55,11 @@ class Scope:
 
 
     is_empty = True
     is_empty = True
     for v in reversed(self.variables):
     for v in reversed(self.variables):
-      s += "    +%d: %s: %s\n" % (v.offset, v.name, str(v.type))
+      s += "    %s\n" % str(v)
       is_empty = False
       is_empty = False
 
 
     if is_empty:
     if is_empty:
-      s += "   ø\n"
+      s += "    ø\n"
 
 
     if self.parent:
     if self.parent:
       s += self.parent.__str__()
       s += self.parent.__str__()
@@ -86,10 +93,10 @@ class Scope:
       scope, scope_offset, var = found
       scope, scope_offset, var = found
       if var.type == type:
       if var.type == type:
         if var.const:
         if var.const:
-          raise ScopeError(self, "Cannot assign to %s: %s of scope '%s': Variable is constant." % (name, str(var.type), scope.name))
+          raise ScopeError(self, "Cannot assign to %s: %s of scope '%s': Variable is constant." % (var.name, str(var.type), scope.name))
         return scope_offset + var.offset
         return scope_offset + var.offset
       else:
       else:
-        raise ScopeError(self, "Cannot assign %s to %s: %s of scope '%s'" %(str(type), name, str(var.type), scope.name))
+        raise ScopeError(self, "Cannot assign %s to %s: %s of scope '%s'" %(str(type), var.name, str(var.type), scope.name))
 
 
     return self._internal_add(name, type, const=False)
     return self._internal_add(name, type, const=False)
 
 
@@ -109,6 +116,6 @@ class Scope:
     found = self._internal_lookup(name)
     found = self._internal_lookup(name)
     if found:
     if found:
       scope, scope_offset, var = found
       scope, scope_offset, var = found
-      raise ScopeError(self, "Cannot declare '%s' in scope '%s': Name already exists in scope '%s'" % (name, self.name, scope.name))
+      raise ScopeError(self, "Cannot declare '%s' in scope '%s': Name already exists in scope '%s'" % (var.name, self.name, scope.name))
 
 
     return self._internal_add(name, type, const)
     return self._internal_add(name, type, const)

+ 20 - 20
src/sccd/syntax/statement.py

@@ -1,5 +1,5 @@
 from typing import *
 from typing import *
-from sccd.syntax.expression import *
+from sccd.action_lang.static.expression import *
 
 
 @dataclass(frozen=True)
 @dataclass(frozen=True)
 class Return:
 class Return:
@@ -76,7 +76,7 @@ AlwaysReturns = lambda t: ReturnBehavior(ReturnBehavior.When.ALWAYS, t)
 class Statement(ABC):
 class Statement(ABC):
     # Execution typically has side effects.
     # Execution typically has side effects.
     @abstractmethod
     @abstractmethod
-    def exec(self, ctx: EvalContext) -> Return:
+    def exec(self, memory: MemoryInterface) -> Return:
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
@@ -93,15 +93,15 @@ class Assignment(Statement):
     rhs: Expression
     rhs: Expression
 
 
     def init_stmt(self, scope: Scope) -> ReturnBehavior:
     def init_stmt(self, scope: Scope) -> ReturnBehavior:
-        rhs_t = self.rhs.init_rvalue(scope)
+        rhs_t = self.rhs.init_expr(scope)
         self.lhs.init_lvalue(scope, rhs_t)
         self.lhs.init_lvalue(scope, rhs_t)
         return NeverReturns
         return NeverReturns
 
 
-    def exec(self, ctx: EvalContext) -> Return:
-        rhs_val = self.rhs.eval(ctx)
-        offset = self.lhs.eval_lvalue(ctx)
+    def exec(self, memory: MemoryInterface) -> Return:
+        rhs_val = self.rhs.eval(memory)
+        offset = self.lhs.eval_lvalue()
 
 
-        ctx.memory.store(offset, rhs_val)
+        memory.store(offset, rhs_val)
 
 
         return DontReturn
         return DontReturn
 
 
@@ -124,9 +124,9 @@ class Block(Statement):
             so_far = ReturnBehavior.sequence(so_far, now_what)            
             so_far = ReturnBehavior.sequence(so_far, now_what)            
         return so_far
         return so_far
 
 
-    def exec(self, ctx: EvalContext) -> Return:
+    def exec(self, memory: MemoryInterface) -> Return:
         for stmt in self.stmts:
         for stmt in self.stmts:
-            ret = stmt.exec(ctx)
+            ret = stmt.exec(memory)
             if ret.ret:
             if ret.ret:
                 break
                 break
         return ret
         return ret
@@ -143,11 +143,11 @@ class ExpressionStatement(Statement):
     expr: Expression
     expr: Expression
 
 
     def init_stmt(self, scope: Scope) -> ReturnBehavior:
     def init_stmt(self, scope: Scope) -> ReturnBehavior:
-        self.expr.init_rvalue(scope)
+        self.expr.init_expr(scope)
         return NeverReturns
         return NeverReturns
 
 
-    def exec(self, ctx: EvalContext) -> Return:
-        self.expr.eval(ctx)
+    def exec(self, memory: MemoryInterface) -> Return:
+        self.expr.eval(memory)
         return DontReturn
         return DontReturn
 
 
     def render(self) -> str:
     def render(self) -> str:
@@ -158,11 +158,11 @@ class ReturnStatement(Statement):
     expr: Expression
     expr: Expression
 
 
     def init_stmt(self, scope: Scope) -> ReturnBehavior:
     def init_stmt(self, scope: Scope) -> ReturnBehavior:
-        t = self.expr.init_rvalue(scope)
+        t = self.expr.init_expr(scope)
         return AlwaysReturns(t)
         return AlwaysReturns(t)
 
 
-    def exec(self, ctx: EvalContext) -> Return:
-        val = self.expr.eval(ctx)
+    def exec(self, memory: MemoryInterface) -> Return:
+        val = self.expr.eval(memory)
         return DoReturn(val)
         return DoReturn(val)
 
 
     def render(self) -> str:
     def render(self) -> str:
@@ -175,7 +175,7 @@ class IfStatement(Statement):
     else_body: Optional[Statement] = None
     else_body: Optional[Statement] = None
 
 
     def init_stmt(self, scope: Scope) -> ReturnBehavior:
     def init_stmt(self, scope: Scope) -> ReturnBehavior:
-        cond_t = self.cond.init_rvalue(scope)
+        cond_t = self.cond.init_expr(scope)
         if_ret = self.if_body.init_stmt(scope)
         if_ret = self.if_body.init_stmt(scope)
         if self.else_body is None:
         if self.else_body is None:
             else_ret = NeverReturns
             else_ret = NeverReturns
@@ -183,12 +183,12 @@ class IfStatement(Statement):
             else_ret = self.else_body.init_stmt(scope)
             else_ret = self.else_body.init_stmt(scope)
         return ReturnBehavior.combine_branches(if_ret, else_ret)
         return ReturnBehavior.combine_branches(if_ret, else_ret)
 
 
-    def exec(self, ctx: EvalContext) -> Return:
-        val = self.cond.eval(ctx)
+    def exec(self, memory: MemoryInterface) -> Return:
+        val = self.cond.eval(memory)
         if val:
         if val:
-            return self.if_body.exec(ctx)
+            return self.if_body.exec(memory)
         elif self.else_body is not None:
         elif self.else_body is not None:
-            return self.else_body.exec(ctx)
+            return self.else_body.exec(memory)
         return DontReturn
         return DontReturn
 
 
     def render(self) -> str:
     def render(self) -> str:

+ 2 - 2
src/sccd/syntax/types.py

@@ -9,8 +9,8 @@ class SCCDType(ABC):
         
         
     def __str__(self):
     def __str__(self):
         import termcolor
         import termcolor
-        # return termcolor.colored(self._str(), 'blue')
-        return self._str()
+        return termcolor.colored(self._str(), 'cyan')
+        # return self._str()
 
 
 @dataclass(frozen=True)
 @dataclass(frozen=True)
 class _SCCDSimpleType(SCCDType):
 class _SCCDSimpleType(SCCDType):

+ 1 - 1
src/sccd/controller/controller.py

@@ -2,7 +2,7 @@ import queue
 import dataclasses
 import dataclasses
 from typing import Dict, List, Optional
 from typing import Dict, List, Optional
 from sccd.controller.event_queue import *
 from sccd.controller.event_queue import *
-from sccd.execution.event import *
+from sccd.statechart.dynamic.event import *
 from sccd.controller.object_manager import *
 from sccd.controller.object_manager import *
 from sccd.util.debug import print_debug
 from sccd.util.debug import print_debug
 from sccd.model.model import *
 from sccd.model.model import *

+ 1 - 1
src/sccd/controller/object_manager.py

@@ -1,7 +1,7 @@
 import re
 import re
 import abc
 import abc
 from typing import List, Tuple
 from typing import List, Tuple
-from sccd.execution.statechart_instance import *
+from sccd.statechart.dynamic.statechart_instance import *
 
 
 # TODO: Clean this mess up. Look at all object management operations and see how they can be improved.
 # TODO: Clean this mess up. Look at all object management operations and see how they can be improved.
 class ObjectManager(Instance):
 class ObjectManager(Instance):

+ 0 - 41
src/sccd/execution/builtin_scope.py

@@ -1,41 +0,0 @@
-from sccd.syntax.expression import *
-from sccd.util.debug import *
-import termcolor
-
-def _in_state(ctx: EvalContext, state_list: List[str]) -> bool:
-  from sccd.execution.statechart_state import StatechartState
-  return StatechartState.in_state(ctx.current_state, state_list)
-
-
-def _log10(ctx: EvalContext, i: int) -> float:
-  import math
-  return math.log10(i)
-
-
-def _float_to_int(ctx: EvalContext, x: float) -> int:
-  return int(x)
-
-
-def _log(ctx: EvalContext, s: str) -> None:
-  print_debug(termcolor.colored("log: ",'blue')+s)
-
-
-def _int_to_str(ctx: EvalContext, i: int) -> str:
-  return str(i)
-
-BuiltIn = Scope("builtin", None)
-
-BuiltIn.declare("INSTATE", SCCDFunction([SCCDArray(SCCDString)], SCCDBool), const=True)
-BuiltIn.declare("log10", SCCDFunction([SCCDInt], SCCDFloat), const=True)
-BuiltIn.declare("float_to_int", SCCDFunction([SCCDFloat], SCCDInt), const=True)
-BuiltIn.declare("log", SCCDFunction([SCCDString]), const=True)
-BuiltIn.declare("int_to_str", SCCDFunction([SCCDInt], SCCDString), const=True)
-
-def load_builtins(memory):
-  memory.push_frame(BuiltIn)
-
-  memory.current_frame().storage[0] = _in_state
-  memory.current_frame().storage[1] = _log10
-  memory.current_frame().storage[2] = _float_to_int
-  memory.current_frame().storage[3] = _log
-  memory.current_frame().storage[4] = _int_to_str

+ 1 - 1
src/sccd/execution/instance.py

@@ -1,6 +1,6 @@
 from abc import *
 from abc import *
 from typing import *
 from typing import *
-from sccd.execution.event import *
+from sccd.statechart.dynamic.event import *
 from sccd.execution.timestamp import *
 from sccd.execution.timestamp import *
 
 
 # Interface for all instances and also the Object Manager
 # Interface for all instances and also the Object Manager

+ 1 - 1
src/sccd/legacy/xml_loader.py

@@ -6,7 +6,7 @@ from typing import List, Any, Optional
 import lxml.etree as ET
 import lxml.etree as ET
 from lark import Lark
 from lark import Lark
 
 
-from sccd.syntax.statechart import *
+from sccd.statechart.static.statechart import *
 from sccd.controller.controller import *
 from sccd.controller.controller import *
 import sccd.schema
 import sccd.schema
 
 

+ 1 - 1
src/sccd/model/globals.py

@@ -1,7 +1,7 @@
 # from dataclasses import *
 # from dataclasses import *
 import termcolor
 import termcolor
 from typing import *
 from typing import *
-from sccd.syntax.expression import *
+from sccd.action_lang.static.expression import *
 from sccd.util.namespace import *
 from sccd.util.namespace import *
 from sccd.util.duration import *
 from sccd.util.duration import *
 from sccd.util.debug import *
 from sccd.util.debug import *

+ 1 - 1
src/sccd/model/model.py

@@ -1,7 +1,7 @@
 from abc import *
 from abc import *
 from dataclasses import *
 from dataclasses import *
 from typing import *
 from typing import *
-from sccd.syntax.statechart import *
+from sccd.statechart.static.statechart import *
 from sccd.model.globals import *
 from sccd.model.globals import *
 
 
 @dataclass
 @dataclass

+ 0 - 0
src/sccd/statechart/__init__.py


+ 0 - 0
src/sccd/statechart/dynamic/__init__.py


+ 38 - 0
src/sccd/statechart/dynamic/builtin_scope.py

@@ -0,0 +1,38 @@
+from sccd.action_lang.static.expression import *
+from sccd.util.debug import *
+
+BuiltIn = Scope("builtin", None)
+
+BuiltIn.declare("INSTATE", SCCDFunction([SCCDArray(SCCDString)], SCCDBool), const=True)
+BuiltIn.declare("log10", SCCDFunction([SCCDInt], SCCDFloat), const=True)
+BuiltIn.declare("float_to_int", SCCDFunction([SCCDFloat], SCCDInt), const=True)
+BuiltIn.declare("log", SCCDFunction([SCCDString]), const=True)
+BuiltIn.declare("int_to_str", SCCDFunction([SCCDInt], SCCDString), const=True)
+
+def load_builtins(memory: MemoryInterface, state):
+  memory.push_frame(BuiltIn)
+
+  def in_state(memory: MemoryInterface, state_list: List[str]) -> bool:
+    return state.in_state(state_list)
+
+  def log10(memory: MemoryInterface, i: int) -> float:
+    import math
+    return math.log10(i)
+
+  def float_to_int(memory: MemoryInterface, x: float) -> int:
+    return int(x)
+
+  def log(memory: MemoryInterface, s: str) -> None:
+    import termcolor
+    print_debug(termcolor.colored("log: ",'blue')+s)
+
+  def int_to_str(memory: MemoryInterface, i: int) -> str:
+    return str(i)
+
+  frame = memory.current_frame()
+
+  frame.storage[0] = in_state
+  frame.storage[1] = log10
+  frame.storage[2] = float_to_int
+  frame.storage[3] = log
+  frame.storage[4] = int_to_str

src/sccd/execution/event.py → src/sccd/statechart/dynamic/event.py


+ 2 - 2
src/sccd/execution/round.py

@@ -1,7 +1,7 @@
 from typing import *
 from typing import *
-from sccd.execution.event import *
+from sccd.statechart.dynamic.event import *
 from sccd.util.bitmap import *
 from sccd.util.bitmap import *
-from sccd.syntax.tree import *
+from sccd.statechart.static.tree import *
 from sccd.util.debug import *
 from sccd.util.debug import *
 from sccd.execution.exceptions import *
 from sccd.execution.exceptions import *
 
 

+ 24 - 44
src/sccd/execution/statechart_state.py

@@ -1,21 +1,22 @@
 from typing import *
 from typing import *
-from sccd.syntax.statechart import *
-from sccd.execution.event import *
+from sccd.statechart.static.statechart import *
+from sccd.statechart.dynamic.event import *
 from sccd.util.debug import print_debug
 from sccd.util.debug import print_debug
 from sccd.util.bitmap import *
 from sccd.util.bitmap import *
-from sccd.syntax.scope import *
+from sccd.action_lang.static.scope import *
 from sccd.execution.exceptions import *
 from sccd.execution.exceptions import *
 
 
-
 # Set of current states etc.
 # Set of current states etc.
-class StatechartState:
+class StatechartExecution:
 
 
-    def __init__(self, statechart: Statechart, instance, gc_memory, rhs_memory, raise_internal):
-        self.model = statechart
+    def __init__(self, statechart: Statechart, instance):
+        self.statechart = statechart
         self.instance = instance
         self.instance = instance
-        self.gc_memory = gc_memory
-        self.rhs_memory = rhs_memory
-        self.raise_internal = raise_internal
+
+        self.gc_memory = None
+        self.rhs_memory = None
+        self.raise_internal = None
+        self.raise_next_bs = None
 
 
         # these 2 fields have the same information
         # these 2 fields have the same information
         self.configuration: List[State] = []
         self.configuration: List[State] = []
@@ -38,13 +39,13 @@ class StatechartState:
 
 
     # enter default states
     # enter default states
     def initialize(self):
     def initialize(self):
-        states = self.model.tree.root.getEffectiveTargetStates(self)
+        states = self.statechart.tree.root.getEffectiveTargetStates(self)
         self.configuration.extend(states)
         self.configuration.extend(states)
         self.configuration_bitmap = Bitmap.from_list(s.gen.state_id for s in states)
         self.configuration_bitmap = Bitmap.from_list(s.gen.state_id for s in states)
 
 
         ctx = EvalContext(current_state=self, events=[], memory=self.rhs_memory)
         ctx = EvalContext(current_state=self, events=[], memory=self.rhs_memory)
-        if self.model.datamodel is not None:
-            self.model.datamodel.exec(ctx)
+        if self.statechart.datamodel is not None:
+            self.statechart.datamodel.exec(self.rhs_memory)
 
 
         for state in states:
         for state in states:
             print_debug(termcolor.colored('  ENTER %s'%state.gen.full_name, 'green'))
             print_debug(termcolor.colored('  ENTER %s'%state.gen.full_name, 'green'))
@@ -103,7 +104,8 @@ class StatechartState:
                     
                     
             # execute transition action(s)
             # execute transition action(s)
             self.rhs_memory.push_frame(t.scope) # make room for event parameters on stack
             self.rhs_memory.push_frame(t.scope) # make room for event parameters on stack
-            self._copy_event_params_to_stack(self.rhs_memory, t, events)
+            if t.trigger:
+                t.trigger.copy_params_to_stack(ctx)
             self._perform_actions(ctx, t.actions)
             self._perform_actions(ctx, t.actions)
             self.rhs_memory.pop_frame()
             self.rhs_memory.pop_frame()
                 
                 
@@ -120,34 +122,13 @@ class StatechartState:
             try:
             try:
                 self.configuration = self.config_mem[self.configuration_bitmap]
                 self.configuration = self.config_mem[self.configuration_bitmap]
             except KeyError:
             except KeyError:
-                self.configuration = self.config_mem[self.configuration_bitmap] = [s for s in self.model.tree.state_list if self.configuration_bitmap & s.gen.state_id_bitmap]
+                self.configuration = self.config_mem[self.configuration_bitmap] = [s for s in self.statechart.tree.state_list if self.configuration_bitmap & s.gen.state_id_bitmap]
 
 
             self.rhs_memory.flush_transition()
             self.rhs_memory.flush_transition()
         except SCCDRuntimeException as e:
         except SCCDRuntimeException as e:
             e.args = ("During execution of transition %s:\n" % str(t) +str(e),)
             e.args = ("During execution of transition %s:\n" % str(t) +str(e),)
             raise
             raise
 
 
-    @staticmethod
-    def _copy_event_params_to_stack(memory, t, events):
-        # Both 'events' and 't.trigger.enabling' are sorted by event ID...
-        # This way we have to iterate over each of both lists at most once.
-        if t.trigger and not isinstance(t.trigger, AfterTrigger):
-            event_decls = iter(t.trigger.enabling)
-            try:
-                event_decl = next(event_decls)
-                offset = 0
-                for e in events:
-                    if e.id < event_decl.id:
-                        continue
-                    else:
-                        while e.id > event_decl.id:
-                            event_decl = next(event_decls)
-                        for p in e.params:
-                            memory.store(offset, p)
-                            offset += 1
-            except StopIteration:
-                pass
-
     def check_guard(self, t, events) -> bool:
     def check_guard(self, t, events) -> bool:
         try:
         try:
             # Special case: after trigger
             # Special case: after trigger
@@ -159,10 +140,12 @@ class StatechartState:
             if t.guard is None:
             if t.guard is None:
                 return True
                 return True
             else:
             else:
+                ctx = EvalContext(current_state=self, events=events, memory=self.gc_memory)
                 self.gc_memory.push_frame(t.scope)
                 self.gc_memory.push_frame(t.scope)
-                self._copy_event_params_to_stack(self.gc_memory, t, events)
-                result = t.guard.eval(
-                    EvalContext(current_state=self, events=events, memory=self.gc_memory))
+                # Guard conditions can also refer to event parameters
+                if t.trigger:
+                    t.trigger.copy_params_to_stack(ctx)
+                result = t.guard.eval(self.gc_memory)
                 self.gc_memory.pop_frame()
                 self.gc_memory.pop_frame()
                 return result
                 return result
         except SCCDRuntimeException as e:
         except SCCDRuntimeException as e:
@@ -182,10 +165,7 @@ class StatechartState:
             delay: Duration = after.delay.eval(
             delay: Duration = after.delay.eval(
                 EvalContext(current_state=self, events=[], memory=self.gc_memory))
                 EvalContext(current_state=self, events=[], memory=self.gc_memory))
             timer_id = self._next_timer_id(after)
             timer_id = self._next_timer_id(after)
-            self.output.append(OutputEvent(
-                Event(id=after.id, name=after.name, params=[timer_id]),
-                target=InstancesTarget([self.instance]),
-                time_offset=delay))
+            self.raise_next_bs(Event(id=after.id, name=after.name, params=[timer_id]), delay)
 
 
     def _next_timer_id(self, trigger: AfterTrigger):
     def _next_timer_id(self, trigger: AfterTrigger):
         self.timer_ids[trigger.after_id] += 1
         self.timer_ids[trigger.after_id] += 1
@@ -193,7 +173,7 @@ class StatechartState:
 
 
     # Return whether the current configuration includes ALL the states given.
     # Return whether the current configuration includes ALL the states given.
     def in_state(self, state_strings: List[str]) -> bool:
     def in_state(self, state_strings: List[str]) -> bool:
-        state_ids_bitmap = Bitmap.from_list((self.model.tree.state_dict[state_string].gen.state_id for state_string in state_strings))
+        state_ids_bitmap = Bitmap.from_list((self.statechart.tree.state_dict[state_string].gen.state_id for state_string in state_strings))
         in_state = self.configuration_bitmap.has_all(state_ids_bitmap)
         in_state = self.configuration_bitmap.has_all(state_ids_bitmap)
         # if in_state:
         # if in_state:
         #     print_debug("in state"+str(state_strings))
         #     print_debug("in state"+str(state_strings))

+ 19 - 15
src/sccd/execution/statechart_instance.py

@@ -2,13 +2,13 @@ import termcolor
 import functools
 import functools
 from typing import List, Tuple, Iterable
 from typing import List, Tuple, Iterable
 from sccd.execution.instance import *
 from sccd.execution.instance import *
-from sccd.execution.builtin_scope import *
-from sccd.syntax.statechart import *
+from sccd.statechart.dynamic.builtin_scope import *
+from sccd.statechart.static.statechart import *
 from sccd.util.debug import print_debug
 from sccd.util.debug import print_debug
 from sccd.util.bitmap import *
 from sccd.util.bitmap import *
-from sccd.execution.round import *
-from sccd.execution.statechart_state import *
-from sccd.execution.memory import *
+from sccd.statechart.dynamic.round import *
+from sccd.statechart.dynamic.statechart_execution import *
+from sccd.action_lang.dynamic.memory import *
 
 
 # Hardcoded limit on number of sub-rounds of combo and big step to detect never-ending superrounds.
 # Hardcoded limit on number of sub-rounds of combo and big step to detect never-ending superrounds.
 # TODO: make this configurable
 # TODO: make this configurable
@@ -18,6 +18,8 @@ class StatechartInstance(Instance):
     def __init__(self, statechart: Statechart, object_manager):
     def __init__(self, statechart: Statechart, object_manager):
         self.object_manager = object_manager
         self.object_manager = object_manager
 
 
+        self.execution = StatechartExecution(statechart, self)
+
         semantics = statechart.semantics
         semantics = statechart.semantics
 
 
         if semantics.has_multiple_variants():
         if semantics.has_multiple_variants():
@@ -31,7 +33,7 @@ class StatechartInstance(Instance):
 
 
         # Big step + combo step maximality semantics
         # Big step + combo step maximality semantics
 
 
-        small_step = SmallStep(termcolor.colored("small", 'blue'), None, generator,
+        small_step = SmallStep(termcolor.colored("small", 'blue'), self.execution, generator,
             concurrency=semantics.concurrency==Concurrency.MANY)
             concurrency=semantics.concurrency==Concurrency.MANY)
 
 
 
 
@@ -79,9 +81,11 @@ class StatechartInstance(Instance):
             InputEventLifeline.FIRST_SMALL_STEP: first_small
             InputEventLifeline.FIRST_SMALL_STEP: first_small
         }[semantics.input_event_lifeline]
         }[semantics.input_event_lifeline]
 
 
+        raise_nextbs = lambda e, time_offset: self.execution.output.append(OutputEvent(e, InstancesTarget([self]), time_offset))
+
         raise_internal = {
         raise_internal = {
             # InternalEventLifeline.QUEUE: self._big_step.add_next_event,
             # InternalEventLifeline.QUEUE: self._big_step.add_next_event,
-            InternalEventLifeline.QUEUE: lambda e: self.state.output.append(OutputEvent(e, InstancesTarget([self]))),
+            InternalEventLifeline.QUEUE: lambda e: raise_nextbs(e, 0),
             InternalEventLifeline.NEXT_COMBO_STEP: combo_step.add_next_event,
             InternalEventLifeline.NEXT_COMBO_STEP: combo_step.add_next_event,
             InternalEventLifeline.NEXT_SMALL_STEP: small_step.add_next_event,
             InternalEventLifeline.NEXT_SMALL_STEP: small_step.add_next_event,
 
 
@@ -92,7 +96,7 @@ class StatechartInstance(Instance):
         # Memory protocol semantics
         # Memory protocol semantics
 
 
         memory = Memory()
         memory = Memory()
-        load_builtins(memory)
+        load_builtins(memory, self.execution)
         memory.push_frame(statechart.scope)
         memory.push_frame(statechart.scope)
 
 
         rhs_memory = MemoryPartialSnapshot("RHS", memory)
         rhs_memory = MemoryPartialSnapshot("RHS", memory)
@@ -115,20 +119,20 @@ class StatechartInstance(Instance):
 
 
         print_debug("\nRound hierarchy: " + str(self._big_step) + '\n')
         print_debug("\nRound hierarchy: " + str(self._big_step) + '\n')
 
 
-        self.state = StatechartState(statechart, self, gc_memory, rhs_memory, raise_internal)
-
-        # Chicken and egg problem
-        small_step.state = self.state
+        self.execution.gc_memory = gc_memory
+        self.execution.rhs_memory = rhs_memory
+        self.execution.raise_internal = raise_internal
+        self.execution.raise_next_bs = raise_nextbs
 
 
 
 
     # enter default states, generating a set of output events
     # enter default states, generating a set of output events
     def initialize(self, now: Timestamp) -> List[OutputEvent]:
     def initialize(self, now: Timestamp) -> List[OutputEvent]:
-        self.state.initialize()
-        return self.state.collect_output()
+        self.execution.initialize()
+        return self.execution.collect_output()
 
 
     # perform a big step. generating a set of output events
     # perform a big step. generating a set of output events
     def big_step(self, now: Timestamp, input_events: List[Event]) -> List[OutputEvent]:
     def big_step(self, now: Timestamp, input_events: List[Event]) -> List[OutputEvent]:
         # print_debug('attempting big step, input_events='+str(input_events))
         # print_debug('attempting big step, input_events='+str(input_events))
         self.set_input(input_events)
         self.set_input(input_events)
         self._big_step.run_and_cycle_events()
         self._big_step.run_and_cycle_events()
-        return self.state.collect_output()
+        return self.execution.collect_output()

+ 58 - 50
src/sccd/parser/statechart_parser.py

@@ -1,17 +1,23 @@
 from typing import *
 from typing import *
-from sccd.syntax.statechart import *
-from sccd.syntax.tree import *
-from sccd.execution.builtin_scope import *
-from sccd.parser.xml_parser import *
-from sccd.parser.expression_parser import *
+from sccd.statechart.static.statechart import *
+from sccd.statechart.static.tree import *
+from sccd.statechart.dynamic.builtin_scope import *
+from sccd.util.xml_parser import *
+from sccd.action_lang.parser.parser import *
 
 
 class SkipFile(Exception):
 class SkipFile(Exception):
   pass
   pass
 
 
-_blank_eval_context = EvalContext(current_state=None, events=[], memory=None)
-
 parse_f = functools.partial(parse, decorate_exceptions=(ModelError,))
 parse_f = functools.partial(parse, decorate_exceptions=(ModelError,))
 
 
+def check_duration_type(type):
+  if type != SCCDDuration:
+    msg = "Expression is '%s' type. Expected 'Duration' type." % str(type)
+    if type == SCCDInt:
+      msg += "\n Hint: Did you forget a duration unit sufix? ('s', 'ms', ...)"
+    raise Exception(msg)
+
+
 def create_statechart_parser(globals, src_file, load_external = True, parse = parse_f) -> Rules:
 def create_statechart_parser(globals, src_file, load_external = True, parse = parse_f) -> Rules:
   def parse_statechart(el):
   def parse_statechart(el):
     ext_file = el.get("src")
     ext_file = el.get("src")
@@ -34,8 +40,7 @@ def create_statechart_parser(globals, src_file, load_external = True, parse = pa
     def parse_semantics(el):
     def parse_semantics(el):
       # Use reflection to find the possible XML attributes and their values
       # Use reflection to find the possible XML attributes and their values
       for aspect_name, aspect_type in SemanticConfiguration.get_fields():
       for aspect_name, aspect_type in SemanticConfiguration.get_fields():
-        text = el.get(aspect_name)
-        if text is not None:
+        def parse_semantic_attribute(text):
           result = parse_semantic_choice(text)
           result = parse_semantic_choice(text)
           if result.data == "wildcard":
           if result.data == "wildcard":
             semantic_choice = list(aspect_type) # all options
             semantic_choice = list(aspect_type) # all options
@@ -45,6 +50,8 @@ def create_statechart_parser(globals, src_file, load_external = True, parse = pa
             semantic_choice = semantic_choice[0]
             semantic_choice = semantic_choice[0]
           setattr(statechart.semantics, aspect_name, semantic_choice)
           setattr(statechart.semantics, aspect_name, semantic_choice)
 
 
+        if_attribute(el, aspect_name, parse_semantic_attribute)
+
     def parse_datamodel(el):
     def parse_datamodel(el):
       body = parse_block(globals, el.text)
       body = parse_block(globals, el.text)
       body.init_stmt(statechart.scope)
       body.init_stmt(statechart.scope)
@@ -70,7 +77,8 @@ def create_statechart_parser(globals, src_file, load_external = True, parse = pa
       root = State("", parent=None)
       root = State("", parent=None)
       children_dict = {}
       children_dict = {}
       transitions = [] # All of the statechart's transitions accumulate here, cause we still need to find their targets, which we can't do before the entire state tree has been built. We find their targets when encoutering the </root> closing tag.
       transitions = [] # All of the statechart's transitions accumulate here, cause we still need to find their targets, which we can't do before the entire state tree has been built. We find their targets when encoutering the </root> closing tag.
-      next_after_id = 0 # Counter for 'after' transitions within the statechart.
+      after_triggers = [] # After triggers accumulate here
+      # next_after_id = 0 # Counter for 'after' transitions within the statechart.
 
 
       def create_actions_parser(scope):
       def create_actions_parser(scope):
 
 
@@ -79,7 +87,7 @@ def create_statechart_parser(globals, src_file, load_external = True, parse = pa
           def parse_param(el):
           def parse_param(el):
             expr_text = require_attribute(el, "expr")
             expr_text = require_attribute(el, "expr")
             expr = parse_expression(globals, expr_text)
             expr = parse_expression(globals, expr_text)
-            expr.init_rvalue(scope)
+            expr.init_expr(scope)
             params.append(expr)
             params.append(expr)
 
 
           def finish_raise():
           def finish_raise():
@@ -110,16 +118,23 @@ def create_statechart_parser(globals, src_file, load_external = True, parse = pa
         return {"raise": parse_raise, "code": parse_code}
         return {"raise": parse_raise, "code": parse_code}
 
 
       def deal_with_initial(el, state, children_dict):
       def deal_with_initial(el, state, children_dict):
-        initial = el.get("initial")
-        if initial is not None:
+        have_initial = False
+
+        def parse_attr_initial(initial):
+          nonlocal have_initial
+          have_initial = True
           try:
           try:
             state.default_state = children_dict[initial]
             state.default_state = children_dict[initial]
           except KeyError as e:
           except KeyError as e:
-            raise XmlError("initial=\"%s\": not a child." % (initial)) from e
-        elif len(state.children) == 1:
-          state.default_state = state.children[0]
-        elif len(state.children) > 1:
-          raise XmlError("More than 1 child state: must set 'initial' attribute.")
+            raise XmlError("Not a child.") from e
+
+        if_attribute(el, "initial", parse_attr_initial)
+
+        if not have_initial:
+          if len(state.children) == 1:
+            state.default_state = state.children[0]
+          elif len(state.children) > 1:
+            raise XmlError("More than 1 child state: must set 'initial' attribute.")
 
 
       def create_state_parser(parent, sibling_dict: Dict[str, State]={}):
       def create_state_parser(parent, sibling_dict: Dict[str, State]={}):
 
 
@@ -169,8 +184,6 @@ def create_statechart_parser(globals, src_file, load_external = True, parse = pa
           return (create_actions_parser(statechart.scope), finish_onexit)
           return (create_actions_parser(statechart.scope), finish_onexit)
 
 
         def parse_transition(el):
         def parse_transition(el):
-          nonlocal next_after_id
-          
           if parent is root:
           if parent is root:
             raise XmlError("Root <state> cannot be source of a transition.")
             raise XmlError("Root <state> cannot be source of a transition.")
 
 
@@ -178,54 +191,49 @@ def create_statechart_parser(globals, src_file, load_external = True, parse = pa
           target_string = require_attribute(el, "target")
           target_string = require_attribute(el, "target")
           transition = Transition(parent, [], scope, target_string)
           transition = Transition(parent, [], scope, target_string)
 
 
-          event = el.get("event")
-          if event is not None:
+          have_event_attr = False
+          def parse_attr_event(event):
+            nonlocal have_event_attr
+            have_event_attr = True
+
             positive_events, negative_events = parse_events_decl(globals, event)
             positive_events, negative_events = parse_events_decl(globals, event)
 
 
             # Optimization: sort events by ID
             # Optimization: sort events by ID
             # Allows us to save time later.
             # Allows us to save time later.
             positive_events.sort(key=lambda e: e.id)
             positive_events.sort(key=lambda e: e.id)
 
 
-            def add_event_params_to_scope(e: EventDecl):
+            def add_params_to_scope(e: EventDecl):
               for i,p in enumerate(e.params_decl):
               for i,p in enumerate(e.params_decl):
                 p.init_param(scope)
                 p.init_param(scope)
 
 
             for e in itertools.chain(positive_events, negative_events):
             for e in itertools.chain(positive_events, negative_events):
-              add_event_params_to_scope(e)
+              add_params_to_scope(e)
 
 
             if not negative_events:
             if not negative_events:
               transition.trigger = Trigger(positive_events)
               transition.trigger = Trigger(positive_events)
             else:
             else:
               transition.trigger = NegatedTrigger(positive_events, negative_events)
               transition.trigger = NegatedTrigger(positive_events, negative_events)
 
 
-          after = el.get("after")
-          if after is not None:
-            if event is not None:
+          def parse_attr_after(after):
+            if have_event_attr:
               raise XmlError("Cannot specify 'after' and 'event' at the same time.")
               raise XmlError("Cannot specify 'after' and 'event' at the same time.")
-            try:
-              after_expr = parse_expression(globals, after)
-              after_type = after_expr.init_rvalue(scope)
-              if after_type != SCCDDuration:
-                msg = "Expression is '%s' type. Expected 'Duration' type." % str(after_type)
-                if after_type == SCCDInt:
-                  msg += "\n Hint: Did you forget a duration unit sufix? ('s', 'ms', ...)"
-                raise Exception(msg)
-              event_name = "_after%d" % next_after_id # transition gets unique event name
-              transition.trigger = AfterTrigger(globals.events.assign_id(event_name), event_name, next_after_id, after_expr)
-              next_after_id += 1
-            except Exception as e:
-              raise XmlError("after=\"%s\": %s" % (after, str(e))) from e
-
-          cond = el.get("cond")
-          if cond is not None:
-            try:
-              expr = parse_expression(globals, cond)
-              expr.init_rvalue(scope)
-            except Exception as e:
-              raise XmlError("cond=\"%s\": %s" % (cond, str(e))) from e
-              # raise except_msg("cond=\"%s\": " % cond, e)
+            after_expr = parse_expression(globals, after)
+            after_type = after_expr.init_expr(scope)
+            check_duration_type(after_type)
+            after_id = len(after_triggers)
+            event_name = "_after%d" % after_id # transition gets unique event name
+            transition.trigger = AfterTrigger(globals.events.assign_id(event_name), event_name, after_id, after_expr)
+            after_triggers.append(transition.trigger)
+
+          def parse_attr_cond(cond):
+            expr = parse_expression(globals, cond)
+            expr.init_expr(scope)
             transition.guard = expr
             transition.guard = expr
 
 
+          if_attribute(el, "event", parse_attr_event)
+          if_attribute(el, "after", parse_attr_after)
+          if_attribute(el, "cond", parse_attr_cond)
+
           def finish_transition(*actions):
           def finish_transition(*actions):
             transition.actions = actions
             transition.actions = actions
             transitions.append((transition, el))
             transitions.append((transition, el))
@@ -263,7 +271,7 @@ def create_statechart_parser(globals, src_file, load_external = True, parse = pa
           except Exception as e:
           except Exception as e:
             raise XmlErrorElement(t_el, "Could not find target '%s'." % (transition.target_string)) from e
             raise XmlErrorElement(t_el, "Could not find target '%s'." % (transition.target_string)) from e
 
 
-        statechart.tree = StateTree(root)
+        statechart.tree = StateTree(root, after_triggers)
 
 
       return (create_state_parser(root, sibling_dict=children_dict), finish_root)
       return (create_state_parser(root, sibling_dict=children_dict), finish_root)
 
 

+ 0 - 0
src/sccd/statechart/static/__init__.py


+ 15 - 8
src/sccd/syntax/action.py

@@ -1,9 +1,15 @@
 from typing import *
 from typing import *
 from dataclasses import *
 from dataclasses import *
 from abc import *
 from abc import *
-from sccd.syntax.expression import *
-from sccd.syntax.statement import *
-from sccd.execution.event import *
+from sccd.action_lang.static.expression import *
+from sccd.action_lang.static.statement import *
+from sccd.statechart.dynamic.event import *
+
+@dataclass
+class EvalContext:
+    current_state: 'StatechartState'
+    events: List['Event']
+    memory: 'MemoryInterface'
 
 
 @dataclass
 @dataclass
 class Action(ABC):
 class Action(ABC):
@@ -24,14 +30,15 @@ class RaiseEvent(Action):
     def render(self) -> str:
     def render(self) -> str:
         return '^'+self.name
         return '^'+self.name
 
 
-    def _eval_params(self, ctx: EvalContext) -> List[Any]:
-        return [p.eval(ctx) for p in self.params]
+    def _eval_params(self, memory: MemoryInterface) -> List[Any]:
+        return [p.eval(memory) for p in self.params]
+
 @dataclass
 @dataclass
 class RaiseInternalEvent(RaiseEvent):
 class RaiseInternalEvent(RaiseEvent):
     event_id: int
     event_id: int
 
 
     def exec(self, ctx: EvalContext):
     def exec(self, ctx: EvalContext):
-        params = self._eval_params(ctx)
+        params = self._eval_params(ctx.memory)
         ctx.current_state.raise_internal(
         ctx.current_state.raise_internal(
             Event(id=self.event_id, name=self.name, port="", params=params))
             Event(id=self.event_id, name=self.name, port="", params=params))
 
 
@@ -41,7 +48,7 @@ class RaiseOutputEvent(RaiseEvent):
     time_offset: int
     time_offset: int
 
 
     def exec(self, ctx: EvalContext):
     def exec(self, ctx: EvalContext):
-        params = self._eval_params(ctx)
+        params = self._eval_params(ctx.memory)
         ctx.current_state.output.append(
         ctx.current_state.output.append(
             OutputEvent(Event(id=0, name=self.name, port=self.outport, params=params),
             OutputEvent(Event(id=0, name=self.name, port=self.outport, params=params),
                     OutputPortTarget(self.outport),
                     OutputPortTarget(self.outport),
@@ -55,7 +62,7 @@ class Code(Action):
     block: Block
     block: Block
 
 
     def exec(self, ctx: EvalContext):
     def exec(self, ctx: EvalContext):
-        self.block.exec(ctx)
+        self.block.exec(ctx.memory)
 
 
     def render(self) -> str:
     def render(self) -> str:
         return '/'+self.block.render()
         return '/'+self.block.render()

+ 2 - 2
src/sccd/syntax/statechart.py

@@ -2,8 +2,8 @@ from enum import *
 from dataclasses import *
 from dataclasses import *
 from typing import *
 from typing import *
 import itertools
 import itertools
-from sccd.syntax.tree import *
-from sccd.syntax.scope import *
+from sccd.statechart.static.tree import *
+from sccd.action_lang.static.scope import *
 
 
 class SemanticAspect:
 class SemanticAspect:
   def __str__(self):
   def __str__(self):

+ 28 - 4
src/sccd/syntax/tree.py

@@ -1,6 +1,6 @@
 import termcolor
 import termcolor
 from typing import *
 from typing import *
-from sccd.syntax.action import *
+from sccd.statechart.static.action import *
 from sccd.util.bitmap import *
 from sccd.util.bitmap import *
 
 
 
 
@@ -112,6 +112,25 @@ class Trigger:
     def render(self) -> str:
     def render(self) -> str:
         return ' ∧ '.join(e.render() for e in self.enabling)
         return ' ∧ '.join(e.render() for e in self.enabling)
 
 
+    def copy_params_to_stack(self, ctx: EvalContext):
+        # Both 'ctx.events' and 'self.enabling' are sorted by event ID,
+        # this way we have to iterate over each of both lists at most once.
+        iterator = iter(self.enabling)
+        try:
+            event_decl = next(iterator)
+            offset = 0
+            for e in ctx.events:
+                if e.id < event_decl.id:
+                    continue
+                else:
+                    while e.id > event_decl.id:
+                        event_decl = next(iterator)
+                    for p in e.params:
+                        ctx.memory.store(offset, p)
+                        offset += 1
+        except StopIteration:
+            pass
+
 @dataclass
 @dataclass
 class NegatedTrigger(Trigger):
 class NegatedTrigger(Trigger):
     disabling: List[EventDecl]
     disabling: List[EventDecl]
@@ -139,6 +158,11 @@ class AfterTrigger(Trigger):
     def render(self) -> str:
     def render(self) -> str:
         return "after("+self.delay.render()+")"
         return "after("+self.delay.render()+")"
 
 
+    # An 'after'-event also has 1 parameter, but it is not accessible to the user,
+    # hence the override.
+    def copy_params_to_stack(self, ctx: EvalContext):
+        pass
+
 @dataclass
 @dataclass
 class Transition:
 class Transition:
     source: State
     source: State
@@ -167,11 +191,11 @@ class StateTree:
 
 
     # root: The root state of a state,transition tree structure with with all fields filled in,
     # root: The root state of a state,transition tree structure with with all fields filled in,
     #       except the 'gen' fields. This function will fill in the 'gen' fields.
     #       except the 'gen' fields. This function will fill in the 'gen' fields.
-    def __init__(self, root: State):
+    def __init__(self, root: State, after_triggers: List[AfterTrigger]):
         self.state_dict = {} # mapping from 'full name' to State
         self.state_dict = {} # mapping from 'full name' to State
         self.state_list = [] # depth-first list of states
         self.state_list = [] # depth-first list of states
         self.transition_list = [] # all transitions in the tree, sorted by source state, depth-first
         self.transition_list = [] # all transitions in the tree, sorted by source state, depth-first
-        self.after_triggers = []
+        self.after_triggers = after_triggers
         self.stable_bitmap = Bitmap() # bitmap of state IDs of states that are stable. Only used for SYNTACTIC-maximality semantics.
         self.stable_bitmap = Bitmap() # bitmap of state IDs of states that are stable. Only used for SYNTACTIC-maximality semantics.
 
 
         next_id = 0
         next_id = 0
@@ -203,7 +227,7 @@ class StateTree:
                     has_eventless_transitions = True
                     has_eventless_transitions = True
                 elif isinstance(t.trigger, AfterTrigger):
                 elif isinstance(t.trigger, AfterTrigger):
                     after_triggers.append(t.trigger)
                     after_triggers.append(t.trigger)
-                    self.after_triggers.append(t.trigger)
+                    # self.after_triggers.append(t.trigger)
 
 
             for c in state.children:
             for c in state.children:
                 init_state(c, full_name, [state] + ancestors)
                 init_state(c, full_name, [state] + ancestors)

+ 0 - 36
src/sccd/syntax/test_scope.py

@@ -1,36 +0,0 @@
-import unittest
-from scope import *
-from typing import *
-
-class TestScope(unittest.TestCase):
-
-  def test_scope(self):
-    
-    builtin = Scope("builtin", parent=None)
-
-    # Lookup LHS value (creating it in the current scope if not found)
-
-    variable = builtin.put_variable_assignment("in_state", Callable[[List[str]], bool])
-    self.assertEqual(variable.offset, 0)
-
-    globals = Scope("globals", parent=builtin)
-    variable = globals.put_variable_assignment("x", int)
-    self.assertEqual(variable.offset, 1)
-
-    variable = globals.put_variable_assignment("in_state", Callable[[List[str]], bool])
-    self.assertEqual(variable.offset, 0)
-
-    local = Scope("local", parent=globals)
-    variable = local.put_variable_assignment("x", int)
-    self.assertEqual(variable.offset, 1)
-
-    # Lookup RHS value (returning None if not found)
-    variable = local.get("x")
-    self.assertEqual(variable.offset, 1)
-
-    # name 'y' doesn't exist in any scope
-    self.assertRaises(Exception, lambda: local.get("y"))
-
-    self.assertEqual(builtin.total_size(), 1)
-    self.assertEqual(globals.total_size(), 2)
-    self.assertEqual(local.total_size(), 2)

+ 10 - 2
src/sccd/parser/xml_parser.py

@@ -184,7 +184,7 @@ def parse(src_file, rules: RulesWDone, ignore_unmatched = False, decorate_except
       raise
       raise
     except XmlErrorElement as e:
     except XmlErrorElement as e:
       # Element where exception occured is part of exception object:
       # Element where exception occured is part of exception object:
-      e.args = (xml_fragment(src_file, t.el) + str(e),)
+      e.args = (xml_fragment(src_file, e.el) + str(e),)
       raise
       raise
 
 
   results = results_stack[0] # sole stack frame remaining
   results = results_stack[0] # sole stack frame remaining
@@ -194,5 +194,13 @@ def parse(src_file, rules: RulesWDone, ignore_unmatched = False, decorate_except
 def require_attribute(el, attr):
 def require_attribute(el, attr):
   val = el.get(attr)
   val = el.get(attr)
   if val is None:
   if val is None:
-    raise XmlError("missing required attribute '%s'" % attr)
+    raise XmlErrorElement(el, "missing required attribute '%s'" % attr)
   return val
   return val
+
+def if_attribute(el, attr, callback):
+  val = el.get(attr)
+  if val is not None:
+    try:
+      callback(val)
+    except Exception as e:
+      raise XmlErrorElement(el, "attribute %s=\"%s\": %s" % (attr, val, str(e))) from e

+ 1 - 1
test/legacy_render.py

@@ -5,7 +5,7 @@ import multiprocessing
 from lib.os_tools import *
 from lib.os_tools import *
 from lib.loader import *
 from lib.loader import *
 from sccd.compiler.utils import FormattedWriter
 from sccd.compiler.utils import FormattedWriter
-from sccd.syntax.statechart import *
+from sccd.statechart.static.statechart import *
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     parser = argparse.ArgumentParser(
     parser = argparse.ArgumentParser(

+ 8 - 5
test/lib/test_parser.py

@@ -1,7 +1,7 @@
-from sccd.parser.statechart_parser import *
+from sccd.statechart.parser.parser import *
 from sccd.model.globals import *
 from sccd.model.globals import *
 from sccd.controller.controller import InputEvent
 from sccd.controller.controller import InputEvent
-from sccd.execution.event import Event
+from sccd.statechart.dynamic.event import Event
 from sccd.model.model import *
 from sccd.model.model import *
 
 
 @dataclass
 @dataclass
@@ -23,8 +23,11 @@ def create_test_parser(create_statechart_parser):
         name = require_attribute(el, "name")
         name = require_attribute(el, "name")
         port = require_attribute(el, "port")
         port = require_attribute(el, "port")
         time = require_attribute(el, "time")
         time = require_attribute(el, "time")
-        duration = parse_duration(globals, time)
-        input.append(InputEvent(name=name, port=port, params=[], time_offset=duration))
+        time_expr = parse_expression(globals, time)
+        time_type = time_expr.init_expr(scope=None)
+        check_duration_type(time_type)
+        time_val = time_expr.eval(memory=None)
+        input.append(InputEvent(name=name, port=port, params=[], time_offset=time_val))
 
 
       return [("event+", parse_input_event)]
       return [("event+", parse_input_event)]
 
 
@@ -42,7 +45,7 @@ def create_test_parser(create_statechart_parser):
           def parse_param(el):
           def parse_param(el):
             val_text = require_attribute(el, "val")
             val_text = require_attribute(el, "val")
             val_expr = parse_expression(globals, val_text)
             val_expr = parse_expression(globals, val_text)
-            val = val_expr.eval(EvalContext(current_state=None, events=[], memory=None))
+            val = val_expr.eval(memory=None)
             params.append(val)
             params.append(val)
 
 
           return [("param*", parse_param)]
           return [("param*", parse_param)]

+ 5 - 1
test/render.py

@@ -2,9 +2,10 @@ import argparse
 import sys
 import sys
 import subprocess
 import subprocess
 import multiprocessing
 import multiprocessing
+import os
 from lib.os_tools import *
 from lib.os_tools import *
 from sccd.util.indenting_writer import *
 from sccd.util.indenting_writer import *
-from sccd.parser.statechart_parser import *
+from sccd.statechart.parser.parser import *
 import lxml.etree as ET
 import lxml.etree as ET
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
@@ -34,6 +35,9 @@ if __name__ == '__main__':
       parser.print_usage()
       parser.print_usage()
       exit()
       exit()
 
 
+    # From this point on, disable terminal colors as we write output files
+    os.environ["ANSI_COLORS_DISABLED"] = "1"
+
     def process(src):
     def process(src):
       try:
       try:
         parse_statechart = create_statechart_parser(Globals(), src, load_external=False)[0][1]
         parse_statechart = create_statechart_parser(Globals(), src, load_external=False)[0][1]

+ 4 - 4
test/test_files/day_atlee/statechart_fig7_dialer.svg

@@ -4,11 +4,11 @@
 <!-- Generated by graphviz version 2.40.1 (20161225.0304)
 <!-- Generated by graphviz version 2.40.1 (20161225.0304)
  -->
  -->
 <!-- Title: state transitions Pages: 1 -->
 <!-- Title: state transitions Pages: 1 -->
-<svg width="225pt" height="83pt"
- viewBox="0.00 0.00 224.92 83.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<svg width="250pt" height="83pt"
+ viewBox="0.00 0.00 250.47 83.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 79)">
 <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 79)">
 <title>state transitions</title>
 <title>state transitions</title>
-<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-79 220.918,-79 220.918,4 -4,4"/>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-79 246.471,-79 246.471,4 -4,4"/>
 <!-- __initial -->
 <!-- __initial -->
 <g id="node1" class="node">
 <g id="node1" class="node">
 <title>__initial</title>
 <title>__initial</title>
@@ -33,7 +33,7 @@
 <title>_D&#45;&gt;_D</title>
 <title>_D&#45;&gt;_D</title>
 <path fill="none" stroke="#000000" d="M56.0183,-23.9381C67.888,-24.3856 78,-22.4063 78,-18 78,-14.9707 73.2205,-13.0885 66.3762,-12.3533"/>
 <path fill="none" stroke="#000000" d="M56.0183,-23.9381C67.888,-24.3856 78,-22.4063 78,-18 78,-14.9707 73.2205,-13.0885 66.3762,-12.3533"/>
 <polygon fill="#000000" stroke="#000000" points="66.1128,-8.8446 56.0183,-12.0619 65.9158,-15.8419 66.1128,-8.8446"/>
 <polygon fill="#000000" stroke="#000000" points="66.1128,-8.8446 56.0183,-12.0619 65.9158,-15.8419 66.1128,-8.8446"/>
-<text text-anchor="start" x="78" y="-15" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dial [c &lt; 10]/c = c + 1 ^out.out &#160;&#160;</text>
+<text text-anchor="start" x="78" y="-15" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dial(d:int) [c &lt; 10]/c = c + 1 ^out.out &#160;&#160;</text>
 </g>
 </g>
 </g>
 </g>
 </svg>
 </svg>

+ 4 - 4
test/test_files/features/datamodel/test_cond.svg

@@ -4,11 +4,11 @@
 <!-- Generated by graphviz version 2.40.1 (20161225.0304)
 <!-- Generated by graphviz version 2.40.1 (20161225.0304)
  -->
  -->
 <!-- Title: state transitions Pages: 1 -->
 <!-- Title: state transitions Pages: 1 -->
-<svg width="135pt" height="147pt"
- viewBox="0.00 0.00 135.19 147.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<svg width="127pt" height="147pt"
+ viewBox="0.00 0.00 126.85 147.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 143)">
 <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 143)">
 <title>state transitions</title>
 <title>state transitions</title>
-<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-143 131.188,-143 131.188,4 -4,4"/>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-143 122.85,-143 122.85,4 -4,4"/>
 <!-- __initial -->
 <!-- __initial -->
 <g id="node1" class="node">
 <g id="node1" class="node">
 <title>__initial</title>
 <title>__initial</title>
@@ -40,7 +40,7 @@
 <title>_start&#45;&gt;_done</title>
 <title>_start&#45;&gt;_done</title>
 <path fill="none" stroke="#000000" d="M28,-63.8314C28,-58.4728 28,-52.4735 28,-46.6262"/>
 <path fill="none" stroke="#000000" d="M28,-63.8314C28,-58.4728 28,-52.4735 28,-46.6262"/>
 <polygon fill="#000000" stroke="#000000" points="31.5001,-46.4363 28,-36.4363 24.5001,-46.4363 31.5001,-46.4363"/>
 <polygon fill="#000000" stroke="#000000" points="31.5001,-46.4363 28,-36.4363 24.5001,-46.4363 31.5001,-46.4363"/>
-<text text-anchor="start" x="28" y="-47" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">e [x == 42]^out.done &#160;&#160;</text>
+<text text-anchor="start" x="28" y="-47" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">[x == 42]^out.done &#160;&#160;</text>
 </g>
 </g>
 </g>
 </g>
 </svg>
 </svg>

+ 0 - 2
test/test_files/features/functions/test_closure.xml

@@ -28,8 +28,6 @@
     <root initial="s1">
     <root initial="s1">
       <state id="s1">
       <state id="s1">
         <onentry>
         <onentry>
-          <!-- calling 'increment', the statement "i += 1" will cause a runtime error
-               since the stack frame of 'counter', containing variable 'i' is no longer there -->
           <code>
           <code>
             x = increment();
             x = increment();
           </code>
           </code>

+ 2 - 0
test/test_files/features/functions/test_functions.xml

@@ -11,6 +11,8 @@
         return float_to_int(log10(i)) + 1;
         return float_to_int(log10(i)) + 1;
       };
       };
 
 
+      parameterless = func {};
+
       ok = numdigits(123) == 3 and digit(123, 1) == 2;
       ok = numdigits(123) == 3 and digit(123, 1) == 2;
     </datamodel>
     </datamodel>