Browse Source

Add 'duration' literal to action language + DurationLiteral expression type to statecharts syntax. Begin implementation of static type checker for expressions.

Joeri Exelmans 5 years ago
parent
commit
3618939825

+ 2 - 3
src/sccd/controller/controller.py

@@ -5,7 +5,6 @@ from sccd.controller.event_queue import *
 from sccd.execution.event import *
 from sccd.controller.object_manager import *
 from sccd.util.debug import print_debug
-# from sccd.syntax.model import *
 from sccd.model.model import *
 
 @dataclasses.dataclass
@@ -39,12 +38,12 @@ class Controller:
             if input.name == "":
                 raise Exception("Input event can't have an empty name.")
         
-            if input.port not in self.model.namespace.inports:
+            if input.port not in self.model.context.inports:
                 raise Exception("No such port: '" + input.port + "'")
 
 
             e = Event(
-                id=self.model.namespace.get_event_id(input.name),
+                id=self.model.context.get_event_id(input.name),
                 name=input.name,
                 port=input.port,
                 parameters=input.parameters)

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

@@ -32,7 +32,7 @@ class EventTarget(ABC):
 
 # A raised output event with a target and a time offset.
 class OutputEvent:
-    def __init__(self, event: Event, target: EventTarget, time_offset=0):
+    def __init__(self, event: Event, target: EventTarget, time_offset = 0):
         self.event = event
         self.target = target
         self.time_offset = time_offset

+ 10 - 9
src/sccd/execution/round.py

@@ -2,7 +2,7 @@ from typing import *
 from sccd.execution.event import *
 from sccd.util.bitmap import *
 from sccd.syntax.tree import *
-from sccd.util.debug import print_debug
+from sccd.util.debug import *
 
 class CandidatesGenerator:
     def __init__(self, reverse: bool):
@@ -61,8 +61,8 @@ class Round(ABC):
         self.name = name
         self.parent = None
 
-        self.remainder_events = []
-        self.next_events = []
+        self.remainder_events = [] # events enabled for the remainder of the current round
+        self.next_events = [] # events enabled for the entirety of the next round
 
     def run(self, arenas_changed: Bitmap = Bitmap()) -> Bitmap:
         changed = self._internal_run(arenas_changed)
@@ -123,12 +123,13 @@ class SmallStep(Round):
         enabled_events = self.enabled_events()
         candidates = self.generator.generate(self.state, enabled_events, arenas_changed)
 
-        candidates = list(candidates) # convert generator to list (gotta do this, otherwise the generator will be all used up by our debug printing
-        if candidates:
-            print_debug("")
-            if enabled_events:
-                print_debug("events: " + str(enabled_events))
-            print_debug("candidates: " + str(candidates))
+        if is_debug():
+            candidates = list(candidates) # convert generator to list (gotta do this, otherwise the generator will be all used up by our debug printing
+            if candidates:
+                print_debug("")
+                if enabled_events:
+                    print_debug("events: " + str(enabled_events))
+                print_debug("candidates: " + str(candidates))
 
         for t in candidates:
             arenas_changed |= self.state.fire_transition(enabled_events, t)

+ 4 - 8
src/sccd/execution/statechart_instance.py

@@ -3,17 +3,13 @@ import functools
 from typing import List, Tuple, Iterable
 from sccd.execution.instance import *
 from sccd.syntax.statechart import *
-# from sccd.execution.event import *
-# from sccd.runtime.semantic_options import *
 from sccd.util.debug import print_debug
 from sccd.util.bitmap import *
-# from sccd.model.model import *
 from sccd.execution.round import *
 from sccd.execution.statechart_state import *
 
 class StatechartInstance(Instance):
     def __init__(self, statechart: Statechart, object_manager):
-        self.statechart = statechart
         self.object_manager = object_manager
 
         semantics = statechart.semantics
@@ -97,9 +93,9 @@ class StatechartInstance(Instance):
         # can the next big step still contain transitions, even if there are no input events?
         stable |= not input_events and not arenas_changed
 
-        # if arenas_changed:
-        #     print_debug(termcolor.colored('completed big step (time=%d)'%now+(" (stable)" if stable else ""), 'red'))
-        # else:
-        #     print_debug(termcolor.colored("(stable)" if stable else "", 'red'))
+        if arenas_changed:
+            print_debug(termcolor.colored('completed big step (time=%d)'%now+(" (stable)" if stable else ""), 'red'))
+        else:
+            print_debug(termcolor.colored("(stable)" if stable else "", 'red'))
 
         return (stable, output)

+ 2 - 3
src/sccd/execution/statechart_state.py

@@ -1,6 +1,5 @@
 from typing import *
 from sccd.syntax.statechart import *
-# from sccd.syntax.model import Statechart
 from sccd.execution.event import *
 from sccd.util.debug import print_debug
 from sccd.util.bitmap import *
@@ -79,7 +78,7 @@ class StatechartState:
         self.eventless_states -= s.has_eventless_transitions
         # execute exit action(s)
         self._perform_actions(events, s.exit)
-        self.configuration_bitmap &= ~Bit(s.state_id)
+        self.configuration_bitmap &= ~bit(s.state_id)
             
     # execute transition action(s)
     self._perform_actions(events, t.actions)
@@ -90,7 +89,7 @@ class StatechartState:
     for s in enter_set:
         print_debug(termcolor.colored('  ENTER %s' % s.name, 'green'))
         self.eventless_states += s.has_eventless_transitions
-        self.configuration_bitmap |= Bit(s.state_id)
+        self.configuration_bitmap |= bit(s.state_id)
         # execute enter action(s)
         self._perform_actions(events, s.enter)
         self._start_timers(s.after_triggers)

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

@@ -39,7 +39,7 @@ def load_model(src_file) -> Tuple[MultiInstanceModel, Optional[Test]]:
     default = c.get("default", "")
 
     scxml_node = c.find("scxml", root.nsmap)
-    statechart = load_statechart(scxml_node, model.namespace)
+    statechart = load_statechart(scxml_node, model.context)
 
     model.classes[class_name] = statechart
     if default or len(classes) == 1:

+ 21 - 0
src/sccd/model/context.py

@@ -0,0 +1,21 @@
+from dataclasses import *
+from typing import *
+from sccd.syntax.expression import *
+
+class Namespace:
+  def __init__(self):
+    self.names: Dict[str, int] = {}
+
+  def assign_id(self, name: str) -> int:
+    return self.names.setdefault(name, len(self.names))
+
+  def get_id(self, name: str) -> int:
+    return self.names[name]
+
+# @dataclass
+class Context:
+  def __init__(self):
+    self.events = Namespace()
+    self.inports = Namespace()
+    self.outports = Namespace()
+    self.durations: List[DurationLiteral] = []

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

@@ -2,11 +2,11 @@ from abc import *
 from dataclasses import *
 from typing import *
 from sccd.syntax.statechart import *
-from sccd.model.namespace import *
+from sccd.model.context import *
 
 @dataclass
 class AbstractModel(ABC):
-  namespace: Namespace
+  context: Context
 
   @abstractmethod
   def get_default_class(self) -> Statechart:

+ 0 - 23
src/sccd/model/namespace.py

@@ -1,23 +0,0 @@
-from dataclasses import *
-from typing import *
-
-@dataclass
-class Namespace:
-  def __init__(self):
-    self.events: Dict[str, int] = {}
-    self.inports: List[str] = []
-    self.outports: List[str] = []
-
-  def assign_event_id(self, name: str) -> int:
-    return self.events.setdefault(name, len(self.events))
-
-  def get_event_id(self, name: str) -> int:
-    return self.events[name]
-
-  def add_inport(self, port: str):
-    if port not in self.inports:
-      self.inports.append(port)
-
-  def add_outport(self, port: str):
-    if port not in self.outports:
-      self.outports.append(port)

+ 59 - 31
src/sccd/model/xml_loader.py

@@ -5,7 +5,7 @@ from copy import deepcopy
 from lark import Lark, Transformer
 
 from sccd.syntax.statechart import *
-from sccd.model.namespace import *
+from sccd.model.context import *
 
 import sccd.schema
 
@@ -14,6 +14,9 @@ with open(os.path.join(os.path.dirname(sccd.schema.__file__),"grammar.g")) as fi
 
 # Lark transformer for parsetree-less parsing of expressions
 class _ExpressionTransformer(Transformer):
+  def __init__(self):
+    super().__init__()
+    self.context: Context = None
   array = Array
   block = Block
   def string(self, node):
@@ -37,39 +40,61 @@ class _ExpressionTransformer(Transformer):
     return Group(node[0])
   def assignment(self, node):
     return Assignment(node[0], node[1].value, node[2])
+  def duration(self, node):
+    unit = {
+      "fs": FemtoSecond,
+      "ps": PicoSecond,
+      "ns": Nanosecond,
+      "us": Microsecond,
+      "ms": Millisecond,
+      "s": Second,
+      "m": Minute,
+      "h": Hour
+    }[node[0].children[1]]
+    d = DurationLiteral(Duration(int(node[0].children[0]),unit))
+    self.context.durations.append(d)
+    return d
 
-_expr_parser = Lark(_grammar, parser="lalr", start=["expr", "block"], transformer=_ExpressionTransformer())
+# Global variables so we don't have to rebuild our parser every time
+_transformer = _ExpressionTransformer()
+_expr_parser = Lark(_grammar, parser="lalr", start=["expr", "block"], transformer=_transformer)
 _state_ref_parser = Lark(_grammar, parser="lalr", start=["state_ref"])
 
+def parse_expression(context: Context, expr: str) -> Expression:
+  _transformer.context = context
+  return _expr_parser.parse(expr, start="expr")
+
+def parse_block(context: Context, block: str) -> Statement:
+  _transformer.context = context
+  return _expr_parser.parse(block, start="block")
 
 # parent_node: XML node containing any number of action nodes as direct children
-def load_actions(namespace: Namespace, parent_node) -> List[Action]:
+def load_actions(context: Context, parent_node) -> List[Action]:
   def load_action(action_node) -> Optional[Action]:
-      tag = ET.QName(action_node).localname
+      # tag = ET.QName(action_node).localname
+      tag = action_node.tag
       if tag == "raise":
         name = action_node.get("event")
         port = action_node.get("port")
         if not port:
-          event_id = namespace.assign_event_id(name)
+          event_id = context.events.assign_id(name)
           return RaiseInternalEvent(name=name, parameters=[], event_id=event_id)
         else:
-          namespace.add_outport(port)
+          context.outports.assign_id(port)
           return RaiseOutputEvent(name=name, parameters=[], outport=port, time_offset=0)
       elif tag == "code":
-        code = action_node.text
         try:
-          stmt_block = _expr_parser.parse(code, start="block")
-          return Code(stmt_block)
+          block = parse_block(context, block=action_node.text)
         except:
-          raise Exception("Line %d: <%s>: Error parsing code." % (action_node.sourceline, tag))
+          raise Exception("Line %d: <%s>: Error parsing code: '%s'" % (action_node.sourceline, tag, action_node.text))
+        return Code(block)
       else:
         raise Exception("Line %d: <%s>: Unsupported action tag." % (action_node.sourceline, tag))
-  return [load_action(child) for child in parent_node]
-
+  return [load_action(child) for child in parent_node if child.tag is not ET.Comment]
 
 # Load state tree from XML <tree> node.
-# Namespace is required for building event namespace and in/outport discovery.
-def load_tree(namespace: Namespace, tree_node) -> StateTree:
+# Context is required for building event namespace and in/outport discovery.
+def load_tree(context: Context, tree_node) -> StateTree:
 
   transition_nodes: List[Tuple[Any, State]] = [] # List of (<transition>, State) tuples
 
@@ -78,7 +103,8 @@ def load_tree(namespace: Namespace, tree_node) -> StateTree:
   def load_state(state_node) -> Optional[State]:
     state = None
     name = state_node.get("id", "")
-    tag = ET.QName(state_node).localname
+    # tag = ET.QName(state_node).localname
+    tag = state_node.tag
     if tag == "state":
         state = State(name)
     elif tag == "parallel" : 
@@ -94,6 +120,8 @@ def load_tree(namespace: Namespace, tree_node) -> StateTree:
 
     initial = state_node.get("initial", "")
     for xml_child in state_node.getchildren():
+        if xml_child.tag is ET.Comment:
+          continue # skip comments
         child = load_state(xml_child) # may throw
         if child:
           state.addChild(child)
@@ -112,7 +140,7 @@ def load_tree(namespace: Namespace, tree_node) -> StateTree:
     def _get_enter_exit(tag, setter):
       node = state_node.find(tag, state_node.nsmap)
       if node is not None:
-        actions = load_actions(namespace, node)
+        actions = load_actions(context, node)
         setter(actions)
 
     _get_enter_exit("onentry", state.setEnter)
@@ -155,19 +183,19 @@ def load_tree(namespace: Namespace, tree_node) -> StateTree:
     port = t_node.get("port")
     after = t_node.get("after")
     if after is not None:
-      after_expr = _expr_parser.parse(after, start="expr")
+      after_expr = parse_expression(context, expr=after)
       # print(after_expr)
       name = "_after%d" % next_after_id # transition gets unique event name
       next_after_id += 1
-      trigger = AfterTrigger(namespace.assign_event_id(name), name, after_expr)
+      trigger = AfterTrigger(context.events.assign_id(name), name, after_expr)
     elif name is not None:
-      trigger = Trigger(namespace.assign_event_id(name), name, port)
-      namespace.add_inport(port)
+      trigger = Trigger(context.events.assign_id(name), name, port)
+      context.inports.assign_id(port)
     else:
       trigger = None
     transition.setTrigger(trigger)
     # Actions
-    actions = load_actions(namespace, t_node)
+    actions = load_actions(context, t_node)
     transition.setActions(actions)
     # Guard
     cond = t_node.get("cond")
@@ -180,8 +208,8 @@ def load_tree(namespace: Namespace, tree_node) -> StateTree:
         expr = _expr_parser.parse(cond, start="expr")
         # print(expr)
         transition.setGuard(expr)
-      except:
-        raise Exception("Line %d: <transition> with cond=\"%s\": Parse error." % (t_node.sourceline, cond))
+      except Exception as e:
+        raise Exception("Line %d: <transition> with cond=\"%s\": %s" % (t_node.sourceline, cond, str(e)))
     source.addTransition(transition)
 
   # Calculate stuff like list of ancestors, descendants, etc.
@@ -208,26 +236,26 @@ def load_semantics(semantics: Semantics, semantics_node):
           value = aspect.type[key.upper()]
           setattr(semantics, aspect.name, value)
 
-def load_datamodel(datamodel_node) -> DataModel:
+def load_datamodel(context: Context, datamodel_node) -> DataModel:
   datamodel = DataModel()
   if datamodel_node is not None:
     for var_node in datamodel_node.findall("var"):
       id = var_node.get("id")
       expr = var_node.get("expr")
-      val = _expr_parser.parse(expr, start="expr")
+      val = parse_expression(context, expr=expr)
       datamodel.names[id] = Variable(val.eval([], datamodel))
   return datamodel
 
-# Namespace is required for building event namespace and in/outport discovery.
-def load_statechart(namespace: Namespace, sc_node) -> Statechart:
+# Context is required for building event namespace and in/outport discovery.
+def load_statechart(context: Context, sc_node) -> Statechart:
+  datamodel_node = sc_node.find("datamodel")
+  datamodel = load_datamodel(context, datamodel_node)
+
   tree_node = sc_node.find("tree")
-  state_tree = load_tree(namespace, tree_node)
+  state_tree = load_tree(context, tree_node)
 
   semantics_node = sc_node.find("semantics")
   semantics = Semantics() # start with default semantics
   load_semantics(semantics, semantics_node)
 
-  datamodel_node = sc_node.find("datamodel")
-  datamodel = load_datamodel(datamodel_node)
-
   return Statechart(tree=state_tree, semantics=semantics, datamodel=datamodel)

+ 14 - 1
src/sccd/schema/grammar.g

@@ -5,7 +5,6 @@
 %import common.WS
 %ignore WS
 
-%import common.SIGNED_NUMBER
 %import common.ESCAPED_STRING
 
 _PATH_SEP: "/" 
@@ -66,6 +65,7 @@ array: "[" (expr ("," expr)*)? "]"
 ?literal: ESCAPED_STRING -> string
         | INT -> int
         | bool_literal -> bool
+        | duration_literal -> duration
 
 ?compare_operator: EQ | NEQ | GT | GEQ | LT | LEQ
 ?add_operator: PLUS | MINUS
@@ -95,6 +95,19 @@ FALSE: "False"
 
 INT: /[0-9]+/
 
+?duration_literal: (INT duration_unit)+
+
+?duration_unit: TIME_H | TIME_M | TIME_S | TIME_MS | TIME_US | TIME_NS | TIME_PS | TIME_FS
+
+TIME_H: "h"
+TIME_M: "m"
+TIME_S: "s"
+TIME_MS: "ms"
+TIME_US: "us"
+TIME_NS: "ns"
+TIME_PS: "ps"
+TIME_FS: "fs"
+
 
 // Statement parsing
 

+ 58 - 1
src/sccd/syntax/expression.py

@@ -2,6 +2,7 @@ from abc import *
 from typing import *
 from dataclasses import *
 from sccd.syntax.datamodel import *
+from sccd.util.duration import *
 
 class Expression(ABC):
     # Evaluation should NOT have side effects.
@@ -10,6 +11,11 @@ class Expression(ABC):
     def eval(self, events, datamodel):
         pass
 
+    # Types of expressions are statically checked in SCCD.
+    # @abstractmethod
+    def get_static_type(self) -> type:
+        pass
+
 class LHS(Expression):
     @abstractmethod
     def lhs(self, events, datamodel) -> Variable:
@@ -23,7 +29,7 @@ class LHS(Expression):
 class Identifier(LHS):
     name: str
 
-    def lhs(self, events, datamodel):
+    def lhs(self, events, datamodel) -> Variable:
         return datamodel.names[self.name]
 
     def render(self):
@@ -52,6 +58,9 @@ class StringLiteral(Expression):
     def render(self):
         return '"'+self.string+'"'
 
+    def get_static_type(self) -> type:
+        return str
+
 @dataclass
 class IntLiteral(Expression):
     i: int 
@@ -62,6 +71,9 @@ class IntLiteral(Expression):
     def render(self):
         return str(self.i)
 
+    def get_static_type(self) -> type:
+        return int
+
 @dataclass
 class BoolLiteral(Expression):
     b: bool 
@@ -72,9 +84,36 @@ class BoolLiteral(Expression):
     def render(self):
         return "true" if self.b else "false"
 
+    def get_static_type(self) -> type:
+        return bool
+
+@dataclass
+class DurationLiteral(Expression):
+    original: Duration
+
+    # All duration expressions in a model evaluate to a duration with the same unit.
+    normalized: Optional[Duration] = None
+
+    def eval(self, events, datamodel):
+        return self.normalized
+
+    def render(self):
+        return self.original.__str__()
+
+    def get_static_type(self) -> type:
+        return Duration
+
 @dataclass
 class Array(Expression):
     elements: List[Any]
+    t: type = None
+
+    def __post_init__(self):
+        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
 
     def eval(self, events, datamodel):
         return [e.eval(events, datamodel) for e in self.elements]
@@ -82,6 +121,9 @@ class Array(Expression):
     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
@@ -94,12 +136,21 @@ class Group(Expression):
     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()
+        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)))
+
     def eval(self, events, datamodel):
         
         return {
@@ -139,6 +190,9 @@ class BinaryExpression(Expression):
     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.
@@ -152,3 +206,6 @@ class UnaryExpression(Expression):
 
     def render(self):
         return self.operator + ' ' + self.expr.render()
+
+    def get_static_type(self) -> type:
+        return self.expr.get_static_type()

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

@@ -57,7 +57,7 @@ class State:
         for c in self.children:
             self.descendants.extend(c.descendants)
         for d in self.descendants:
-            self.descendant_bitmap |= Bit(d.state_id)
+            self.descendant_bitmap |= bit(d.state_id)
         return next_id
 
     # def print(self, w = FormattedWriter()):
@@ -153,7 +153,7 @@ class AfterTrigger(Trigger):
         return self.expected_id
 
     def render(self) -> str:
-        return "after("+str(self.delay)+")"
+        return "after("+self.delay.render()+")"
 
 class Transition:
     def __init__(self, source, targets: List[State]):

+ 47 - 22
src/sccd/test/xml_loader.py

@@ -7,39 +7,64 @@ from sccd.model.xml_loader import *
 from sccd.syntax.statechart import *
 from copy import deepcopy
 
+# For a test with "should_fail_load" attribute set, we generate a succeeding test if the loading failed :)
+class PseudoTest(unittest.TestCase):
+  def __init__(self, name: str):
+    super().__init__()
+    self.name = name
+
+  def __str__(self):
+    return self.name
+
+  def runTest(self):
+    pass
+
 # Returned list contains more than one test if the semantic configuration contains wildcard values.
 def load_test(src_file) -> List[Test]:
-  namespace = Namespace()
+  namespace = Context()
 
   test_node = ET.parse(src_file).getroot()
-  sc_node = test_node.find("statechart")
-  src = sc_node.get("src")
-  if src is None:
-    statechart = load_statechart(namespace, sc_node)
-  else:
-    external_node = ET.parse(os.path.join(os.path.dirname(src_file), src)).getroot()
-    statechart = load_statechart(namespace, external_node)
-    semantics_node = sc_node.find("override_semantics")
-    load_semantics(statechart.semantics, semantics_node)
+  should_fail_load = test_node.get("should_fail_load", "") == "true"
+
+  try:
+    sc_node = test_node.find("statechart")
+    src = sc_node.get("src")
+    if src is None:
+      statechart = load_statechart(namespace, sc_node)
+    else:
+      external_node = ET.parse(os.path.join(os.path.dirname(src_file), src)).getroot()
+      statechart = load_statechart(namespace, external_node)
+      semantics_node = sc_node.find("override_semantics")
+      load_semantics(statechart.semantics, semantics_node)
+
+    input_node = test_node.find("input")
+    output_node = test_node.find("output")
+    input = load_input(input_node)
+    output = load_output(output_node)
+
+    def variant_description(i, variant) -> str:
+      if not variant:
+        return ""
+      return " (variant %d: %s)" % (i, ",".join(str(val) for val in variant.values()))
 
-  input_node = test_node.find("input")
-  output_node = test_node.find("output")
-  input = load_input(input_node)
-  output = load_output(output_node)
+  except Exception as e:
+    if should_fail_load:
+      print("load failed as excpeted:", e)
+      return [ PseudoTest(name=src_file) ]
+    else:
+      raise e
 
-  def variant_description(i, variant) -> str:
-    if not variant:
-      return ""
-    return " (variant %d: %s)" % (i, ",".join(str(val) for val in variant.values()))
+  if should_fail_load:
+    raise Exception("Unexpectedly suceeded at loading test '%s'" % src_file)
 
   return [
     Test(
-      src_file + variant_description(i, variant),
-      SingleInstanceModel(
+      name=src_file + variant_description(i, variant),
+      model=SingleInstanceModel(
         namespace,
         Statechart(tree=statechart.tree, datamodel=deepcopy(statechart.datamodel), semantics=dataclasses.replace(statechart.semantics, **variant))),
-      input,
-      output)
+      input=input,
+      output=output)
     for i, variant in enumerate(statechart.semantics.wildcard_cart_product())
   ]
 

+ 4 - 1
src/sccd/util/bitmap.py

@@ -1,6 +1,9 @@
 from functools import reduce
 import math
 
+# Bitmap inherits 'int' and is therefore immutable.
+# Methods that return a Bitmap return a new bitmap and leave the arguments untouched.
+# To change a bitmap, use an assignment operator ('=', '|=', '&=', ...)
 class Bitmap(int):
   def __new__(cls, value=0, *args, **kwargs):
     return super(cls, cls).__new__(cls, value)
@@ -43,5 +46,5 @@ class Bitmap(int):
     return math.floor(math.log2(x & -x))
 
 
-def Bit(pos):
+def bit(pos):
   return Bitmap(2 ** pos)

+ 4 - 0
src/sccd/util/debug.py

@@ -7,3 +7,7 @@ except KeyError:
 def print_debug(msg):
     if DEBUG:
         print(msg)
+
+
+def is_debug() -> bool:
+  return DEBUG

+ 5 - 7
src/sccd/util/duration.py

@@ -2,6 +2,7 @@ from enum import *
 from dataclasses import *
 from typing import *
 import math
+import functools
 
 @dataclass
 class _Unit:
@@ -90,6 +91,9 @@ class Duration:
   def __eq__(self, other):
     return self.val == other.val and self.unit is other.unit
 
+  def __mul__(self, other):
+    return Duration(self.val*other, self.unit)
+
 def gcd_pair(x: Duration, y: Duration) -> Duration:
   if x.unit is None:
     return y
@@ -107,10 +111,4 @@ def gcd_pair(x: Duration, y: Duration) -> Duration:
   return Duration(gcd, x_converted.unit).normalize()
 
 def gcd(*iterable) -> Duration:
-  g = Duration(0)
-  for d in iterable:
-    if g is None:
-      g = d
-    else:
-      g = gcd_pair(g, d)
-  return g
+  return functools.reduce(gcd_pair, iterable, Duration(0))

+ 1 - 1
test/render.py

@@ -39,7 +39,7 @@ if __name__ == '__main__':
       tree_node = statechart_node.find(".//tree")
       if tree_node is None:
         return # no tree here :(
-      tree = load_tree(Namespace(), tree_node)
+      tree = load_tree(Context(), tree_node)
 
       target_path = lambda ext: os.path.join(args.output_dir, dropext(src)+ext)
       smcat_target = target_path('.smcat')

+ 22 - 22
test/test_files/features/after/test_after.svg

@@ -4,29 +4,29 @@
 <!-- Generated by graphviz version 2.40.1 (20161225.0304)
  -->
 <!-- Title: state transitions Pages: 1 -->
-<svg width="281pt" height="231pt"
- viewBox="0.00 0.00 280.73 231.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<svg width="242pt" height="231pt"
+ viewBox="0.00 0.00 242.00 231.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 227)">
 <title>state transitions</title>
-<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-227 276.733,-227 276.733,4 -4,4"/>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-227 238,-227 238,4 -4,4"/>
 <!-- __initial -->
 <g id="node1" class="node">
 <title>__initial</title>
-<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="119" cy="-217.5" rx="5.5" ry="5.5"/>
+<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="117" cy="-217.5" rx="5.5" ry="5.5"/>
 </g>
 <!-- _s1 -->
 <g id="node2" class="node">
 <title>_s1</title>
-<polygon fill="transparent" stroke="transparent" stroke-width="2" points="147,-184 91,-184 91,-148 147,-148 147,-184"/>
-<text text-anchor="start" x="112.6646" y="-162.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">s1</text>
-<path fill="none" stroke="#000000" stroke-width="2" d="M103.3333,-149C103.3333,-149 134.6667,-149 134.6667,-149 140.3333,-149 146,-154.6667 146,-160.3333 146,-160.3333 146,-171.6667 146,-171.6667 146,-177.3333 140.3333,-183 134.6667,-183 134.6667,-183 103.3333,-183 103.3333,-183 97.6667,-183 92,-177.3333 92,-171.6667 92,-171.6667 92,-160.3333 92,-160.3333 92,-154.6667 97.6667,-149 103.3333,-149"/>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="145,-184 89,-184 89,-148 145,-148 145,-184"/>
+<text text-anchor="start" x="110.6646" y="-162.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">s1</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M101.3333,-149C101.3333,-149 132.6667,-149 132.6667,-149 138.3333,-149 144,-154.6667 144,-160.3333 144,-160.3333 144,-171.6667 144,-171.6667 144,-177.3333 138.3333,-183 132.6667,-183 132.6667,-183 101.3333,-183 101.3333,-183 95.6667,-183 90,-177.3333 90,-171.6667 90,-171.6667 90,-160.3333 90,-160.3333 90,-154.6667 95.6667,-149 101.3333,-149"/>
 </g>
 <!-- __initial&#45;&gt;_s1 -->
 <g id="edge1" class="edge">
 <title>__initial&#45;&gt;_s1</title>
-<path fill="none" stroke="#000000" d="M119,-211.9886C119,-207.6293 119,-201.1793 119,-194.4801"/>
-<polygon fill="#000000" stroke="#000000" points="122.5001,-194.0122 119,-184.0122 115.5001,-194.0122 122.5001,-194.0122"/>
-<text text-anchor="middle" x="120.3895" y="-195" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+<path fill="none" stroke="#000000" d="M117,-211.9886C117,-207.6293 117,-201.1793 117,-194.4801"/>
+<polygon fill="#000000" stroke="#000000" points="120.5001,-194.0122 117,-184.0122 113.5001,-194.0122 120.5001,-194.0122"/>
+<text text-anchor="middle" x="118.3895" y="-195" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
 </g>
 <!-- _s2 -->
 <g id="node3" class="node">
@@ -40,25 +40,25 @@
 <!-- _s1&#45;&gt;_s2 -->
 <g id="edge2" class="edge">
 <title>_s1&#45;&gt;_s2</title>
-<path fill="none" stroke="#000000" d="M90.7748,-162.2424C77.0482,-158.8397 61.7231,-152.3556 53.267,-140 51.3275,-137.1661 50.0763,-133.9622 49.3288,-130.6129"/>
-<polygon fill="#000000" stroke="#000000" points="52.7883,-130.0018 48.4124,-120.3529 45.816,-130.6246 52.7883,-130.0018"/>
-<text text-anchor="start" x="54" y="-131" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(IntLiteral(i=100)) &#160;&#160;</text>
+<path fill="none" stroke="#000000" d="M100.1848,-147.8711C94.4188,-141.6547 87.7966,-134.5151 81.372,-127.5886"/>
+<polygon fill="#000000" stroke="#000000" points="83.8947,-125.1616 74.5281,-120.21 78.7625,-129.9219 83.8947,-125.1616"/>
+<text text-anchor="start" x="93" y="-131" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(100) &#160;&#160;</text>
 </g>
 <!-- _s3 -->
 <g id="node4" class="node">
 <title>_s3</title>
-<polygon fill="transparent" stroke="transparent" stroke-width="2" points="238,-120 132,-120 132,-74 238,-74 238,-120"/>
-<text text-anchor="start" x="178.6646" y="-103.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">s3</text>
-<text text-anchor="start" x="137.5022" y="-83.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">onentry/ ^out.in_3</text>
-<polygon fill="#000000" stroke="#000000" points="132,-97 132,-97 238,-97 238,-97 132,-97"/>
-<path fill="none" stroke="#000000" stroke-width="2" d="M145,-75C145,-75 225,-75 225,-75 231,-75 237,-81 237,-87 237,-87 237,-107 237,-107 237,-113 231,-119 225,-119 225,-119 145,-119 145,-119 139,-119 133,-113 133,-107 133,-107 133,-87 133,-87 133,-81 139,-75 145,-75"/>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="234,-120 128,-120 128,-74 234,-74 234,-120"/>
+<text text-anchor="start" x="174.6646" y="-103.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">s3</text>
+<text text-anchor="start" x="133.5022" y="-83.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">onentry/ ^out.in_3</text>
+<polygon fill="#000000" stroke="#000000" points="128,-97 128,-97 234,-97 234,-97 128,-97"/>
+<path fill="none" stroke="#000000" stroke-width="2" d="M141,-75C141,-75 221,-75 221,-75 227,-75 233,-81 233,-87 233,-87 233,-107 233,-107 233,-113 227,-119 221,-119 221,-119 141,-119 141,-119 135,-119 129,-113 129,-107 129,-107 129,-87 129,-87 129,-81 135,-75 141,-75"/>
 </g>
 <!-- _s1&#45;&gt;_s3 -->
 <g id="edge3" class="edge">
 <title>_s1&#45;&gt;_s3</title>
-<path fill="none" stroke="#000000" d="M147.36,-149.8944C151.5102,-146.8958 155.5437,-143.5707 159,-140 162.2272,-136.6661 165.2356,-132.8614 167.977,-128.9254"/>
-<polygon fill="#000000" stroke="#000000" points="171.0974,-130.5422 173.5329,-120.2311 165.1989,-126.7729 171.0974,-130.5422"/>
-<text text-anchor="start" x="168" y="-131" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(IntLiteral(i=200)) &#160;&#160;</text>
+<path fill="none" stroke="#000000" d="M141.4695,-147.9993C144.4564,-145.4372 147.376,-142.7385 150,-140 153.4025,-136.4491 156.7352,-132.5071 159.8791,-128.4919"/>
+<polygon fill="#000000" stroke="#000000" points="162.8542,-130.3572 166.029,-120.2492 157.2437,-126.1712 162.8542,-130.3572"/>
+<text text-anchor="start" x="159" y="-131" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(200) &#160;&#160;</text>
 </g>
 <!-- _s4 -->
 <g id="node5" class="node">
@@ -74,7 +74,7 @@
 <title>_s2&#45;&gt;_s4</title>
 <path fill="none" stroke="#000000" d="M53,-73.9916C53,-68.476 53,-62.474 53,-56.5881"/>
 <polygon fill="#000000" stroke="#000000" points="56.5001,-56.249 53,-46.2491 49.5001,-56.2491 56.5001,-56.249"/>
-<text text-anchor="start" x="53" y="-57" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(IntLiteral(i=150)) &#160;&#160;</text>
+<text text-anchor="start" x="53" y="-57" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(150) &#160;&#160;</text>
 </g>
 </g>
 </svg>

+ 43 - 0
test/test_files/features/after/test_after_fixed_delta.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" ?>
+<test should_fail_load="true">
+  <statechart>
+    <!-- after events are always received as input events in a later big step -->
+    <semantics
+        big_step_maximality="*"
+        combo_step_maximality="*"/>
+    <tree>
+      <state initial="s1">
+        <state id="s1">
+          <transition after="100 us" target="/s2"/>
+          <transition after="200 ms" target="/s3"/>
+        </state>
+        <state id="s2">
+          <onentry>
+            <raise event="in_2" port="out" />
+          </onentry>
+          <transition after="150 h" target="/s4"/>
+        </state>
+        <state id="s3">
+          <onentry>
+            <raise event="in_3" port="out"/>
+          </onentry>
+        </state>
+        <state id="s4">
+          <onentry>
+            <raise event="in_4" port="out"/>
+          </onentry>
+        </state>
+      </state>
+    </tree>
+  </statechart>
+  <delta
+    fixed="10 ms"/>
+  <output>
+    <big_step>
+      <event name="in_2" port="out"/>
+    </big_step>
+    <big_step>
+      <event name="in_4" port="out"/>
+    </big_step>
+  </output>
+</test>

+ 36 - 36
test/test_files/features/after/test_after_reentry.svg

@@ -4,25 +4,25 @@
 <!-- Generated by graphviz version 2.40.1 (20161225.0304)
  -->
 <!-- Title: state transitions Pages: 1 -->
-<svg width="638pt" height="404pt"
- viewBox="0.00 0.00 638.00 404.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<svg width="479pt" height="404pt"
+ viewBox="0.00 0.00 479.00 404.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 400)">
 <title>state transitions</title>
-<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-400 634,-400 634,4 -4,4"/>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-400 475,-400 475,4 -4,4"/>
 <g id="clust1" class="cluster">
 <title>cluster__p</title>
-<path fill="none" stroke="#000000" stroke-width="2" d="M20,-8C20,-8 610,-8 610,-8 616,-8 622,-14 622,-20 622,-20 622,-345 622,-345 622,-351 616,-357 610,-357 610,-357 20,-357 20,-357 14,-357 8,-351 8,-345 8,-345 8,-20 8,-20 8,-14 14,-8 20,-8"/>
-<text text-anchor="start" x="311.6646" y="-338.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">p</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M20,-8C20,-8 451,-8 451,-8 457,-8 463,-14 463,-20 463,-20 463,-345 463,-345 463,-351 457,-357 451,-357 451,-357 20,-357 20,-357 14,-357 8,-351 8,-345 8,-345 8,-20 8,-20 8,-14 14,-8 20,-8"/>
+<text text-anchor="start" x="232.1646" y="-338.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">p</text>
 </g>
 <g id="clust2" class="cluster">
 <title>cluster__p_o0</title>
-<polygon fill="none" stroke="#000000" stroke-dasharray="5,2" points="175,-16 175,-319 614,-319 614,-16 175,-16"/>
-<text text-anchor="start" x="388.3292" y="-300.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">o0</text>
+<polygon fill="none" stroke="#000000" stroke-dasharray="5,2" points="122,-16 122,-319 455,-319 455,-16 122,-16"/>
+<text text-anchor="start" x="282.3292" y="-300.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">o0</text>
 </g>
 <g id="clust3" class="cluster">
 <title>cluster__p_o1</title>
-<polygon fill="none" stroke="#000000" stroke-dasharray="5,2" points="24,-21 24,-319 167,-319 167,-21 24,-21"/>
-<text text-anchor="start" x="89.3292" y="-300.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">o1</text>
+<polygon fill="none" stroke="#000000" stroke-dasharray="5,2" points="24,-21 24,-319 114,-319 114,-21 24,-21"/>
+<text text-anchor="start" x="62.8292" y="-300.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">o1</text>
 </g>
 <!-- __initial -->
 <g id="node1" class="node">
@@ -41,60 +41,60 @@
 <!-- _p_o0_initial -->
 <g id="node4" class="node">
 <title>_p_o0_initial</title>
-<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="454" cy="-275.5" rx="5.5" ry="5.5"/>
+<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="348" cy="-275.5" rx="5.5" ry="5.5"/>
 </g>
 <!-- _p_o0_a -->
 <g id="node5" class="node">
 <title>_p_o0_a</title>
-<polygon fill="transparent" stroke="transparent" stroke-width="2" points="482,-188 426,-188 426,-152 482,-152 482,-188"/>
-<text text-anchor="start" x="450.6646" y="-166.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">a</text>
-<path fill="none" stroke="#000000" stroke-width="2" d="M438.3333,-153C438.3333,-153 469.6667,-153 469.6667,-153 475.3333,-153 481,-158.6667 481,-164.3333 481,-164.3333 481,-175.6667 481,-175.6667 481,-181.3333 475.3333,-187 469.6667,-187 469.6667,-187 438.3333,-187 438.3333,-187 432.6667,-187 427,-181.3333 427,-175.6667 427,-175.6667 427,-164.3333 427,-164.3333 427,-158.6667 432.6667,-153 438.3333,-153"/>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="376,-188 320,-188 320,-152 376,-152 376,-188"/>
+<text text-anchor="start" x="344.6646" y="-166.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">a</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M332.3333,-153C332.3333,-153 363.6667,-153 363.6667,-153 369.3333,-153 375,-158.6667 375,-164.3333 375,-164.3333 375,-175.6667 375,-175.6667 375,-181.3333 369.3333,-187 363.6667,-187 363.6667,-187 332.3333,-187 332.3333,-187 326.6667,-187 321,-181.3333 321,-175.6667 321,-175.6667 321,-164.3333 321,-164.3333 321,-158.6667 326.6667,-153 332.3333,-153"/>
 </g>
 <!-- _p_o0_initial&#45;&gt;_p_o0_a -->
 <g id="edge2" class="edge">
 <title>_p_o0_initial&#45;&gt;_p_o0_a</title>
-<path fill="none" stroke="#000000" d="M454,-269.8288C454,-265.1736 454,-258.4097 454,-252.5 454,-252.5 454,-252.5 454,-205.5 454,-203.1079 454,-200.6252 454,-198.1342"/>
-<polygon fill="#000000" stroke="#000000" points="457.5001,-198.0597 454,-188.0598 450.5001,-198.0598 457.5001,-198.0597"/>
-<text text-anchor="middle" x="455.3895" y="-226" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+<path fill="none" stroke="#000000" d="M348,-269.8288C348,-265.1736 348,-258.4097 348,-252.5 348,-252.5 348,-252.5 348,-205.5 348,-203.1079 348,-200.6252 348,-198.1342"/>
+<polygon fill="#000000" stroke="#000000" points="351.5001,-198.0597 348,-188.0598 344.5001,-198.0598 351.5001,-198.0597"/>
+<text text-anchor="middle" x="349.3895" y="-226" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
 </g>
 <!-- _p_o0_b -->
 <g id="node6" class="node">
 <title>_p_o0_b</title>
-<polygon fill="transparent" stroke="transparent" stroke-width="2" points="358,-70 252,-70 252,-24 358,-24 358,-70"/>
-<text text-anchor="start" x="301.6646" y="-53.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">b</text>
-<text text-anchor="start" x="257.5022" y="-33.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">onentry/ ^out.in_b</text>
-<polygon fill="#000000" stroke="#000000" points="252,-47 252,-47 358,-47 358,-47 252,-47"/>
-<path fill="none" stroke="#000000" stroke-width="2" d="M265,-25C265,-25 345,-25 345,-25 351,-25 357,-31 357,-37 357,-37 357,-57 357,-57 357,-63 351,-69 345,-69 345,-69 265,-69 265,-69 259,-69 253,-63 253,-57 253,-57 253,-37 253,-37 253,-31 259,-25 265,-25"/>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="278,-70 172,-70 172,-24 278,-24 278,-70"/>
+<text text-anchor="start" x="221.6646" y="-53.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">b</text>
+<text text-anchor="start" x="177.5022" y="-33.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">onentry/ ^out.in_b</text>
+<polygon fill="#000000" stroke="#000000" points="172,-47 172,-47 278,-47 278,-47 172,-47"/>
+<path fill="none" stroke="#000000" stroke-width="2" d="M185,-25C185,-25 265,-25 265,-25 271,-25 277,-31 277,-37 277,-37 277,-57 277,-57 277,-63 271,-69 265,-69 265,-69 185,-69 185,-69 179,-69 173,-63 173,-57 173,-57 173,-37 173,-37 173,-31 179,-25 185,-25"/>
 </g>
 <!-- _p_o0_a&#45;&gt;_p_o0_b -->
 <g id="edge3" class="edge">
 <title>_p_o0_a&#45;&gt;_p_o0_b</title>
-<path fill="none" stroke="#000000" d="M425.9206,-168.8931C360.8007,-165.9522 205,-156.6061 205,-134.5 205,-134.5 205,-134.5 205,-87.5 205,-77.5959 222.0646,-68.9632 242.0953,-62.2417"/>
-<polygon fill="#000000" stroke="#000000" points="243.2909,-65.5347 251.7793,-59.1945 241.1899,-58.8575 243.2909,-65.5347"/>
-<text text-anchor="start" x="205" y="-108" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(IntLiteral(i=100)) [INSTATE([&quot;/p/o1/x&quot;])] &#160;&#160;</text>
+<path fill="none" stroke="#000000" d="M319.918,-167.7853C265.7222,-163.1318 152,-151.3584 152,-134.5 152,-134.5 152,-134.5 152,-87.5 152,-78.3329 156.4562,-71.2234 163.1666,-65.7143"/>
+<polygon fill="#000000" stroke="#000000" points="165.5053,-68.3661 171.9476,-59.9549 161.6661,-62.5128 165.5053,-68.3661"/>
+<text text-anchor="start" x="152" y="-108" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(100) [INSTATE([&quot;/p/o1/x&quot;])] &#160;&#160;</text>
 </g>
 <!-- _p_o0_c -->
 <g id="node7" class="node">
 <title>_p_o0_c</title>
-<polygon fill="transparent" stroke="transparent" stroke-width="2" points="554,-70 448,-70 448,-24 554,-24 554,-70"/>
-<text text-anchor="start" x="498" y="-53.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">c</text>
-<text text-anchor="start" x="453.8376" y="-33.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">onentry/ ^out.in_c</text>
-<polygon fill="#000000" stroke="#000000" points="448,-47 448,-47 554,-47 554,-47 448,-47"/>
-<path fill="none" stroke="#000000" stroke-width="2" d="M461,-25C461,-25 541,-25 541,-25 547,-25 553,-31 553,-37 553,-37 553,-57 553,-57 553,-63 547,-69 541,-69 541,-69 461,-69 461,-69 455,-69 449,-63 449,-57 449,-57 449,-37 449,-37 449,-31 455,-25 461,-25"/>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="447,-70 341,-70 341,-24 447,-24 447,-70"/>
+<text text-anchor="start" x="391" y="-53.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">c</text>
+<text text-anchor="start" x="346.8376" y="-33.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">onentry/ ^out.in_c</text>
+<polygon fill="#000000" stroke="#000000" points="341,-47 341,-47 447,-47 447,-47 341,-47"/>
+<path fill="none" stroke="#000000" stroke-width="2" d="M354,-25C354,-25 434,-25 434,-25 440,-25 446,-31 446,-37 446,-37 446,-57 446,-57 446,-63 440,-69 434,-69 434,-69 354,-69 354,-69 348,-69 342,-63 342,-57 342,-57 342,-37 342,-37 342,-31 348,-25 354,-25"/>
 </g>
 <!-- _p_o0_a&#45;&gt;_p_o0_c -->
 <g id="edge4" class="edge">
 <title>_p_o0_a&#45;&gt;_p_o0_c</title>
-<path fill="none" stroke="#000000" d="M482.1541,-159.659C492.1638,-154.0322 501,-145.8506 501,-134.5 501,-134.5 501,-134.5 501,-87.5 501,-85.127 501,-82.6757 501,-80.2081"/>
-<polygon fill="#000000" stroke="#000000" points="504.5001,-80.1306 501,-70.1306 497.5001,-80.1306 504.5001,-80.1306"/>
-<text text-anchor="start" x="501" y="-108" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(IntLiteral(i=150)) &#160;&#160;</text>
+<path fill="none" stroke="#000000" d="M376.1541,-159.659C386.1638,-154.0322 395,-145.8506 395,-134.5 395,-134.5 395,-134.5 395,-87.5 395,-85.1262 394.9826,-82.6744 394.9524,-80.2065"/>
+<polygon fill="#000000" stroke="#000000" points="398.4505,-80.0625 394.7681,-70.1282 391.4517,-80.1906 398.4505,-80.0625"/>
+<text text-anchor="start" x="395" y="-108" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(150) &#160;&#160;</text>
 </g>
 <!-- _p_o0_b&#45;&gt;_p_o0_a -->
 <g id="edge5" class="edge">
 <title>_p_o0_b&#45;&gt;_p_o0_a</title>
-<path fill="none" stroke="#000000" d="M358.3716,-56.4955C401.1255,-64.8642 454,-77.28 454,-87.5 454,-134.5 454,-134.5 454,-134.5 454,-136.8921 454,-139.3748 454,-141.8658"/>
-<polygon fill="#000000" stroke="#000000" points="450.5001,-141.9402 454,-151.9402 457.5001,-141.9403 450.5001,-141.9402"/>
-<text text-anchor="middle" x="455.3895" y="-108" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+<path fill="none" stroke="#000000" d="M278.2342,-59.6571C311.4764,-68.3378 348,-79.6594 348,-87.5 348,-134.5 348,-134.5 348,-134.5 348,-136.8921 348,-139.3748 348,-141.8658"/>
+<polygon fill="#000000" stroke="#000000" points="344.5001,-141.9402 348,-151.9402 351.5001,-141.9403 344.5001,-141.9402"/>
+<text text-anchor="middle" x="349.3895" y="-108" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
 </g>
 <!-- _p_o1 -->
 <!-- _p_o1_initial -->
@@ -128,7 +128,7 @@
 <title>_p_o1_x&#45;&gt;_p_o1_y</title>
 <path fill="none" stroke="#000000" d="M55.7033,-151.6741C54.7416,-146.1833 54,-140.1255 54,-134.5 54,-134.5 54,-134.5 54,-87.5 54,-83.4573 54.2962,-79.2119 54.7569,-75.0534"/>
 <polygon fill="#000000" stroke="#000000" points="58.2353,-75.4511 56.1661,-65.0603 51.3039,-74.4736 58.2353,-75.4511"/>
-<text text-anchor="start" x="54" y="-108" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(IntLiteral(i=250)) &#160;&#160;</text>
+<text text-anchor="start" x="54" y="-108" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(250) &#160;&#160;</text>
 </g>
 </g>
 </svg>

+ 80 - 0
test/test_files/features/after/test_after_units.svg

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.40.1 (20161225.0304)
+ -->
+<!-- Title: state transitions Pages: 1 -->
+<svg width="242pt" height="231pt"
+ viewBox="0.00 0.00 242.00 231.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 227)">
+<title>state transitions</title>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-227 238,-227 238,4 -4,4"/>
+<!-- __initial -->
+<g id="node1" class="node">
+<title>__initial</title>
+<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="117" cy="-217.5" rx="5.5" ry="5.5"/>
+</g>
+<!-- _s1 -->
+<g id="node2" class="node">
+<title>_s1</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="145,-184 89,-184 89,-148 145,-148 145,-184"/>
+<text text-anchor="start" x="110.6646" y="-162.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">s1</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M101.3333,-149C101.3333,-149 132.6667,-149 132.6667,-149 138.3333,-149 144,-154.6667 144,-160.3333 144,-160.3333 144,-171.6667 144,-171.6667 144,-177.3333 138.3333,-183 132.6667,-183 132.6667,-183 101.3333,-183 101.3333,-183 95.6667,-183 90,-177.3333 90,-171.6667 90,-171.6667 90,-160.3333 90,-160.3333 90,-154.6667 95.6667,-149 101.3333,-149"/>
+</g>
+<!-- __initial&#45;&gt;_s1 -->
+<g id="edge1" class="edge">
+<title>__initial&#45;&gt;_s1</title>
+<path fill="none" stroke="#000000" d="M117,-211.9886C117,-207.6293 117,-201.1793 117,-194.4801"/>
+<polygon fill="#000000" stroke="#000000" points="120.5001,-194.0122 117,-184.0122 113.5001,-194.0122 120.5001,-194.0122"/>
+<text text-anchor="middle" x="118.3895" y="-195" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+</g>
+<!-- _s2 -->
+<g id="node3" class="node">
+<title>_s2</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="106,-120 0,-120 0,-74 106,-74 106,-120"/>
+<text text-anchor="start" x="46.6646" y="-103.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">s2</text>
+<text text-anchor="start" x="5.5022" y="-83.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">onentry/ ^out.in_2</text>
+<polygon fill="#000000" stroke="#000000" points="0,-97 0,-97 106,-97 106,-97 0,-97"/>
+<path fill="none" stroke="#000000" stroke-width="2" d="M13,-75C13,-75 93,-75 93,-75 99,-75 105,-81 105,-87 105,-87 105,-107 105,-107 105,-113 99,-119 93,-119 93,-119 13,-119 13,-119 7,-119 1,-113 1,-107 1,-107 1,-87 1,-87 1,-81 7,-75 13,-75"/>
+</g>
+<!-- _s1&#45;&gt;_s2 -->
+<g id="edge2" class="edge">
+<title>_s1&#45;&gt;_s2</title>
+<path fill="none" stroke="#000000" d="M96.3763,-147.8814C93.6472,-145.2958 90.9328,-142.6227 88.436,-140 84.8592,-136.2428 81.226,-132.1729 77.717,-128.0861"/>
+<polygon fill="#000000" stroke="#000000" points="80.2789,-125.6942 71.1695,-120.2841 74.9168,-130.1941 80.2789,-125.6942"/>
+<text text-anchor="start" x="88" y="-131" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(100 µs) &#160;&#160;</text>
+</g>
+<!-- _s3 -->
+<g id="node4" class="node">
+<title>_s3</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="234,-120 128,-120 128,-74 234,-74 234,-120"/>
+<text text-anchor="start" x="174.6646" y="-103.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">s3</text>
+<text text-anchor="start" x="133.5022" y="-83.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">onentry/ ^out.in_3</text>
+<polygon fill="#000000" stroke="#000000" points="128,-97 128,-97 234,-97 234,-97 128,-97"/>
+<path fill="none" stroke="#000000" stroke-width="2" d="M141,-75C141,-75 221,-75 221,-75 227,-75 233,-81 233,-87 233,-87 233,-107 233,-107 233,-113 227,-119 221,-119 221,-119 141,-119 141,-119 135,-119 129,-113 129,-107 129,-107 129,-87 129,-87 129,-81 135,-75 141,-75"/>
+</g>
+<!-- _s1&#45;&gt;_s3 -->
+<g id="edge3" class="edge">
+<title>_s1&#45;&gt;_s3</title>
+<path fill="none" stroke="#000000" d="M145.1022,-148.8417C148.6296,-146.1072 152.0347,-143.1369 155,-140 158.1875,-136.6281 161.1728,-132.8012 163.9023,-128.8539"/>
+<polygon fill="#000000" stroke="#000000" points="167.0262,-130.4645 169.4484,-120.1502 161.1228,-126.7027 167.0262,-130.4645"/>
+<text text-anchor="start" x="164" y="-131" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(200 ms) &#160;&#160;</text>
+</g>
+<!-- _s4 -->
+<g id="node5" class="node">
+<title>_s4</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="106,-46 0,-46 0,0 106,0 106,-46"/>
+<text text-anchor="start" x="46.6646" y="-29.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">s4</text>
+<text text-anchor="start" x="5.5022" y="-9.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">onentry/ ^out.in_4</text>
+<polygon fill="#000000" stroke="#000000" points="0,-23 0,-23 106,-23 106,-23 0,-23"/>
+<path fill="none" stroke="#000000" stroke-width="2" d="M13,-1C13,-1 93,-1 93,-1 99,-1 105,-7 105,-13 105,-13 105,-33 105,-33 105,-39 99,-45 93,-45 93,-45 13,-45 13,-45 7,-45 1,-39 1,-33 1,-33 1,-13 1,-13 1,-7 7,-1 13,-1"/>
+</g>
+<!-- _s2&#45;&gt;_s4 -->
+<g id="edge4" class="edge">
+<title>_s2&#45;&gt;_s4</title>
+<path fill="none" stroke="#000000" d="M53,-73.9916C53,-68.476 53,-62.474 53,-56.5881"/>
+<polygon fill="#000000" stroke="#000000" points="56.5001,-56.249 53,-46.2491 49.5001,-56.2491 56.5001,-56.249"/>
+<text text-anchor="start" x="53" y="-57" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">after(150 h) &#160;&#160;</text>
+</g>
+</g>
+</svg>

+ 41 - 0
test/test_files/features/after/test_after_units.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" ?>
+<test>
+  <statechart>
+    <!-- after events are always received as input events in a later big step -->
+    <semantics
+        big_step_maximality="*"
+        combo_step_maximality="*"/>
+    <tree>
+      <state initial="s1">
+        <state id="s1">
+          <transition after="100 us" target="/s2"/>
+          <transition after="200 ms" target="/s3"/>
+        </state>
+        <state id="s2">
+          <onentry>
+            <raise event="in_2" port="out" />
+          </onentry>
+          <transition after="150 h" target="/s4"/>
+        </state>
+        <state id="s3">
+          <onentry>
+            <raise event="in_3" port="out"/>
+          </onentry>
+        </state>
+        <state id="s4">
+          <onentry>
+            <raise event="in_4" port="out"/>
+          </onentry>
+        </state>
+      </state>
+    </tree>
+  </statechart>
+  <output>
+    <big_step>
+      <event name="in_2" port="out"/>
+    </big_step>
+    <big_step>
+      <event name="in_4" port="out"/>
+    </big_step>
+  </output>
+</test>

test/test_files/features/datamodel/test_datamodel.svg → test/test_files/features/datamodel/test_guard_action.svg


test/test_files/features/datamodel/test_datamodel.xml → test/test_files/features/datamodel/test_guard_action.xml


+ 26 - 0
test/test_files/features/datamodel/test_static_types.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" ?>
+<test should_fail_load="true">
+  <statechart>
+    <semantics/>
+
+    <datamodel>
+      <var id="x" expr="5"/>
+    </datamodel>
+
+    <tree>
+      <state initial="a">
+        <state id="a">
+          <transition cond="x == True" target="/b">
+            <!-- illegal assignment, x is type 'int' -->
+            <!-- <code>x = "hello"</code> -->
+          </transition>
+        </state>
+
+        <state id="b"/>
+      </state>
+    </tree>
+  </statechart>
+
+  <output>
+  </output>
+</test>