Просмотр исходного кода

GC memory is read-only. Introduce Python class SCCDRuntimeException for all runtime exceptions. Clean up some things.

Joeri Exelmans 5 лет назад
Родитель
Сommit
ceb4a73f2b

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

@@ -80,8 +80,6 @@ class Controller:
     # If no timestamp is given (now = None), run until event queue is empty.
     def run_until(self, now: Optional[Timestamp], pipe: queue.Queue):
 
-        # unstable: List[Instance] = []
-
         # Helper. Put big step output events in the event queue or add them to the right output listeners.
         def process_big_step_output(events: List[OutputEvent]):
             pipe_events = []
@@ -104,39 +102,21 @@ class Controller:
             # initialize the object manager, in turn initializing our default class
             # and adding the generated events to the queue
             for i in self.object_manager.instances:
-                stable, events = i.initialize(self.simulated_time)
+                events = i.initialize(self.simulated_time)
                 process_big_step_output(events)
-                # if not stable:
-                #     unstable.append(i)
             print_debug("initialized. time is now %s" % str(self.get_simulated_duration()))
 
 
         # Actual "event loop"
-        # TODO: What is are the right semantics for this loop?
-        # Should we stabilize every object after it has made a big step?
-        # Should we only stabilize when there are no more events?
-        # Should we never stabilize?
-        # Should this be a semantic option?
-        # while unstable or self.queue.is_due(now):
-            # 1. Handle events
         for timestamp, entry in self.queue.due(now):
-            # check if there's a time leap
-            if timestamp is not self.simulated_time:
-                # before every "time leap", continue to run instances until they are stable.
-                # if not do_stabilize():
-                    # return
+            if timestamp != self.simulated_time:
                 # make time leap
                 self.simulated_time = timestamp
                 print_debug("\ntime is now %s" % str(self.get_simulated_duration()))
             # run all instances for whom there are events
             for instance in entry.targets:
-                stable, output = instance.big_step(timestamp, [entry.event])
+                output = instance.big_step(timestamp, [entry.event])
                 # print_debug("completed big step (time = %s)" % str(self.model.globals.delta * self.simulated_time))
                 process_big_step_output(output)
-                # if not stable:
-                    # unstable.append(instance)
-            # 2. No more due events -> stabilize
-            # if not do_stabilize():
-                # return
 
         self.simulated_time = now

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

@@ -25,11 +25,11 @@ class ObjectManager(Instance):
         self.instances.append(i)
         return i
 
-    def initialize(self, now: Timestamp) -> Tuple[bool, List[OutputEvent]]:
-        return True, []
+    def initialize(self, now: Timestamp) -> List[OutputEvent]:
+        return []
 
     # Implementation of super class: Instance
-    def big_step(self, timestamp: Timestamp, input_events: List[Event]) -> Tuple[bool, List[OutputEvent]]:
+    def big_step(self, timestamp: Timestamp, input_events: List[Event]) -> List[OutputEvent]:
         output = []
         for e in input_events:
             try:
@@ -40,7 +40,7 @@ class ObjectManager(Instance):
                     output.extend(o)
             except KeyError:
                 pass
-        return True, output
+        return output
 
     # def _assoc_ref(self, input_string) -> List[Tuple[str,int]]:
     #     if len(input_string) == 0:

+ 3 - 0
src/sccd/execution/exceptions.py

@@ -0,0 +1,3 @@
+
+class SCCDRuntimeException(Exception):
+  pass

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

@@ -6,9 +6,9 @@ from sccd.execution.timestamp import *
 # Interface for all instances and also the Object Manager
 class Instance(ABC):
     @abstractmethod
-    def initialize(self, timestamp: Timestamp) -> Tuple[bool, List[OutputEvent]]:
+    def initialize(self, timestamp: Timestamp) -> List[OutputEvent]:
         pass
 
     @abstractmethod
-    def big_step(self, timestamp: Timestamp, input_events: List[Event]) -> Tuple[bool, List[OutputEvent]]:
+    def big_step(self, timestamp: Timestamp, input_events: List[Event]) -> List[OutputEvent]:
         pass

+ 26 - 7
src/sccd/execution/memory.py

@@ -3,6 +3,7 @@ from dataclasses import *
 from sccd.util.bitmap import *
 from sccd.util.debug import *
 from sccd.syntax.scope import *
+from sccd.execution.exceptions import *
 
 @dataclass(frozen=True)
 class StackFrame:
@@ -55,6 +56,13 @@ class MemoryInterface(ABC):
   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):
 
@@ -97,11 +105,19 @@ class Memory(MemoryInterface):
     frame, offset = self._get_frame(offset)
     frame.storage[offset] = value
 
+  def flush_transition(self, read_only: bool = False):
+    pass
+
+  def flush_round(self):
+    pass
 
 class MemoryPartialSnapshot(MemoryInterface):
 
-  def __init__(self, memory: Memory):
+  def __init__(self, description: str, memory: Memory, read_only: bool = False):
+    self.description = description
     self.memory = memory
+    self.read_only = read_only
+
     self.frame = memory.current_frame()
 
     self.actual: List[Any] = self.frame.storage
@@ -142,21 +158,24 @@ class MemoryPartialSnapshot(MemoryInterface):
 
   def store(self, offset: int, value: Any):
     frame, offset = self.memory._get_frame(offset)
-    # Always write to 'actual' storage
-    frame.storage[offset] = value
-
     if frame is self.frame:
+      if self.read_only:
+        raise SCCDRuntimeException("Attempt to write to read-only %s memory." % self.description)
       # "our" frame! :)
       # Remember that we wrote, such that next read during same transition will be the value we wrote.
       self.trans_dirty |= bit(offset)
 
-  def flush_transition(self):  
+    # Always write to 'actual' storage
+    frame.storage[offset] = value
+
+
+  def flush_transition(self, read_only: bool = False):
     race_conditions = self.trans_dirty & self.round_dirty
     if race_conditions:
       variables = self.frame.scope.variables
       # some variable written to twice before refresh
-      raise Exception("Race condition for variables %s" %
-          ", ".join(variables[offset].name for offset in race_conditions.items()))
+      raise SCCDRuntimeException("Race condition in %s memory: More than one transition assigned a new value to variables: %s" %
+          (self.description, ", ".join(variables[offset].name for offset in race_conditions.items())))
 
     self.round_dirty |= self.trans_dirty
     self.trans_dirty = Bitmap() # reset

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

@@ -3,6 +3,7 @@ from sccd.execution.event import *
 from sccd.util.bitmap import *
 from sccd.syntax.tree import *
 from sccd.util.debug import *
+from sccd.execution.exceptions import *
 
 class CandidatesGenerator:
     def __init__(self, reverse: bool):
@@ -66,8 +67,8 @@ class Round(ABC):
     def when_done(self, callback):
         self.callbacks.append(callback)
 
-    def run(self, forbidden_arenas: Bitmap = Bitmap()) -> RoundResult:
-        changed, stable = self._internal_run(forbidden_arenas)
+    def run_and_cycle_events(self, forbidden_arenas: Bitmap = Bitmap()) -> RoundResult:
+        changed, stable = self._run(forbidden_arenas)
         if changed:
             # notify round observers
             for callback in self.callbacks:
@@ -79,7 +80,7 @@ class Round(ABC):
         return (changed, stable)
 
     @abstractmethod
-    def _internal_run(self, forbidden_arenas: Bitmap) -> RoundResult:
+    def _run(self, forbidden_arenas: Bitmap) -> RoundResult:
         pass
 
     def add_remainder_event(self, event: Event):
@@ -129,13 +130,13 @@ class SuperRound(Round):
     def __repr__(self):
         return self.name + " > " + self.subround.__repr__()
 
-    def _internal_run(self, forbidden_arenas: Bitmap) -> RoundResult:
+    def _run(self, forbidden_arenas: Bitmap) -> RoundResult:
         arenas_changed = Bitmap()
         arenas_stabilized = Bitmap()
 
         while True:
             forbidden = self.maximality.forbidden_arenas(forbidden_arenas, arenas_changed, arenas_stabilized)
-            changed, stabilized = self.subround.run(forbidden) # no forbidden arenas in subround
+            changed, stabilized = self.subround.run_and_cycle_events(forbidden) # no forbidden arenas in subround
             if not changed:
                 break # no more transitions could be executed, done!
 
@@ -151,20 +152,20 @@ class SuperRoundWithLimit(SuperRound):
         super().__init__(name, subround, maximality)
         self.limit = limit
 
-    def _internal_run(self, forbidden_arenas: Bitmap) -> RoundResult:
+    def _run(self, forbidden_arenas: Bitmap) -> RoundResult:
         arenas_changed = Bitmap()
         arenas_stabilized = Bitmap()
 
         subrounds = 0
         while True:
             forbidden = self.maximality.forbidden_arenas(forbidden_arenas, arenas_changed, arenas_stabilized)
-            changed, stabilized = self.subround.run(forbidden) # no forbidden arenas in subround
+            changed, stabilized = self.subround.run_and_cycle_events(forbidden) # no forbidden arenas in subround
             if not changed:
                 break # no more transitions could be executed, done!
 
             subrounds += 1
             if subrounds >= self.limit:
-                raise Exception("%s: Limit reached! (%d×%s) Possibly a never-ending big step." % (self.name, subrounds, self.subround.name))
+                raise SCCDRuntimeException("%s: Limit reached! (%d×%s) Possibly a never-ending big step." % (self.name, subrounds, self.subround.name))
 
             arenas_changed |= changed
             arenas_stabilized |= stabilized
@@ -179,7 +180,7 @@ class SmallStep(Round):
         self.generator = generator
         self.concurrency = concurrency
 
-    def _internal_run(self, forbidden_arenas: Bitmap) -> RoundResult:
+    def _run(self, forbidden_arenas: Bitmap) -> RoundResult:
         enabled_events = None
         def get_candidates(extra_forbidden):
             nonlocal enabled_events

+ 27 - 37
src/sccd/execution/statechart_instance.py

@@ -25,9 +25,12 @@ class StatechartInstance(Instance):
 
         reverse = semantics.priority == Priority.SOURCE_CHILD
 
+        # 2 transition candidate generation algorithms to choose from!
         generator = CandidatesGeneratorCurrentConfigBased(reverse)
         # generator = CandidatesGeneratorEventBased(reverse)
 
+        # Big step + combo step maximality semantics
+
         small_step = SmallStep(termcolor.colored("small", 'blue'), None, generator,
             concurrency=semantics.concurrency==Concurrency.MANY)
 
@@ -59,6 +62,8 @@ class StatechartInstance(Instance):
         else:
             raise Exception("Unsupported option: %s" % semantics.big_step_maximality)
 
+        # Event lifeline semantics
+
         def whole(input):
             self._big_step.remainder_events = input
 
@@ -84,61 +89,46 @@ class StatechartInstance(Instance):
             InternalEventLifeline.SAME: small_step.add_remainder_event,
         }[semantics.internal_event_lifeline]
 
+        # Memory protocol semantics
+
         memory = Memory()
         load_builtins(memory)
         memory.push_frame(statechart.scope)
 
-        if semantics.enabledness_memory_protocol == MemoryProtocol.NONE:
-            gc_memory = memory
-        else:
-            gc_memory = MemoryPartialSnapshot(memory)
+        rhs_memory = MemoryPartialSnapshot("RHS", memory)
 
-            if semantics.enabledness_memory_protocol == MemoryProtocol.BIG_STEP:
-                self._big_step.when_done(gc_memory.flush_round)
-            elif semantics.enabledness_memory_protocol == MemoryProtocol.COMBO_STEP:
-                combo_step.when_done(gc_memory.flush_round)
-            elif semantics.enabledness_memory_protocol == MemoryProtocol.SMALL_STEP:
-                small_step.when_done(gc_memory.flush_round)
+        if semantics.assignment_memory_protocol == MemoryProtocol.BIG_STEP:
+            self._big_step.when_done(rhs_memory.flush_round)
+        elif semantics.enabledness_memory_protocol == MemoryProtocol.COMBO_STEP:
+            combo_step.when_done(rhs_memory.flush_round)
+        elif semantics.enabledness_memory_protocol == MemoryProtocol.SMALL_STEP:
+            small_step.when_done(rhs_memory.flush_round)
 
+        gc_memory = MemoryPartialSnapshot("GC", memory, read_only=True)
 
-        if semantics.assignment_memory_protocol == semantics.enabledness_memory_protocol:
-            rhs_memory = gc_memory
-        else:
-            rhs_memory = MemoryPartialSnapshot(memory)
-
-            if semantics.assignment_memory_protocol == MemoryProtocol.BIG_STEP:
-                self._big_step.when_done(rhs_memory.flush_round)
-            elif semantics.assignment_memory_protocol == MemoryProtocol.COMBO_STEP:
-                combo_step.when_done(rhs_memory.flush_round)
-            elif semantics.assignment_memory_protocol == MemoryProtocol.SMALL_STEP:
-                small_step.when_done(rhs_memory.flush_round)
+        if semantics.assignment_memory_protocol == MemoryProtocol.BIG_STEP:
+            self._big_step.when_done(gc_memory.flush_round)
+        elif semantics.assignment_memory_protocol == MemoryProtocol.COMBO_STEP:
+            combo_step.when_done(gc_memory.flush_round)
+        elif semantics.assignment_memory_protocol == MemoryProtocol.SMALL_STEP:
+            small_step.when_done(gc_memory.flush_round)
 
         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
 
 
     # enter default states, generating a set of output events
-    def initialize(self, now: Timestamp) -> Tuple[bool, List[OutputEvent]]:
+    def initialize(self, now: Timestamp) -> List[OutputEvent]:
         self.state.initialize()
-        stable, output = self.state.collect_output()
-
-        return (stable, output)
+        return self.state.collect_output()
 
     # perform a big step. generating a set of output events
-    def big_step(self, now: Timestamp, input_events: List[Event]) -> Tuple[bool, List[OutputEvent]]:
-
+    def big_step(self, now: Timestamp, input_events: List[Event]) -> List[OutputEvent]:
         # print_debug('attempting big step, input_events='+str(input_events))
-
         self.set_input(input_events)
-
-        arenas_changed = self._big_step.run()
-
-        stable, output = self.state.collect_output()
-
-        # can the next big step still contain transitions, even if there are no input events?
-        stable |= not input_events and not arenas_changed
-
-        return (stable, output)
+        self._big_step.run_and_cycle_events()
+        return self.state.collect_output()

+ 89 - 83
src/sccd/execution/statechart_state.py

@@ -4,6 +4,7 @@ from sccd.execution.event import *
 from sccd.util.debug import print_debug
 from sccd.util.bitmap import *
 from sccd.syntax.scope import *
+from sccd.execution.exceptions import *
 
 
 # Set of current states etc.
@@ -53,73 +54,78 @@ class StatechartState:
 
         self.rhs_memory.flush_transition()
         self.rhs_memory.flush_round()
+        self.gc_memory.flush_round()
 
     # events: list SORTED by event id
     def fire_transition(self, events: List[Event], t: Transition):
-        def __exitSet():
-            return [s for s in reversed(t.gen.lca.gen.descendants) if (s in self.configuration)]
-        
-        def __enterSet(targets):
-            target = targets[0]
-            for a in reversed(target.gen.ancestors):
-                if a in t.source.gen.ancestors:
-                    continue
-                else:
-                    yield a
-            for target in targets:
-                yield target
-
-        def __getEffectiveTargetStates():
-            targets = []
-            for target in t.targets:
-                for e_t in target.getEffectiveTargetStates(self):
-                    if not e_t in targets:
-                        targets.append(e_t)
-            return targets
-
-        # exit states...
-        exit_set = __exitSet()
-        for s in exit_set:
-            # remember which state(s) we were in if a history state is present
-            for h in s.gen.history:
-                f = lambda s0: s0.gen.ancestors and s0.parent == s
-                if isinstance(h, DeepHistoryState):
-                    f = lambda s0: not s0.gen.descendants and s0 in s.gen.descendants
-                self.history_values[h.gen.state_id] = list(filter(f, self.configuration))
-
-        ctx = EvalContext(current_state=self, events=events, memory=self.rhs_memory)
-
-        print_debug("fire " + str(t))
-        for s in exit_set:
-            print_debug(termcolor.colored('  EXIT %s' % s.gen.full_name, 'green'))
-            self.eventless_states -= s.gen.has_eventless_transitions
-            # execute exit action(s)
-            self._perform_actions(ctx, s.exit)
-            # self.rhs_memory.pop_local_scope(s.scope)
-            self.configuration_bitmap &= ~s.gen.state_id_bitmap
-                
-        # execute transition action(s)
-        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)
-        self._perform_actions(ctx, t.actions)
-        self.rhs_memory.pop_frame()
-            
-        # enter states...
-        targets = __getEffectiveTargetStates()
-        enter_set = __enterSet(targets)
-        for s in enter_set:
-            print_debug(termcolor.colored('  ENTER %s' % s.gen.full_name, 'green'))
-            self.eventless_states += s.gen.has_eventless_transitions
-            self.configuration_bitmap |= s.gen.state_id_bitmap
-            # execute enter action(s)
-            self._perform_actions(ctx, s.enter)
-            self._start_timers(s.gen.after_triggers)
         try:
-            self.configuration = self.config_mem[self.configuration_bitmap]
-        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]
+            def __exitSet():
+                return [s for s in reversed(t.gen.lca.gen.descendants) if (s in self.configuration)]
+            
+            def __enterSet(targets):
+                target = targets[0]
+                for a in reversed(target.gen.ancestors):
+                    if a in t.source.gen.ancestors:
+                        continue
+                    else:
+                        yield a
+                for target in targets:
+                    yield target
+
+            def __getEffectiveTargetStates():
+                targets = []
+                for target in t.targets:
+                    for e_t in target.getEffectiveTargetStates(self):
+                        if not e_t in targets:
+                            targets.append(e_t)
+                return targets
+
+            # exit states...
+            exit_set = __exitSet()
+            for s in exit_set:
+                # remember which state(s) we were in if a history state is present
+                for h in s.gen.history:
+                    f = lambda s0: s0.gen.ancestors and s0.parent == s
+                    if isinstance(h, DeepHistoryState):
+                        f = lambda s0: not s0.gen.descendants and s0 in s.gen.descendants
+                    self.history_values[h.gen.state_id] = list(filter(f, self.configuration))
+
+            ctx = EvalContext(current_state=self, events=events, memory=self.rhs_memory)
+
+            print_debug("fire " + str(t))
+            for s in exit_set:
+                print_debug(termcolor.colored('  EXIT %s' % s.gen.full_name, 'green'))
+                self.eventless_states -= s.gen.has_eventless_transitions
+                # execute exit action(s)
+                self._perform_actions(ctx, s.exit)
+                # self.rhs_memory.pop_local_scope(s.scope)
+                self.configuration_bitmap &= ~s.gen.state_id_bitmap
+                    
+            # execute transition action(s)
+            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)
+            self._perform_actions(ctx, t.actions)
+            self.rhs_memory.pop_frame()
+                
+            # enter states...
+            targets = __getEffectiveTargetStates()
+            enter_set = __enterSet(targets)
+            for s in enter_set:
+                print_debug(termcolor.colored('  ENTER %s' % s.gen.full_name, 'green'))
+                self.eventless_states += s.gen.has_eventless_transitions
+                self.configuration_bitmap |= s.gen.state_id_bitmap
+                # execute enter action(s)
+                self._perform_actions(ctx, s.enter)
+                self._start_timers(s.gen.after_triggers)
+            try:
+                self.configuration = self.config_mem[self.configuration_bitmap]
+            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.rhs_memory.flush_transition()
+            self.rhs_memory.flush_transition()
+        except SCCDRuntimeException as e:
+            e.args = ("During execution of transition %s:\n" % str(t) +str(e),)
+            raise
 
     @staticmethod
     def _copy_event_params_to_stack(memory, t, events):
@@ -143,24 +149,25 @@ class StatechartState:
                 pass
 
     def check_guard(self, t, events) -> bool:
-        # Special case: after trigger
-        if isinstance(t.trigger, AfterTrigger):
-            e = [e for e in events if bit(e.id) & t.trigger.enabling_bitmap][0] # it's safe to assume the list will contain one element cause we only check a transition's guard after we know it may be enabled given the set of events
-            if self.timer_ids[t.trigger.after_id] != e.params[0]:
-                return False
-
-        if t.guard is None:
-            return True
-        else:
-            # print("evaluating guard for ", str(t))
-            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))
-            self.gc_memory.pop_frame()
-            self.gc_memory.flush_transition()
-            # print("done with guard for ", str(t))
-            return result
+        try:
+            # Special case: after trigger
+            if isinstance(t.trigger, AfterTrigger):
+                e = [e for e in events if bit(e.id) & t.trigger.enabling_bitmap][0] # it's safe to assume the list will contain one element cause we only check a transition's guard after we know it may be enabled given the set of events
+                if self.timer_ids[t.trigger.after_id] != e.params[0]:
+                    return False
+
+            if t.guard is None:
+                return True
+            else:
+                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))
+                self.gc_memory.pop_frame()
+                return result
+        except SCCDRuntimeException as e:
+            e.args = ("While checking guard of transition %s:\n" % str(t) +str(e),)
+            raise
 
     def check_source(self, t) -> bool:
         return self.configuration_bitmap & t.source.gen.state_id_bitmap
@@ -174,7 +181,6 @@ class StatechartState:
         for after in triggers:
             delay: Duration = after.delay.eval(
                 EvalContext(current_state=self, events=[], memory=self.gc_memory))
-            self.gc_memory.flush_transition()
             timer_id = self._next_timer_id(after)
             self.output.append(OutputEvent(
                 Event(id=after.id, name=after.name, params=[timer_id]),
@@ -195,7 +201,7 @@ class StatechartState:
         #     print_debug("not in state"+str(state_strings))
         return in_state
 
-    def collect_output(self) -> Tuple[bool, List[OutputEvent]]:
+    def collect_output(self) -> List[OutputEvent]:
         output = self.output
         self.output = []
-        return (not self.eventless_states, output)
+        return output

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

@@ -36,7 +36,7 @@ class MemoryProtocol(SemanticAspect, Enum):
   BIG_STEP = auto()
   COMBO_STEP = auto()
   SMALL_STEP = auto()
-  NONE = auto()
+  # NONE = auto()
 
 class Priority(SemanticAspect, Enum):
   SOURCE_PARENT = auto()
@@ -73,7 +73,7 @@ class SemanticConfiguration:
         return True
     return False
 
-  # Get all possible combinations for aspects with multiple options set.
+  # Get all possible combinations for aspects with multiple options (as a list) set.
   # Calling has_multiple_variants on resulting objects will return False.
   def generate_variants(self) -> List['SemanticConfiguration']:
     my_fields = fields(self)

+ 13 - 3
test/test_files/features/datamodel/test_cond.xml

@@ -4,18 +4,28 @@
     <datamodel>
       x = 42;
     </datamodel>
+
+    <inport name="in">
+      <event name="start"/>
+    </inport>
+
+    <outport name="out">
+      <event name="done"/>
+    </outport>
+
     <root initial="start">
       <state id="start">
-        <transition event="e" port="in" target="/done" cond="x == 42">
-          <raise event="done" port="out"/>
+        <transition target="/done" cond="x == 42">
+          <raise event="done"/>
         </transition>
       </state>
+
       <state id="done"/>
     </root>
   </statechart>
 
   <input>
-    <event name="e" port="in" time="0 d"/>
+    <event name="start" port="in" time="0 d"/>
   </input>
 
   <output>