Переглянути джерело

Abstract syntax: Basic states modeled as empty And-states. History states modeled as pseudo-states.

Joeri Exelmans 5 роки тому
батько
коміт
a53cf88893

+ 6 - 6
src/sccd/statechart/cmd/render.py

@@ -68,7 +68,7 @@ if __name__ == '__main__':
         w = IndentingWriter(f)
 
         def name_to_label(state):
-          label = state.opt.full_name.split('/')[-1]
+          label = state.full_name.split('/')[-1]
           if state.stable:
             label += " ✓"
           return label if len(label) else "root"
@@ -96,11 +96,11 @@ if __name__ == '__main__':
 
         def write_state(s, hide=False):
           if not hide:
-            w.writeln(name_to_name(s.opt.full_name))
+            w.writeln(name_to_name(s.full_name))
             w.write(' [label="')
             w.write(name_to_label(s))
             w.write('"')
-            if isinstance(s, ParallelState):
+            if isinstance(s.type, AndState):
               w.write(' type=parallel')
             elif isinstance(s, ShallowHistoryState):
               w.write(' type=history')
@@ -121,8 +121,8 @@ if __name__ == '__main__':
               w.write(' {')
               w.indent()
             if s.default_state:
-              w.writeln(name_to_name(s.opt.full_name)+'_initial [type=initial],')
-              transitions.append(PseudoTransition(source=PseudoState(s.opt.full_name+'/initial'), target=s.default_state))
+              w.writeln(name_to_name(s.full_name)+'_initial [type=initial],')
+              transitions.append(PseudoTransition(source=PseudoState(s.full_name+'/initial'), target=s.default_state))
             s.children.reverse() # this appears to put orthogonal components in the right order :)
             for i, c in enumerate(s.children):
               write_state(c)
@@ -144,7 +144,7 @@ if __name__ == '__main__':
           if t.actions:
             label += ' '.join(a.render() for a in t.actions)
 
-          w.writeln(name_to_name(t.source.opt.full_name) + ' -> ' + name_to_name(t.target.opt.full_name))
+          w.writeln(name_to_name(t.source.full_name) + ' -> ' + name_to_name(t.target.full_name))
           if label:
             w.write(': '+label)
           w.write(';')

+ 44 - 58
src/sccd/statechart/codegen/rust.py

@@ -8,16 +8,16 @@ from sccd.util.indenting_writer import *
 # Conversion functions from abstract syntax elements to identifiers in Rust
 
 def snake_case(state: State) -> str:
-    return state.opt.full_name.replace('/', '_');
+    return state.full_name.replace('/', '_');
 
 def ident_var(state: State) -> str:
-    if state.opt.full_name == "/":
+    if state.full_name == "/":
         return "root" # no technical reason, it's just clearer than "s_"
     else:
         return "s" + snake_case(state)
 
 def ident_type(state: State) -> str:
-    if state.opt.full_name == "/":
+    if state.full_name == "/":
         return "Root" # no technical reason, it's just clearer than "State_"
     else:
         return "State" + snake_case(state)
@@ -35,16 +35,16 @@ def ident_source_target(state: State) -> str:
     return snake_case(state)[1:]
 
 def ident_transition(t: Transition) -> str:
-    return "transition%d_FROM_%s_TO_%s" % (t.opt.id, ident_source_target(t.source), ident_source_target(t.target))
+    return "transition%d_FROM_%s_TO_%s" % (t.id, ident_source_target(t.source), ident_source_target(t.target))
 
 def ident_arena_label(state: State) -> str:
-    if state.opt.full_name == "/":
+    if state.full_name == "/":
         return "arena_root"
     else:
         return "arena" + snake_case(state)
 
 def ident_arena_const(state: State) -> str:
-    if state.opt.full_name == "/":
+    if state.full_name == "/":
         return "ARENA_ROOT"
     else:
         return "ARENA" + snake_case(state)
@@ -103,26 +103,12 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                     w.writeln("  %s(%s)," % (ident_enum_variant(child), ident_type(child)))
             w.writeln("}")
 
-        if isinstance(state, ParallelState):
+        if isinstance(state.type, AndState):
             w.writeln("// And-state")
             as_struct()
-        elif isinstance(state, State):
-            if len(state.children) > 0:
-                w.writeln("// Or-state")
-                as_enum() # Or-state
-            else:
-                # Basic state: write as empty struct
-                #
-                # An empty struct in Rust is a type with one possible value.
-                # An empty struct is a Zero-Sized Type.
-                #
-                # An empty enum is also a valid type in Rust, but no instances
-                # of it can be created. Also called an "uninhabited type".
-                w.writeln("// Basic state")
-                as_struct()
-
-            # The above if-else construction hints at the fact that we would have
-            # better used empty And-states to model basic states, instead of empty Or-states...
+        elif isinstance(state.type, OrState):
+            w.writeln("// Or-state")
+            as_enum()
 
         w.writeln()
         return state
@@ -140,16 +126,16 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
         w.writeln("impl Default for %s {" % ident_type(state))
         w.writeln("  fn default() -> Self {")
 
-        if isinstance(state, ParallelState):
+        if isinstance(state.type, AndState):
             w.writeln("    Self {")
             for child in children:
                 if child is not None:
                     w.writeln("      %s: Default::default()," % (ident_field(child)))
             w.writeln("    }")
-        elif isinstance(state, State):
-            if state.default_state is not None:
+        elif isinstance(state.type, OrState):
+            if state.type.default_state is not None:
                 # Or-state
-                w.writeln("    Self::%s(Default::default())" % (ident_enum_variant(state.default_state)))
+                w.writeln("    Self::%s(Default::default())" % (ident_enum_variant(state.type.default_state)))
             else:
                 # Basic state
                 w.writeln("    Self{}")
@@ -168,14 +154,14 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
         w.writeln("impl<'a, OutputCallback: FnMut(&'a str, &'a str)> State<OutputCallback> for %s {" % ident_type(state))
 
         w.writeln("  fn enter_actions(%s: &mut OutputCallback) {" % IDENT_OC)
-        w.writeln("    println!(\"enter %s\");" % state.opt.full_name);
+        w.writeln("    println!(\"enter %s\");" % state.full_name);
         w.indent(); w.indent()
         compile_actions(state.enter, w)
         w.dedent(); w.dedent()
         w.writeln("  }")
 
         w.writeln("  fn exit_actions(%s: &mut OutputCallback) {" % IDENT_OC)
-        w.writeln("    println!(\"exit %s\");" % state.opt.full_name);
+        w.writeln("    println!(\"exit %s\");" % state.full_name);
         w.indent(); w.indent()
         compile_actions(state.exit, w)
         w.dedent(); w.dedent()
@@ -183,18 +169,18 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
 
         w.writeln("  fn enter_default(%s: &mut OutputCallback) {" % IDENT_OC)
         w.writeln("    %s::enter_actions(%s);" % (ident_type(state), IDENT_OC))
-        if isinstance(state, ParallelState):
+        if isinstance(state.type, AndState):
             for child in children:
                 if child is not None:
                     w.writeln("    %s::enter_default(%s);" % (ident_type(child), IDENT_OC))
         else:
-            if state.default_state is not None:
-                w.writeln("    %s::enter_default(%s);" % (ident_type(state.default_state), IDENT_OC))
+            if state.type.default_state is not None:
+                w.writeln("    %s::enter_default(%s);" % (ident_type(state.type.default_state), IDENT_OC))
         w.writeln("  }")
 
         w.writeln("  fn exit_current(&self, %s: &mut OutputCallback) {" % IDENT_OC)
         # Children's exit actions
-        if isinstance(state, ParallelState):
+        if isinstance(state.type, AndState):
             for child in children:
                 if child is not None:
                     w.writeln("    self.%s.exit_current(%s);" % (ident_field(child), IDENT_OC))
@@ -213,7 +199,7 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
         # Children's enter actions
         w.writeln("    %s::enter_actions(%s);" % (ident_type(state), IDENT_OC))
         # Our own enter actions
-        if isinstance(state, ParallelState):
+        if isinstance(state.type, AndState):
             for child in children:
                 if child is not None:
                     w.writeln("    self.%s.enter_current(%s);" % (ident_field(child), IDENT_OC))
@@ -250,7 +236,7 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
     # Write arena type
     arenas = set()
     for t in tree.transition_list:
-        arenas.add(t.opt.arena)
+        arenas.add(t.arena)
     w.writeln("// Arenas (bitmap type)")
     for size, typ in [(8, 'u8'), (16, 'u16'), (32, 'u32'), (64, 'u64'), (128, 'u128')]:
         if len(arenas) + 1 <= size:
@@ -334,14 +320,14 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                     w.writeln("%s.exit_current(%s);" % (ident_var(s), IDENT_OC))
                 else:
                     # Exit children:
-                    if isinstance(s, ParallelState):
+                    if isinstance(s.type, AndState):
                         for c in reversed(s.children):
                             if exit_path[1] is c:
                                 write_exit(exit_path[1:]) # continue recursively
                             else:
                                 w.writeln("%s.exit_current(%s);" % (ident_var(c), IDENT_OC))
-                    elif isinstance(s, State):
-                        if s.default_state is not None:
+                    elif isinstance(s.type, OrState):
+                        if s.type.default_state is not None:
                             # Or-state
                             write_exit(exit_path[1:]) # continue recursively with the next child on the exit path
 
@@ -349,12 +335,12 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                     w.writeln("%s::exit_actions(%s);" % (ident_type(s), IDENT_OC))
 
                 # Store history
-                if s.opt.deep_history:
-                    _, _, h = s.opt.deep_history
+                if s.deep_history:
+                    _, _, h = s.deep_history
                     w.writeln("sc.%s = *%s; // Store deep history" % (ident_history_field(h), ident_var(s)))
-                if s.opt.shallow_history:
-                    _, h = s.opt.shallow_history
-                    if isinstance(s, ParallelState):
+                if s.shallow_history:
+                    _, h = s.shallow_history
+                    if isinstance(s.type, AndState):
                         raise Exception("Shallow history makes no sense for And-state!")
                     w.writeln("sc.%s = match %s { // Store shallow history" % (ident_history_field(h), ident_var(s)))
                     for c in s.children:
@@ -378,7 +364,7 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                     # Enter s:
                     w.writeln("%s::enter_actions(%s);" % (ident_type(s), IDENT_OC))
                     # Enter children:
-                    if isinstance(s, ParallelState):
+                    if isinstance(s.type, AndState):
                         for c in s.children:
                             if enter_path[1] is c:
                                 # if not isinstance(c, HistoryState):
@@ -386,7 +372,7 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                                 write_enter(enter_path[1:]) # continue recursively
                             else:
                                 w.writeln("%s::enter_default(%s);" % (ident_type(c), IDENT_OC))
-                    elif isinstance(s, State):
+                    elif isinstance(s.type, OrState):
                         if len(s.children) > 0:
                             write_enter(enter_path[1:]) # continue recursively with the next child on the enter path
                         else:
@@ -409,7 +395,7 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                         # No recursion
                         w.writeln("let new_%s = sc.%s; // Restore history value" % (ident_var(s), ident_history_field(next_child)))
                     else:
-                        if isinstance(s, ParallelState):
+                        if isinstance(s.type, AndState):
                             for c in s.children:
                                 if next_child is c:
                                     write_new_configuration(enter_path[1:]) # recurse
@@ -418,7 +404,7 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                                     w.writeln("let new_%s: %s = Default::default();" % (ident_var(c), ident_type(c)))
                             # Construct struct
                             w.writeln("let new_%s = %s{%s:new_%s, ..Default::default()};" % (ident_var(s), ident_type(s), ident_field(next_child), ident_var(next_child)))
-                        elif isinstance(s, State):
+                        elif isinstance(s.type, OrState):
                             if len(s.children) > 0:
                                 # Or-state
                                 write_new_configuration(enter_path[1:]) # recurse
@@ -444,11 +430,11 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                 # 1. Execute transition's actions
 
                 # Path from arena to source, including source but not including arena
-                exit_path_bm = t.opt.arena.opt.descendants & (t.source.opt.state_id_bitmap | t.source.opt.ancestors) # bitmap
+                exit_path_bm = t.arena.descendants & (t.source.state_id_bitmap | t.source.ancestors) # bitmap
                 exit_path = list(tree.bitmap_to_states(exit_path_bm)) # list of states
 
                 # Path from arena to target, including target but not including arena
-                enter_path_bm = t.opt.arena.opt.descendants & (t.target.opt.state_id_bitmap | t.target.opt.ancestors) # bitmap
+                enter_path_bm = t.arena.descendants & (t.target.state_id_bitmap | t.target.ancestors) # bitmap
                 enter_path = list(tree.bitmap_to_states(enter_path_bm)) # list of states
 
                 w.writeln("println!(\"fire %s\");" % str(t))
@@ -467,18 +453,18 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
 
                 # A state configuration is just a value
                 w.writeln("// Build new state configuration")
-                write_new_configuration([t.opt.arena] + enter_path)
+                write_new_configuration([t.arena] + enter_path)
 
                 w.writeln("// Update arena configuration")
-                w.writeln("*%s = new_%s;" % (ident_var(t.opt.arena), ident_var(t.opt.arena)))
+                w.writeln("*%s = new_%s;" % (ident_var(t.arena), ident_var(t.arena)))
 
                 if not syntactic_maximality or t.target.stable:
-                    w.writeln("fired |= %s; // Stable target" % ident_arena_const(t.opt.arena))
+                    w.writeln("fired |= %s; // Stable target" % ident_arena_const(t.arena))
                 else:
                     w.writeln("fired |= ARENA_FIRED; // Unstable target")
 
                 # This arena is done:
-                w.writeln("break '%s;" % (ident_arena_label(t.opt.arena)))
+                w.writeln("break '%s;" % (ident_arena_label(t.arena)))
 
                 if t.trigger is not EMPTY_TRIGGER:
                     w.dedent()
@@ -486,14 +472,14 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
 
         def child():
             # Here is were we recurse and write the transition code for the children of our 'state'.
-            if isinstance(state, ParallelState):
+            if isinstance(state.type, AndState):
                 for child in state.children:
                     if not isinstance(child, HistoryState):
                         w.writeln("// Orthogonal region")
                         w.writeln("let %s = &mut %s.%s;" % (ident_var(child), ident_var(state), ident_field(child)))
                         write_transitions(child)
-            elif isinstance(state, State):
-                if state.default_state is not None:
+            elif isinstance(state.type, OrState):
+                if state.type.default_state is not None:
                     if syntactic_maximality and state in arenas:
                         w.writeln("if dirty & %s == 0 {" % ident_arena_const(state))
                         w.indent()
@@ -611,7 +597,7 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
         w.writeln("  println!(\"info: Event: {} bytes\", size_of::<Event>());")
         w.writeln("  println!(\"info: Arenas: {} bytes\", size_of::<Arenas>());")
         def write_state_size(state):
-            w.writeln("  println!(\"info: %s: {} bytes\", size_of::<%s>());" % (state.opt.full_name, ident_type(state)))
+            w.writeln("  println!(\"info: %s: {} bytes\", size_of::<%s>());" % (state.full_name, ident_type(state)))
             for child in state.children:
                 if not isinstance(child, HistoryState):
                     write_state_size(child)

+ 8 - 8
src/sccd/statechart/dynamic/candidate_generator.py

@@ -54,8 +54,8 @@ class CurrentConfigStrategy(GeneratorStrategy):
 
     def generate(self, execution, events_bitmap, forbidden_arenas):
         return [ t for t in self.priority_ordered_transitions
-                      if (t.source.opt.state_id_bitmap & execution.configuration)
-                       and (not forbidden_arenas & t.opt.arena_bitmap) ]
+                      if (t.source.state_id_bitmap & execution.configuration)
+                       and (not forbidden_arenas & t.arena_bitmap) ]
 
     def filter_f(self, execution, enabled_events, events_bitmap):
         return lambda t: (not t.trigger or t.trigger.check(events_bitmap)) and execution.check_guard(t, enabled_events)
@@ -81,10 +81,10 @@ class EnabledEventsStrategy(GeneratorStrategy):
     def generate(self, execution, events_bitmap, forbidden_arenas):
         return [ t for t in self.priority_ordered_transitions
                       if (not t.trigger or t.trigger.check(events_bitmap))
-                      and (not forbidden_arenas & t.opt.arena_bitmap) ]
+                      and (not forbidden_arenas & t.arena_bitmap) ]
 
     def filter_f(self, execution, enabled_events, events_bitmap):
-        return lambda t: (execution.configuration & t.source.opt.state_id_bitmap) and execution.check_guard(t, enabled_events)
+        return lambda t: (execution.configuration & t.source.state_id_bitmap) and execution.check_guard(t, enabled_events)
 
 class CurrentConfigAndEnabledEventsStrategy(GeneratorStrategy):
     __slots__ = ["statechart"]
@@ -101,8 +101,8 @@ class CurrentConfigAndEnabledEventsStrategy(GeneratorStrategy):
     def generate(self, execution, events_bitmap, forbidden_arenas):
         return [ t for t in self.priority_ordered_transitions
                       if (not t.trigger or t.trigger.check(events_bitmap))
-                      and (t.source.opt.state_id_bitmap & execution.configuration)
-                      and (not forbidden_arenas & t.opt.arena_bitmap) ]
+                      and (t.source.state_id_bitmap & execution.configuration)
+                      and (not forbidden_arenas & t.arena_bitmap) ]
 
     def filter_f(self, execution, enabled_events, events_bitmap):
         return lambda t: execution.check_guard(t, enabled_events)
@@ -170,6 +170,6 @@ class ConcurrentCandidateGenerator:
                 else:
                     break
                 if self.synchronous:
-                    events_bitmap |= t.opt.raised_events
-                forbidden_arenas |= t.opt.arena_bitmap
+                    events_bitmap |= t.raised_events
+                forbidden_arenas |= t.arena_bitmap
             return transitions

+ 2 - 2
src/sccd/statechart/dynamic/round.py

@@ -137,10 +137,10 @@ class SmallStep(Round):
         stable_arenas = Bitmap()
 
         for t in transitions:
-            arena = t.opt.arena_bitmap
+            arena = t.arena_bitmap
             self.execution.fire_transition(enabled_events, t)
             dirty_arenas |= arena
-            if t.opt.target_stable:
+            if t.target_stable:
                 stable_arenas |= arena
             enabled_events = self.enabled_events()
             enabled_events.sort(key=lambda e: e.id)

+ 18 - 18
src/sccd/statechart/dynamic/statechart_execution.py

@@ -38,9 +38,9 @@ class StatechartExecution:
             self.statechart.datamodel.exec(self.rhs_memory)
 
         for state in self.statechart.tree.bitmap_to_states(self.configuration):
-            print_debug(termcolor.colored('  ENTER %s'%state.opt.full_name, 'green'))
+            print_debug(termcolor.colored('  ENTER %s'%state.full_name, 'green'))
             _perform_actions(ctx, state.enter)
-            self._start_timers(state.opt.after_triggers)
+            self._start_timers(state.after_triggers)
 
         self.rhs_memory.flush_transition()
         self.rhs_memory.flush_round()
@@ -52,14 +52,14 @@ class StatechartExecution:
             with timer.Context("transition"):
                 # Sequence of exit states is the intersection between set of current states and the arena's descendants.
                 with timer.Context("exit set"):
-                    exit_ids = self.configuration & t.opt.exit_mask
+                    exit_ids = self.configuration & t.exit_mask
                     exit_set = self.statechart.tree.bitmap_to_states_reverse(exit_ids)
 
                 with timer.Context("enter set"):
                     # Sequence of enter states is more complex but has for a large part already been computed statically.
-                    enter_ids = t.opt.enter_states_static
-                    if t.opt.target_history_id is not None:
-                        enter_ids |= self.history_values[t.opt.target_history_id]
+                    enter_ids = t.enter_states_static
+                    if t.target_history_id is not None:
+                        enter_ids |= self.history_values[t.target_history_id]
                     enter_set = self.statechart.tree.bitmap_to_states(enter_ids)
 
                 ctx = EvalContext(execution=self, events=events, memory=self.rhs_memory)
@@ -69,17 +69,17 @@ class StatechartExecution:
                 with timer.Context("exit states"):
                     just_exited = None
                     for s in exit_set:
-                        print_debug(termcolor.colored('  EXIT %s' % s.opt.full_name, 'green'))
-                        if s.opt.deep_history is not None:
+                        print_debug(termcolor.colored('  EXIT %s' % s.full_name, 'green'))
+                        if s.deep_history is not None:
                             # s has a deep-history child:
-                            history_id, history_mask, _ = s.opt.deep_history
+                            history_id, history_mask, _ = s.deep_history
                             self.history_values[history_id] = exit_ids & history_mask
-                        if s.opt.shallow_history is not None:
-                            history_id, _ = s.opt.shallow_history
-                            self.history_values[history_id] = just_exited.opt.effective_targets
-                        self._cancel_timers(s.opt.after_triggers)
+                        if s.shallow_history is not None:
+                            history_id, _ = s.shallow_history
+                            self.history_values[history_id] = just_exited.effective_targets
+                        self._cancel_timers(s.after_triggers)
                         _perform_actions(ctx, s.exit)
-                        self.configuration &= ~s.opt.state_id_bitmap
+                        self.configuration &= ~s.state_id_bitmap
                         just_exited = s
 
                 # execute transition action(s)
@@ -92,10 +92,10 @@ class StatechartExecution:
 
                 with timer.Context("enter states"):
                     for s in enter_set:
-                        print_debug(termcolor.colored('  ENTER %s' % s.opt.full_name, 'green'))
-                        self.configuration |= s.opt.state_id_bitmap
+                        print_debug(termcolor.colored('  ENTER %s' % s.full_name, 'green'))
+                        self.configuration |= s.state_id_bitmap
                         _perform_actions(ctx, s.enter)
-                        self._start_timers(s.opt.after_triggers)
+                        self._start_timers(s.after_triggers)
 
                 self.rhs_memory.flush_transition()
 
@@ -136,7 +136,7 @@ class StatechartExecution:
     # Return whether the current configuration includes ALL the states given.
     def in_state(self, state_strings: List[str]) -> bool:
         try:
-            state_ids_bitmap = bm_union(self.statechart.tree.state_dict[s].opt.state_id_bitmap for s in state_strings)
+            state_ids_bitmap = bm_union(self.statechart.tree.state_dict[s].state_id_bitmap for s in state_strings)
         except KeyError as e:
             raise ModelRuntimeError("INSTATE argument %s: invalid state" % str(e)) from e
         in_state = bm_has_all(self.configuration, state_ids_bitmap)

+ 19 - 10
src/sccd/statechart/parser/xml.py

@@ -126,14 +126,16 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
 
         return {"raise": parse_raise, "code": parse_code}
 
-      def deal_with_initial(el, state, children_dict):
+      def get_default_state(el, state, children_dict):
         have_initial = False
 
         def parse_attr_initial(initial):
+          nonlocal default_state
           nonlocal have_initial
+          default_state = None
           have_initial = True
           try:
-            state.default_state = children_dict[initial]
+            default_state = children_dict[initial]
           except KeyError as e:
             raise XmlError("Not a child.") from e
 
@@ -141,10 +143,12 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
 
         if not have_initial:
           if len(state.children) == 1:
-            state.default_state = state.children[0]
-          elif len(state.children) > 1:
+            default_state = state.children[0]
+          else:
             raise XmlError("More than 1 child state: must set 'initial' attribute.")
 
+        return default_state
+
       def state_child_rules(parent, sibling_dict: Dict[str, State]):
 
         def common(el, constructor):
@@ -168,19 +172,24 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
           state = common_nonpseudo(el, State)
           children_dict = {}
           def finish_state():
-            deal_with_initial(el, state, children_dict)
+            if len(state.children) > 0:
+              state.type = OrState(state=state,
+                default_state=get_default_state(el, state, children_dict))
+            else:
+              state.type = AndState(state=state)
           return (state_child_rules(parent=state, sibling_dict=children_dict), finish_state)
 
         def parse_parallel(el):
-          state = common_nonpseudo(el, ParallelState)
+          state = common_nonpseudo(el, State)
+          state.type = AndState(state=state)
           return state_child_rules(parent=state, sibling_dict={})
 
         def parse_history(el):
           history_type = el.get("type", "shallow")
           if history_type == "deep":
-            common(el, DeepHistoryState)
+            state = common(el, DeepHistoryState)
           elif history_type == "shallow":
-            common(el, ShallowHistoryState)
+            state = common(el, ShallowHistoryState)
           else:
             raise XmlError("attribute 'type' must be \"shallow\" or \"deep\".")
 
@@ -200,7 +209,7 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
 
           scope = Scope("event_params", parent=statechart.scope)
           target_string = require_attribute(el, "target")
-          transition = Transition(source=parent, target=None, scope=scope, target_string=target_string)
+          transition = Transition(source=parent, target_string=target_string, scope=scope)
 
           have_event_attr = False
           def parse_attr_event(event):
@@ -265,7 +274,7 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
         return {"state": parse_state, "parallel": parse_parallel, "history": parse_history, "onentry": parse_onentry, "onexit": parse_onexit, "transition": parse_transition}
 
       def finish_root():
-        deal_with_initial(el, root, children_dict)
+        root.type = OrState(state=root, default_state=get_default_state(el, root, children_dict))
 
         for transition, t_el in transitions:
           try:

+ 3 - 3
src/sccd/statechart/static/concurrency.py

@@ -13,7 +13,7 @@ class NoConcurrency(SmallStepConsistency):
 
 class ArenaOrthogonal(SmallStepConsistency):
     def can_fire_together(self, t1: Transition, t2: Transition) -> bool:
-        return not (t1.opt.arena_bitmap & t2.opt.arena_bitmap) # nonoverlapping arenas
+        return not (t1.arena_bitmap & t2.arena_bitmap) # nonoverlapping arenas
 
 class SrcDstOrthogonal(SmallStepConsistency):
     def __init__(self, tree: StateTree):
@@ -22,7 +22,7 @@ class SrcDstOrthogonal(SmallStepConsistency):
     def can_fire_together(self, t1: Transition, t2: Transition) -> bool:
         source_lca = self.tree.lca(t1.source, t2.source)
         target_lca = self.tree.lca(t1.target, t2.target)
-        return isinstance(source_lca, ParallelState) and isinstance(target_lca, ParallelState)
+        return isinstance(source_lca.type, AndState) and isinstance(target_lca.type, AndState)
 
 # Raises an exception if the given set of transitions can potentially be enabled simulatenously, wrt. their source states in the state tree.
 def check_nondeterminism(tree: StateTree, transitions: Iterable[Transition], consistency: SmallStepConsistency):
@@ -36,7 +36,7 @@ def check_nondeterminism(tree: StateTree, transitions: Iterable[Transition], con
             raise ModelStaticError("Nondeterminism! No priority between outgoing transitions of same state: %s, %s" % (t1, t2))
         # Their sources are orthogonal to each other:
         lca = tree.lca(t1.source, t2.source)
-        if isinstance(lca, ParallelState):
+        if isinstance(lca.type, AndState):
             raise ModelStaticError("Nondeterminism! No priority between orthogonal transitions: %s, %s" % (t1, t2))
         # Their source states are ancestors of one another:
         if is_ancestor(t1.source, t2.source) or is_ancestor(t2.source, t1.source):

+ 11 - 11
src/sccd/statechart/static/priority.py

@@ -1,4 +1,4 @@
-from sccd.statechart.static.tree import StateTree, Transition, State, ParallelState
+from sccd.statechart.static.tree import StateTree, Transition, State, AndState, OrState
 from sccd.statechart.static.concurrency import check_nondeterminism, SmallStepConsistency
 from sccd.statechart.static.semantic_configuration import *
 from sccd.util.graph import strongly_connected_components
@@ -23,14 +23,14 @@ def explicit_ortho(tree: StateTree) -> EdgeList:
         transitions = []
         def visit_state(s: State, _=None):
             transitions.extend(s.transitions)
-        visit_tree(s, lambda s: s.children, parent_first=[visit_state])
+        visit_tree(s, lambda s: s.real_children, parent_first=[visit_state])
         return transitions
     # create edges between transitions in one region to another
     def visit_parallel_state(s: State, _=None):
-        if isinstance(s, ParallelState):
+        if isinstance(s.type, AndState):
             prev = []
-            # s.children are the orthogonal regions in document order
-            for region in s.children:
+            # s.real_children are the orthogonal regions in document order
+            for region in s.real_children:
                 curr = get_transitions(region)
                 if len(curr) > 0: # skip empty regions
                     # instead of creating edges between all transitions in component 'prev' and all transitions in component 'curr' (|prev| x |curr| edges), we add a pseudo-vertex in the graph between them, so we only have to create |prev| + |curr| edges, expressing the same information.
@@ -39,7 +39,7 @@ def explicit_ortho(tree: StateTree) -> EdgeList:
                         edges.extend((t, connector) for t in prev)
                         edges.extend((connector, t) for t in curr)
                     prev = curr
-    visit_tree(tree.root, lambda s: s.children,
+    visit_tree(tree.root, lambda s: s.real_children,
         parent_first=[visit_parallel_state])
     return edges
 
@@ -53,7 +53,7 @@ def explicit_same_state(tree: StateTree) -> EdgeList:
             if prev is not None:
                 edges.append((prev, t))
             prev = t
-    visit_tree(tree.root, lambda s: s.children,
+    visit_tree(tree.root, lambda s: s.real_children,
         parent_first=[visit_state])
     return edges
 
@@ -65,7 +65,7 @@ def source_parent(tree: StateTree) -> EdgeList:
             edges.extend(itertools.product(parent_transitions, s.transitions))
             return s.transitions
         return parent_transitions
-    visit_tree(tree.root, lambda s: s.children, parent_first=[visit_state])
+    visit_tree(tree.root, lambda s: s.real_children, parent_first=[visit_state])
     return edges
 
 # hierarchical Source-Child ordering
@@ -78,7 +78,7 @@ def source_child(tree: StateTree) -> EdgeList:
             return s.transitions
         else:
             return children_transitions
-    visit_tree(tree.root, lambda s: s.children, child_first=[visit_state])
+    visit_tree(tree.root, lambda s: s.real_children, child_first=[visit_state])
     return edges
 
 # hierarchical Arena-Parent ordering
@@ -86,7 +86,7 @@ def arena_parent(tree: StateTree) -> EdgeList:
     edges: EdgeList = []
     partitions = collections.defaultdict(list) # mapping of transition's arena depth to list of transitions
     for t in tree.transition_list:
-        partitions[t.opt.arena.opt.depth].append(t)
+        partitions[t.arena.depth].append(t)
     ordered_partitions = sorted(partitions.items(), key=lambda tup: tup[0])
     prev = []
     for depth, curr in ordered_partitions:
@@ -99,7 +99,7 @@ def arena_child(tree: StateTree) -> EdgeList:
     edges: EdgeList = []
     partitions = collections.defaultdict(list) # mapping of transition's arena depth to list of transitions
     for t in tree.transition_list:
-        partitions[t.opt.arena.opt.depth].append(t)
+        partitions[t.arena.depth].append(t)
     ordered_partitions = sorted(partitions.items(), key=lambda tup: -tup[0])
     prev = []
     for depth, curr in ordered_partitions:

+ 182 - 160
src/sccd/statechart/static/tree.py

@@ -6,82 +6,138 @@ from sccd.util.bitmap import *
 from sccd.util import timer
 from sccd.util.visit_tree import *
 from sccd.util.freezable import *
+from abc import *
 
-class State(Freezable):
-    __slots__ = ["short_name", "parent", "stable", "children", "default_state", "transitions", "enter", "exit", "opt"]
+@dataclass(eq=False)
+class AbstractState:
+    short_name: str # value of 'id' attribute in XML
+    parent: Optional['AbstractState'] # only None if root state
+    children: List['AbstractState'] = field(default_factory=list)
 
-    def __init__(self, short_name: str, parent: Optional['State']):
-        super().__init__()
 
-        self.short_name: str = short_name # value of 'id' attribute in XML
-        self.parent: Optional['State'] = parent # only None if root state
+    ####### Calculated values below ########
 
-        self.stable: bool = False # whether this is a stable stabe. this field is ignored if maximality semantics is not set to SYNTACTIC
+    state_id: int = -1
+    state_id_bitmap: Bitmap = Bitmap() # bitmap with only state_id-bit set
+    full_name: str = ""
+    ancestors: Bitmap = Bitmap()
+    descendants: Bitmap = Bitmap()
 
-        self.children: List['State'] = []
-        self.default_state: 'State' = None # child state pointed to by 'initial' attribute
+    effective_targets: Bitmap = Bitmap()
 
-        self.transitions: List['Transition'] = []
+    def __post_init__(self):
+        if self.parent is not None:
+            self.parent.children.append(self)
 
-        self.enter: List[Action] = []
-        self.exit: List[Action] = []
+    def __str__(self):
+        return "AbstractState(%s)" % self.short_name
 
-        # Statically computed stuff from tree structure:
-        self.opt: Optional['StateStatic'] = None
+    __repr__ = __str__
 
-        if self.parent is not None:
-            self.parent.children.append(self)
+@dataclass(eq=False)
+class State(AbstractState):
+    type: 'StateType' = None
 
-    # Subset of descendants that are always entered when this state is the target of a transition, minus history states. Recursive implementation, so better to use self.opt.effective_targets, which is computed statically
-    def effective_targets(self) -> List['State']:
-        if self.default_state:
-            # or-state: this state + recursion on 'default state'
-            return [self] + self.default_state.effective_targets()
-        else:
-            # basic state
-            return [self]
+    real_children: List['State'] = field(default_factory=list) # children, but not including pseudo-states such as history
 
-    # States that are always entered when this state is part of the "enter path", but not the actual target of a transition.
-    def additional_effective_targets(self, exclude: 'State') -> List['State']:
-        return [self]
+    # whether this is a stable stabe. this field is ignored if maximality semantics is not set to SYNTACTIC
+    stable: bool = False
 
-    def __repr__(self):
-        return "State(\"%s\")" % (self.short_name)
+    # Outgoing transitions
+    transitions: List['Transition'] = field(default_factory=list)
 
-class HistoryState(State):
-    __slots__ = ["history_id"]
+    enter: List[Action] = field(default_factory=list)
+    exit: List[Action] = field(default_factory=list)
 
-    def __init__(self, short_name: str, parent: Optional['State']):
-        super().__init__(short_name, parent)
+    ####### Calculated values below ########
 
-    def effective_targets(self) -> List['State']:
-        return []
+    depth: int = -1 # Root is 0, root's children are 1, and so on
 
-    def additional_effective_targets(self, exclude: 'State') -> List['State']:
-        assert False # history state cannot have children and therefore should never occur in a "enter path"
+    # If a direct child of this state is a deep history state, then "deep history" needs to be recorded when exiting this state. This value contains a tuple, with the (history-id, history_mask, history state) of that child state.
+    deep_history: Optional[Tuple[int, Bitmap, 'DeepHistoryState']] = None
+
+    # If a direct child of this state is a shallow history state, then "shallow history" needs to be recorded when exiting this state. This value is the history-id of that child state
+    shallow_history: Optional[Tuple[int, 'ShallowHistoryState']] = None
+
+    # Triggers of outgoing transitions that are AfterTrigger.
+    after_triggers: List['AfterTrigger'] = field(default_factory=list)
+
+    def __post_init__(self):
+        super().__post_init__()
+        if self.parent is not None:
+            self.parent.real_children.append(self)
+
+    def __str__(self):
+        if isinstance(self.type, AndState):
+            return "AndState(%s)" % self.short_name
+        elif isinstance(self.type, OrState):
+            return "OrState(%s)" % self.short_name
+        else:
+            return "State?(%s)" % self.short_name
 
+    __repr__ = __str__
+
+
+@dataclass(eq=False)
+class HistoryState(AbstractState):
+    history_id: int = -1
+
+    def __post_init__(self):
+        super().__post_init__()
+        self.type = HistoryStateType(self)
+
+@dataclass(eq=False)
 class ShallowHistoryState(HistoryState):
+    def __str__(self):
+        return "ShallowHistoryState(%s)" % self.short_name
+    __repr__ = __str__
 
-    def __repr__(self):
-        return "ShallowHistoryState(\"%s\")" % (self.short_name)
 
+@dataclass(eq=False)
 class DeepHistoryState(HistoryState):
+    def __str__(self):
+        return "DeepHistoryState(%s)" % self.short_name
+    __repr__ = __str__
+
+@dataclass(eq=False)
+class StateType(ABC):
+    state: State
 
-    def __repr__(self):
-        return "DeepHistoryState(\"%s\")" % (self.short_name)
+    @abstractmethod
+    def effective_targets(self):
+        pass
+
+    @abstractmethod
+    def additional_effective_targets(self, exclude: 'State'):
+        pass
 
-class ParallelState(State):
+@dataclass(eq=False)
+class AndState(StateType):
 
-    def effective_targets(self) -> List['State']:
-        # this state + recursive on all children that are not a history state
-        return self.additional_effective_targets(exclude=None)
+    def effective_targets(self):
+        return self.additional_effective_targets(None)
 
-    def additional_effective_targets(self, exclude: 'State') -> List['State']:
-        # this state + all effective targets of children, except for 'excluded state' and history states
-        return [self] + list(itertools.chain(*(c.effective_targets() for c in self.children if not isinstance(c, HistoryState) and c is not exclude)))
+    def additional_effective_targets(self, exclude: 'State'):
+        return [self.state] + list(itertools.chain(*(c.type.effective_targets() for c in self.state.children if not isinstance(c, HistoryState) and c is not exclude)))
 
-    def __repr__(self):
-        return "ParallelState(\"%s\")" % (self.short_name)
+@dataclass(eq=False)
+class OrState(StateType):
+    default_state: AbstractState
+
+    def effective_targets(self):
+        return [self.state] + self.default_state.type.effective_targets()
+
+    def additional_effective_targets(self, exclude: 'State'):
+        return [self.state]
+
+@dataclass(eq=False)
+class HistoryStateType(StateType):
+
+    def effective_targets(self):
+        return []
+
+    def additional_effective_targets(self, exclude: 'State'):
+        assert False # history state cannot have children and therefore should never occur in a "enter path"
 
 @dataclass
 class EventDecl:
@@ -171,79 +227,40 @@ class AfterTrigger(Trigger):
 
 EMPTY_TRIGGER = Trigger(enabling=[])
 
-class Transition(Freezable):
-    __slots__ = ["source", "target", "scope", "target_string", "guard", "actions", "trigger", "opt"]
-
-    def __init__(self, source: State, target: State, scope: Scope, target_string: Optional[str] = None):
-        super().__init__()
+@dataclass(eq=False)
+class Transition:
+    source: State
+    target_string: Optional[str]
+    scope: Scope
 
-        self.source: State = source
-        self.target: State = target
-        self.scope: Scope = scope
+    target: State = None
 
-        self.target_string: Optional[str] = target_string
+    guard: Optional[Expression] = None
+    actions: List[Action] = field(default_factory=list)
+    trigger: Trigger = EMPTY_TRIGGER
 
-        self.guard: Optional[Expression] = None
-        self.actions: List[Action] = []
-        self.trigger: Trigger = EMPTY_TRIGGER
+    ####### CALCULATED VALUES ########
 
-        # Statically computed stuff from tree structure:
-        self.opt: Optional['TransitionStatic'] = None        
+    id: int = None # just a unique number, >= 0
+    exit_mask: State = None
+    arena: State = None
+    arena_bitmap: Bitmap = None
+        # The "enter set" can be computed partially statically, or entirely statically if there are no history states in it.
+    enter_states_static: Bitmap = None
+    target_history_id: Optional[int] = None # History ID if target of transition is a history state, otherwise None.
+    target_stable: bool = None # Whether target state is a stable state
+    raised_events: Bitmap = None # (internal) event IDs raised by this transition
 
     def __str__(self):
-        return termcolor.colored("%s -> %s" % (self.source.opt.full_name, self.target.opt.full_name), 'green')
+        if self.target is None:
+            return "Transition(%s -> %s)" % (self.source.short_name, self.target_string)
+        else:
+            return termcolor.colored("%s -> %s" % (self.source.full_name, self.target.full_name), 'green')
 
     __repr__ = __str__
 
 
-# Simply a collection of read-only fields, generated during "optimization" for each state, inferred from the model, i.e. the hierarchy of states and transitions
-class StateStatic(Freezable):
-    __slots__ = ["full_name", "depth", "state_id", "state_id_bitmap", "ancestors", "descendants", "effective_targets", "deep_history", "shallow_history", "after_triggers"]
-    def __init__(self):
-        super().__init__()
-
-        self.full_name: str = ""
-
-        self.depth: int -1 # Root is 0, root's children are 1, and so on
-        self.state_id: int = -1
-        self.state_id_bitmap: Bitmap = Bitmap() # bitmap with only state_id-bit set
-
-        self.ancestors: Bitmap = Bitmap()
-        self.descendants: Bitmap = Bitmap()
-
-        self.effective_targets: Bitmap = Bitmap()
-
-        # If a direct child of this state is a deep history state, then "deep history" needs to be recorded when exiting this state. This value contains a tuple, with the (history-id, history_mask, history state) of that child state.
-        self.deep_history: Optional[Tuple[int, Bitmap, DeepHistoryState]] = None
-
-        # If a direct child of this state is a shallow history state, then "shallow history" needs to be recorded when exiting this state. This value is the history-id of that child state
-        self.shallow_history: Optional[Tuple[int, ShallowHistoryState]] = None
-
-        # Triggers of outgoing transitions that are AfterTrigger.
-        self.after_triggers: List[AfterTrigger] = []
-
-
-# Data that is generated for each transition.
-class TransitionStatic(Freezable):
-    __slots__ = ["id", "exit_mask", "arena", "arena_bitmap", "enter_states_static", "target_history_id", "target_stable", "raised_events"]
-
-    def __init__(self, id: int, exit_mask: Bitmap, arena: State, arena_bitmap: Bitmap, enter_states_static: Bitmap, target_history_id: Optional[int], target_stable: bool, raised_events: Bitmap):
-        super().__init__()
-        self.id: int = id # just a unique number, >= 0
-        self.exit_mask: State = exit_mask
-        self.arena: State = arena
-        self.arena_bitmap: Bitmap = arena_bitmap
-        # The "enter set" can be computed partially statically, or entirely statically if there are no history states in it.
-        self.enter_states_static: Bitmap = enter_states_static
-        self.target_history_id: Optional[int] = target_history_id # History ID if target of transition is a history state, otherwise None.
-        self.target_stable: bool = target_stable # Whether target state is a stable state
-        self.raised_events: Bitmap = raised_events # (internal) event IDs raised by this transition
-        self.freeze()
-
-
-class StateTree(Freezable):
-    __slots__ = ["root", "transition_list", "state_list", "state_dict", "timer_count", "history_states", "initial_history_values", "initial_states"]
-
+class StateTree:
     def __init__(self, root: State):
         super().__init__()
         self.root: State = root
@@ -260,11 +277,9 @@ class StateTree(Freezable):
             def assign_state_id():
                 next_id = 0
                 def f(state: State, _=None):
-                    state.opt = StateStatic()
-
                     nonlocal next_id
-                    state.opt.state_id = next_id
-                    state.opt.state_id_bitmap = bit(next_id)
+                    state.state_id = next_id
+                    state.state_id_bitmap = bit(next_id)
                     next_id += 1
 
                 return f
@@ -276,63 +291,56 @@ class StateTree(Freezable):
                     full_name = '/' + state.short_name
                 else:
                     full_name = parent_full_name + '/' + state.short_name
-                state.opt.full_name = full_name
+                state.full_name = full_name
                 return full_name
 
             def assign_depth(state: State, parent_depth: int = 0):
-                state.opt.depth = parent_depth + 1
+                state.depth = parent_depth + 1
                 return parent_depth + 1
 
             def add_to_list(state: State ,_=None):
-                self.state_dict[state.opt.full_name] = state
+                self.state_dict[state.full_name] = state
                 self.state_list.append(state)
 
             def visit_transitions(state: State, _=None):
                     for t in state.transitions:
                         self.transition_list.append(t)
                         if t.trigger and isinstance(t.trigger, AfterTrigger):
-                            state.opt.after_triggers.append(t.trigger)
+                            state.after_triggers.append(t.trigger)
                             self.timer_count += 1
 
             def set_ancestors(state: State, ancestors=Bitmap()):
-                state.opt.ancestors = ancestors
-                return ancestors | state.opt.state_id_bitmap
+                state.ancestors = ancestors
+                return ancestors | state.state_id_bitmap
 
             def set_descendants(state: State, children_descendants):
                 descendants = bm_union(children_descendants)
-                state.opt.descendants = descendants
-                return state.opt.state_id_bitmap | descendants
+                state.descendants = descendants
+                return state.state_id_bitmap | descendants
 
             def calculate_effective_targets(state: State, _=None):
                 # implementation of "effective_targets"-method is recursive (slow)
                 # store the result, it is always the same:
-                state.opt.effective_targets = states_to_bitmap(state.effective_targets())
+                state.effective_targets = states_to_bitmap(state.type.effective_targets())
 
-            history_ids = {}
             def deal_with_history(state: State, children_history):
                 for h in children_history:
                     if isinstance(h, DeepHistoryState):
-                        state.opt.deep_history = (history_ids[h], h.parent.opt.descendants, h)
+                        state.deep_history = (h.history_id, h.parent.descendants, h)
                     elif isinstance(h, ShallowHistoryState):
-                        state.opt.shallow_history = (history_ids[h], h)
+                        state.shallow_history = (h.history_id, h)
 
                 if isinstance(state, HistoryState):
-                    history_ids[state] = len(self.initial_history_values) # generate history ID
+                    state.history_id = len(self.initial_history_values) # generate history ID
                     self.history_states.append(state)
-                    self.initial_history_values.append(state.parent.opt.effective_targets)
+                    self.initial_history_values.append(state.parent.effective_targets)
                     return state
 
-            def freeze(state: State, _=None):
-                state.freeze()
-                state.opt.freeze()
-
             visit_tree(root, lambda s: s.children,
                 parent_first=[
                     assign_state_id(),
                     assign_full_name,
-                    assign_depth,
                     add_to_list,
-                    visit_transitions,
                     set_ancestors,
                 ],
                 child_first=[
@@ -340,19 +348,34 @@ class StateTree(Freezable):
                     calculate_effective_targets,
                 ])
 
-            self.initial_states = root.opt.effective_targets
+            visit_tree(root, lambda s: s.real_children,
+                parent_first=[
+                    assign_depth,
+                    visit_transitions,
+                ],
+                child_first=[])
+
+            self.initial_states = root.effective_targets
 
             visit_tree(root, lambda s: s.children,
                 child_first=[
                     deal_with_history,
-                    freeze,
                 ])
 
+            # print()
+            # def pretty_print(s, indent=""):
+            #     print(indent, s)
+            #     return indent + "  "
+            # visit_tree(root, lambda s: s.children,
+            #     parent_first=[
+            #         pretty_print,
+            #     ])
+
             for t_id, t in enumerate(self.transition_list):
                 # Arena can be computed statically. First compute Lowest-common ancestor:
                 arena = self.lca(t.source, t.target)
                 # Arena must be an Or-state:
-                while isinstance(arena, (ParallelState, HistoryState)):
+                while not isinstance(arena.type, OrState):
                     arena = arena.parent
 
                 # Exit states can be efficiently computed at runtime based on the set of current states.
@@ -363,7 +386,7 @@ class StateTree(Freezable):
                 # Enter path is the intersection between:
                 #   1) the transition's target and its ancestors, and
                 #   2) the arena's descendants
-                enter_path = (t.target.opt.state_id_bitmap | t.target.opt.ancestors) & arena.opt.descendants
+                enter_path = (t.target.state_id_bitmap | t.target.ancestors) & arena.descendants
                 # All states on the enter path will be entered, but on the enter path, there may also be AND-states whose children are not on the enter path, but should also be entered.
                 enter_path_iter = self.bitmap_to_states(enter_path)
                 entered_state = next(enter_path_iter, None)
@@ -372,34 +395,33 @@ class StateTree(Freezable):
                     next_entered_state = next(enter_path_iter, None)
                     if next_entered_state:
                         # an intermediate state on the path from arena to target
-                        enter_states_static |= states_to_bitmap(entered_state.additional_effective_targets(next_entered_state))
+                        enter_states_static |= states_to_bitmap(entered_state.type.additional_effective_targets(next_entered_state))
                     else:
                         # the actual target of the transition
-                        enter_states_static |= entered_state.opt.effective_targets
+                        enter_states_static |= entered_state.effective_targets
                     entered_state = next_entered_state
 
                 target_history_id = None
                 if isinstance(t.target, HistoryState):
-                    target_history_id = history_ids[t.target]
+                    target_history_id = t.target.history_id
+
+                target_stable = False
+                if isinstance(t.target, State):
+                    target_stable = t.target.stable
 
                 raised_events = Bitmap()
                 for a in t.actions:
                     if isinstance(a, RaiseInternalEvent):
                         raised_events |= bit(a.event_id)
 
-                t.opt = TransitionStatic(
-                    id=t_id,
-                    exit_mask=arena.opt.descendants,
-                    arena=arena,
-                    arena_bitmap=arena.opt.descendants | arena.opt.state_id_bitmap,
-                    enter_states_static=enter_states_static,
-                    target_history_id=target_history_id,
-                    target_stable=t.target.stable,
-                    raised_events=raised_events)
-
-                t.freeze()
-
-            self.freeze()
+                t.id = t_id
+                t.exit_mask = arena.descendants
+                t.arena = arena
+                t.arena_bitmap = arena.descendants | arena.state_id_bitmap
+                t.enter_states_static = enter_states_static
+                t.target_history_id = target_history_id
+                t.target_stable = target_stable
+                t.raised_events = raised_events
 
     def bitmap_to_states(self, bitmap: Bitmap) -> Iterator[State]:
         return (self.state_list[id] for id in bm_items(bitmap))
@@ -409,10 +431,10 @@ class StateTree(Freezable):
 
     def lca(self, s1: State, s2: State) -> State:
         # Intersection between source & target ancestors, last member in depth-first sorted state list.
-        return self.state_list[bm_highest_bit(s1.opt.ancestors & s2.opt.ancestors)]
+        return self.state_list[bm_highest_bit(s1.ancestors & s2.ancestors)]
 
 def states_to_bitmap(states: Iterable[State]) -> Bitmap:
-    return bm_from_list(s.opt.state_id for s in states)
+    return bm_from_list(s.state_id for s in states)
 
 def is_ancestor(parent: State, child: State) -> bool:
-    return bm_has(child.opt.ancestors, parent.opt.state_id)
+    return bm_has(child.ancestors, parent.state_id)