ソースを参照

Rust: 'Arenas' bitmap type turns to boolean type when no syntactic-maximality semantics are being used.

Joeri Exelmans 4 年 前
コミット
117474da63

+ 13 - 0
notes.txt

@@ -31,6 +31,19 @@ Long-term vision:
 
 Random notes:
 
+  - Statechart interface:
+      - want YAKINDU style: strict separation between:
+        - input events
+        - internal events
+        - output events
+      - each declared with their parameter types
+
+      - in/out/internal events grouped per port
+      - operations? also belong to port, or keep separate?
+
+  - CD-part of SCCD:
+      - look at ROOM! (Real-time Object Oriented Modeling, Bran Selic)
+
   - (DONE) Currently, a basic state is represented in the abstract syntax as an Or-state with no children. Instead, it should be represented as an And-state with no children.
     Rationale:
       - The (flattened) set of states representable by an Or-state is the *sum* of the sets of states of its children. If an Or-state has no children, mathematically its set of states has 0 elements, as 0 is the neutral element for addition.

+ 77 - 86
src/sccd/statechart/codegen/rust.py

@@ -78,56 +78,52 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
 
     tree = sc.tree
 
-    # Note: The reason for the
-    #    #[allow(non_camel_case_types)]
-    # lines is that we cannot convert the casing of our state's names:
-    # SCCD allows any combination of upper and lower case symbols, and
-    # converting to, say, camelcase, as Rust likes it for type names,
-    # could cause naming collisions.
-    # (these naming collisions would be detected by the Rust compiler, so the error would not go unnoticed,
-    # still, it's better to NOT break our model :)
-
     w.writeln("type Timers = [TimerId; %d];" % tree.timer_count)
     w.writeln()
 
-    # Write 'current state' types
+    # Write event types
+    input_events = sc.internal_events & ~sc.internally_raised_events
+    internal_events = sc.internally_raised_events
+    w.writeln("#[derive(Copy, Clone)]")
+    w.writeln("// Input Events")
+    w.writeln("enum InEvent {")
+    for event_name in (globals.events.names[i] for i in bm_items(input_events)):
+        w.writeln("  %s," % ident_event(event_name))
+    w.writeln("}")
+    w.writeln("#[derive(Copy, Clone)]")
+    w.writeln("// Internal Events")
+    w.writeln("enum Internal {")
+    for event_name in (globals.events.names[i] for i in bm_items(internal_events)):
+        w.writeln("  %s," % ident_event(event_name))
+    w.writeln("}")
+    w.writeln()
 
+    # Write 'current state' types
     def write_state_type(state: State, children: List[State]):
-        w.writeln("#[allow(non_camel_case_types)]")
-        w.writeln("#[derive(Copy, Clone)]")
-
-        def as_struct():
+        if isinstance(state.type, AndState):
+            w.writeln("// And-state")
+            # TODO: Only annotate Copy for states that will be recorded by deep history.
+            w.writeln("#[derive(Copy, Clone)] // for history")
             w.writeln("struct %s {" % ident_type(state))
             for child in children:
                 w.writeln("  %s: %s," % (ident_field(child), ident_type(child)))
             w.writeln("}")
-
-        def as_enum():
+        elif isinstance(state.type, OrState):
+            w.writeln("// Or-state")
+            w.writeln("#[derive(Copy, Clone)]")
             w.writeln("enum %s {" % ident_type(state))
             for child in children:
                 w.writeln("  %s(%s)," % (ident_enum_variant(child), ident_type(child)))
             w.writeln("}")
-
-        if isinstance(state.type, AndState):
-            w.writeln("// And-state")
-            as_struct()
-        elif isinstance(state.type, OrState):
-            w.writeln("// Or-state")
-            as_enum()
-
-        w.writeln()
         return state
 
 
     # Write "default" constructor
-
     def write_default(state: State, children: List[State]):
         # We use Rust's Default-trait to record default states,
         # this way, constructing a state instance without parameters will initialize it as the default state.
-
         w.writeln("impl Default for %s {" % ident_type(state))
         w.writeln("  fn default() -> Self {")
-
         if isinstance(state.type, AndState):
             w.writeln("    Self {")
             for child in children:
@@ -135,28 +131,26 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
             w.writeln("    }")
         elif isinstance(state.type, OrState):
             w.writeln("    Self::%s(Default::default())" % (ident_enum_variant(state.type.default_state)))
-
         w.writeln("  }")
         w.writeln("}")
-        w.writeln()
         return state
 
-    # Write "enter/exit state" functions
-
+    # Implement trait 'State': enter/exit
     def write_enter_exit(state: State, children: List[State]):
-        w.writeln("impl<'a, OutputCallback: FnMut(OutEvent)> State<Timers, Controller<Event, OutputCallback>> for %s {" % ident_type(state))
-        # w.writeln("impl<OutputCallback: FnMut(OutEvent)> State<Handle<Controller<Event, OutputCallback>>> for %s {" % ident_type(state))
+        w.writeln("impl<'a, OutputCallback: FnMut(OutEvent)> State<Timers, Controller<InEvent, OutputCallback>> for %s {" % ident_type(state))
 
-        w.writeln("  fn enter_actions(timers: &mut Timers, ctrl: &mut Controller<Event, OutputCallback>) {")
+        # Enter actions: Executes enter actions of only this state
+        w.writeln("  fn enter_actions(timers: &mut Timers, ctrl: &mut Controller<InEvent, OutputCallback>) {")
         w.writeln("    println!(\"enter %s\");" % state.full_name);
         w.indent(); w.indent()
         compile_actions(state.enter, w)
         w.dedent(); w.dedent()
         for a in state.after_triggers:
-            w.writeln("    timers[%d] = ctrl.set_timeout(%d, Event::%s);" % (a.after_id, a.delay.opt, ident_event(a.enabling[0].name)))
+            w.writeln("    timers[%d] = ctrl.set_timeout(%d, InEvent::%s);" % (a.after_id, a.delay.opt, ident_event(a.enabling[0].name)))
         w.writeln("  }")
 
-        w.writeln("  fn exit_actions(timers: &mut Timers, ctrl: &mut Controller<Event, OutputCallback>) {")
+        # Enter actions: Executes exit actions of only this state
+        w.writeln("  fn exit_actions(timers: &mut Timers, ctrl: &mut Controller<InEvent, OutputCallback>) {")
         w.writeln("    println!(\"exit %s\");" % state.full_name);
         for a in state.after_triggers:
             w.writeln("    ctrl.unset_timeout(timers[%d]);" % (a.after_id))
@@ -165,7 +159,8 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
         w.dedent(); w.dedent()
         w.writeln("  }")
 
-        w.writeln("  fn enter_default(timers: &mut Timers, ctrl: &mut Controller<Event, OutputCallback>) {")
+        # Enter default: Executes enter actions of entering this state and its default substates, recursively
+        w.writeln("  fn enter_default(timers: &mut Timers, ctrl: &mut Controller<InEvent, OutputCallback>) {")
         w.writeln("    %s::enter_actions(timers, ctrl);" % (ident_type(state)))
         if isinstance(state.type, AndState):
             for child in children:
@@ -174,8 +169,9 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
             w.writeln("    %s::enter_default(timers, ctrl);" % (ident_type(state.type.default_state)))
         w.writeln("  }")
 
-        w.writeln("  fn exit_current(&self, timers: &mut Timers, ctrl: &mut Controller<Event, OutputCallback>) {")
-        # Children's exit actions
+        # Exit current: Executes exit actions of this state and current children, recursively
+        w.writeln("  fn exit_current(&self, timers: &mut Timers, ctrl: &mut Controller<InEvent, OutputCallback>) {")
+        # first, children (recursion):
         if isinstance(state.type, AndState):
             for child in children:
                 w.writeln("    self.%s.exit_current(timers, ctrl);" % (ident_field(child)))
@@ -184,14 +180,15 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
             for child in children:
                 w.writeln("      Self::%s(s) => { s.exit_current(timers, ctrl); }," % (ident_enum_variant(child)))
             w.writeln("    }")
-        # Our own exit actions
+        # then, parent:
         w.writeln("    %s::exit_actions(timers, ctrl);" % (ident_type(state)))
         w.writeln("  }")
 
-        w.writeln("  fn enter_current(&self, timers: &mut Timers, ctrl: &mut Controller<Event, OutputCallback>) {")
-        # Children's enter actions
+        # Exit current: Executes enter actions of this state and current children, recursively
+        w.writeln("  fn enter_current(&self, timers: &mut Timers, ctrl: &mut Controller<InEvent, OutputCallback>) {")
+        # first, parent:
         w.writeln("    %s::enter_actions(timers, ctrl);" % (ident_type(state)))
-        # Our own enter actions
+        # then, children (recursion):
         if isinstance(state.type, AndState):
             for child in children:
                 w.writeln("    self.%s.enter_current(timers, ctrl);" % (ident_field(child)))
@@ -213,42 +210,43 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
             write_enter_exit,
         ])
 
-    # Write event type
-
-    w.writeln("#[allow(non_camel_case_types)]")
-    w.writeln("#[derive(Copy, Clone)]")
-    w.writeln("enum Event {")
-    for event_name in (globals.events.names[i] for i in bm_items(sc.internal_events)):
-        w.writeln("  %s," % ident_event(event_name))
-    w.writeln("}")
-    w.writeln()
+    syntactic_maximality = (
+        sc.semantics.big_step_maximality == Maximality.SYNTACTIC
+        or sc.semantics.combo_step_maximality == Maximality.SYNTACTIC)
 
     # Write arena type
     arenas = set()
     for t in tree.transition_list:
         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:
-            w.writeln("type Arenas = %s;" % typ)
-            break
+    if syntactic_maximality:
+        for size, typ in [(8, 'u8'), (16, 'u16'), (32, 'u32'), (64, 'u64'), (128, 'u128')]:
+            if len(arenas) + 1 <= size:
+                w.writeln("type Arenas = %s;" % typ)
+                break
+        else:
+            raise UnsupportedFeature("Too many arenas! Cannot fit into an unsigned int.")
+        w.writeln("const ARENA_NONE: Arenas = 0;")
+        w.writeln("const ARENA_UNSTABLE: Arenas = 1; // indicates any transition fired with an unstable target")
+        for i, a in enumerate(arenas):
+            w.writeln("const %s: Arenas = %d;" % (ident_arena_const(a), 2**(i+1)))
     else:
-        raise Exception("Too many arenas! Cannot fit into an unsigned int.")
-    for i, a in enumerate(arenas):
-        w.writeln("const %s: Arenas = %d;" % (ident_arena_const(a), 2**i))
-    # Just an extra bit to indicate that a transition has fired during fair_step
-    # This is to decide when to stop stepping in case of Take Many or Take Syntactic
-    w.writeln("const ARENA_FIRED: Arenas = %d;" % 2**(i+1))
+        w.writeln("type Arenas = bool;")
+        w.writeln("const ARENA_NONE: Arenas = false;")
+        for a in arenas:
+            w.writeln("const %s: Arenas = true;" % ident_arena_const(a))
+        w.writeln("const ARENA_UNSTABLE: Arenas = false; // inapplicable to chosen semantics - all transition targets considered stable")
     w.writeln()
 
     # Write statechart type
     w.writeln("pub struct Statechart {")
     w.writeln("  current_state: %s," % ident_type(tree.root))
+    # We always store a history value as 'deep' (also for shallow history).
+    # TODO: We may save a tiny bit of space in some rare cases by storing shallow history as only the exited child of the Or-state.
     for h in tree.history_states:
         w.writeln("  %s: %s," % (ident_history_field(h), ident_type(h.parent)))
     w.writeln("  timers: Timers,")
     w.writeln("}")
-    w.writeln()
 
     w.writeln("impl Default for Statechart {")
     w.writeln("  fn default() -> Self {")
@@ -262,21 +260,12 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
     w.writeln("}")
     w.writeln()
 
-
     # Function fair_step: a single "Take One" Maximality 'round' (= nonoverlapping arenas allowed to fire 1 transition)
-    w.writeln("fn fair_step<OutputCallback: FnMut(OutEvent)>(sc: &mut Statechart, _event: Option<Event>, ctrl: &mut Controller<Event, OutputCallback>, dirty: Arenas) -> Arenas {")
-    w.writeln("  #![allow(non_snake_case)]")
-    w.writeln("  #![allow(unused_labels)]")
-    w.writeln("  #![allow(unused_variables)]")
-    w.writeln("  let mut fired: Arenas = 0;")
+    w.writeln("fn fair_step<OutputCallback: FnMut(OutEvent)>(sc: &mut Statechart, _event: Option<InEvent>, ctrl: &mut Controller<InEvent, OutputCallback>, dirty: Arenas) -> Arenas {")
+    w.writeln("  let mut fired: Arenas = ARENA_NONE;")
     w.writeln("  let %s = &mut sc.current_state;" % ident_var(tree.root))
     w.indent()
 
-    syntactic_maximality = (
-        sc.semantics.big_step_maximality == Maximality.SYNTACTIC
-        or sc.semantics.combo_step_maximality == Maximality.SYNTACTIC)
-
-
     def write_transitions(state: State):
 
         # Many of the states to exit can be computed statically (i.e. they are always the same)
@@ -402,7 +391,7 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                 if t.trigger is not EMPTY_TRIGGER:
                     if len(t.trigger.enabling) > 1:
                         raise UnsupportedFeature("Multi-event trigger")
-                    w.writeln("if let Some(Event::%s) = _event {" % ident_event(t.trigger.enabling[0].name))
+                    w.writeln("if let Some(InEvent::%s) = _event {" % ident_event(t.trigger.enabling[0].name))
                     w.indent()
 
                 if t.guard is not None:
@@ -442,7 +431,7 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                 if not syntactic_maximality or t.target.stable:
                     w.writeln("fired |= %s; // Stable target" % ident_arena_const(t.arena))
                 else:
-                    w.writeln("fired |= ARENA_FIRED; // Unstable target")
+                    w.writeln("fired |= ARENA_UNSTABLE; // Unstable target")
 
                 # This arena is done:
                 w.writeln("break '%s;" % (ident_arena_label(t.arena)))
@@ -504,8 +493,9 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
     w.writeln("  fired")
     w.writeln("}")
 
+    # Write combo step and big step function
     def write_stepping_function(name: str, title: str, maximality: Maximality, substep: str, input_whole: bool):
-        w.write("fn %s<OutputCallback: FnMut(OutEvent)>(sc: &mut Statechart, event: Option<Event>, ctrl: &mut Controller<Event, OutputCallback>, dirty: Arenas)" % (name))
+        w.write("fn %s<OutputCallback: FnMut(OutEvent)>(sc: &mut Statechart, event: Option<InEvent>, ctrl: &mut Controller<InEvent, OutputCallback>, dirty: Arenas)" % (name))
         w.writeln(" -> Arenas {")
         if maximality == Maximality.TAKE_ONE:
             w.writeln("  // %s Maximality: Take One" % title)
@@ -516,14 +506,14 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
             w.writeln("  loop {")
             if maximality == Maximality.TAKE_MANY:
                 w.writeln("    // %s Maximality: Take Many" % title)
-                w.writeln("    let just_fired = %s(sc, e, ctrl, 0);" % (substep))
+                w.writeln("    let just_fired = %s(sc, e, ctrl, ARENA_NONE);" % (substep))
             elif maximality == Maximality.SYNTACTIC:
                 w.writeln("    // %s Maximality: Syntactic" % title)
                 w.writeln("    let just_fired = %s(sc, e, ctrl, fired);" % (substep))
-            w.writeln("    if just_fired == 0 {")
+            w.writeln("    if just_fired == ARENA_NONE { // did any transition fire? (incl. unstable)")
             w.writeln("      break;")
             w.writeln("    }")
-            w.writeln("    fired |= just_fired & !ARENA_FIRED;")
+            w.writeln("    fired |= just_fired & !ARENA_UNSTABLE; // only record stable arenas")
             if not input_whole:
                 w.writeln("    // Input Event Lifeline: %s" % sc.semantics.input_event_lifeline)
                 w.writeln("    e = None;")
@@ -545,12 +535,12 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
     w.writeln()
 
     # Implement 'SC' trait
-    w.writeln("impl<OutputCallback: FnMut(OutEvent)> SC<Event, Controller<Event, OutputCallback>> for Statechart {")
-    w.writeln("  fn init(&mut self, ctrl: &mut Controller<Event, OutputCallback>) {")
+    w.writeln("impl<OutputCallback: FnMut(OutEvent)> SC<InEvent, Controller<InEvent, OutputCallback>> for Statechart {")
+    w.writeln("  fn init(&mut self, ctrl: &mut Controller<InEvent, OutputCallback>) {")
     w.writeln("    %s::enter_default(&mut self.timers, ctrl)" % (ident_type(tree.root)))
     w.writeln("  }")
-    w.writeln("  fn big_step(&mut self, event: Option<Event>, c: &mut Controller<Event, OutputCallback>) {")
-    w.writeln("    big_step(self, event, c, 0);")
+    w.writeln("  fn big_step(&mut self, event: Option<InEvent>, c: &mut Controller<InEvent, OutputCallback>) {")
+    w.writeln("    big_step(self, event, c, ARENA_NONE);")
     w.writeln("  }")
     w.writeln("}")
     w.writeln()
@@ -566,7 +556,8 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
             for child in state.real_children:
                 write_state_size(child)
         write_state_size(tree.root)
-        w.writeln("  println!(\"info: Event: {} bytes\", size_of::<Event>());")
+        w.writeln("  println!(\"info: InEvent: {} bytes\", size_of::<InEvent>());")
+        w.writeln("  println!(\"info: OutEvent: {} bytes\", size_of::<OutEvent>());")
         w.writeln("  println!(\"info: Arenas: {} bytes\", size_of::<Arenas>());")
         w.writeln("  println!(\"------------------------\");")
         w.writeln("}")

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

@@ -21,6 +21,7 @@ class Statechart(Freezable):
 
     # Union of internally raised and input events. Basically all the events that a transition could be triggered by.
     self.internal_events: Bitmap = internal_events
+    
     # All internally raised events in the statechart, may overlap with input events, as an event can be both an input event and internally raised.
     self.internally_raised_events: Bitmap = internally_raised_events
 

+ 14 - 2
src/sccd/test/codegen/rust.py

@@ -8,6 +8,18 @@ rustlib = os.path.dirname(sccd.cd.codegen.__file__) + "/sccdlib.rs"
 
 def compile_test(variants: List[TestVariant], w: IndentingWriter):
 
+    # Note: The reason for these is that we cannot convert the casing of our state's names:
+    # SCCD allows any combination of upper and lower case symbols, and
+    # converting to, say, camelcase, as Rust likes it for type names,
+    # could cause naming collisions.
+    # Rust may output a ton of warnings for this. We disable these types of warnings,
+    # so that other, more interesting types of warnings don't go unnoticed.
+    w.writeln("#![allow(non_camel_case_types)]")
+    w.writeln("#![allow(non_snake_case)]")
+    w.writeln("#![allow(unused_labels)]")
+    w.writeln("#![allow(unused_variables)]")
+    w.writeln("#![allow(dead_code)]")
+
     with open(rustlib, 'r') as file:
         data = file.read()
         w.writeln(data)
@@ -29,7 +41,7 @@ def compile_test(variants: List[TestVariant], w: IndentingWriter):
         w.writeln("  println!(\"^{}:{}\", out.port, out.event);")
         w.writeln("  raised.push(out);")
         w.writeln("};")
-        w.writeln("let mut controller = Controller::<Event,_>::new(&mut output);")
+        w.writeln("let mut controller = Controller::<InEvent,_>::new(&mut output);")
         w.writeln("let mut sc: Statechart = Default::default();")
         w.writeln("sc.init(&mut controller);")
         for i in v.input:
@@ -37,7 +49,7 @@ def compile_test(variants: List[TestVariant], w: IndentingWriter):
                 raise Exception("Multiple simultaneous input events not supported")
             elif len(i.events) == 0:
                 raise Exception("Test declares empty bag of input events - not supported")
-            w.writeln("controller.set_timeout(%d, Event::%s);" % (i.timestamp.opt, ident_event(i.events[0].name)))
+            w.writeln("controller.set_timeout(%d, InEvent::%s);" % (i.timestamp.opt, ident_event(i.events[0].name)))
 
         w.writeln("controller.run_until(&mut sc, Until::Eternity);")
         ctr = 0

+ 6 - 1
src/sccd/test/dynamic/test_rust.py

@@ -45,11 +45,16 @@ def run_variants(variants: List[TestVariant], unittest):
 
         print_debug("Generated Rust code.")
 
+        ruststderr = pipe.stderr.read().decode('UTF-8')
+
         status = pipe.wait()
 
+        if DEBUG:
+            print(ruststderr)
+
         if status != 0:
             # This does not indicate a test failure, but an error in our code generator
-            raise Exception("Rust compiler status %d. Sterr:\n%s" % (status, pipe.stderr.read().decode('UTF-8')))
+            raise Exception("Rust compiler status %d. Sterr:\n%s" % (status, ruststderr))
 
     print_debug("Generated binary. Running...")