Browse Source

Rust: simplify code generator now that basic states are And-states

Joeri Exelmans 4 years ago
parent
commit
c86f9ab9e3
2 changed files with 43 additions and 68 deletions
  1. 37 67
      src/sccd/statechart/codegen/rust.py
  2. 6 1
      src/sccd/test/dynamic/test_rust.py

+ 37 - 67
src/sccd/statechart/codegen/rust.py

@@ -5,6 +5,9 @@ from sccd.statechart.static.statechart import *
 from sccd.statechart.static.globals import *
 from sccd.util.indenting_writer import *
 
+class UnsupportedFeature(Exception):
+    pass
+
 # Conversion functions from abstract syntax elements to identifiers in Rust
 
 def snake_case(state: State) -> str:
@@ -61,7 +64,7 @@ def compile_actions(actions: List[Action], w: IndentingWriter):
             # TODO: evaluate event parameters
             w.writeln("%s(\"%s\", \"%s\");" % (IDENT_OC, a.outport, a.name))
         else:
-            raise Exception("Unimplemented action " + str(type(a)))
+            raise UnsupportedFeature(str(type(a)))
 
 def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
 
@@ -80,27 +83,23 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
     # (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 :)
 
+
     # Write 'current state' types
 
     def write_state_type(state: State, children: List[State]):
-        if isinstance(state, HistoryState):
-            return None # we got no time for pseudo-states!
-
         w.writeln("#[allow(non_camel_case_types)]")
         w.writeln("#[derive(Copy, Clone)]")
 
         def as_struct():
             w.writeln("struct %s {" % ident_type(state))
             for child in children:
-                if child is not None:
-                    w.writeln("  %s: %s," % (ident_field(child), ident_type(child)))
+                w.writeln("  %s: %s," % (ident_field(child), ident_type(child)))
             w.writeln("}")
 
         def as_enum():
             w.writeln("enum %s {" % ident_type(state))
             for child in children:
-                if child is not None:
-                    w.writeln("  %s(%s)," % (ident_enum_variant(child), ident_type(child)))
+                w.writeln("  %s(%s)," % (ident_enum_variant(child), ident_type(child)))
             w.writeln("}")
 
         if isinstance(state.type, AndState):
@@ -117,9 +116,6 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
     # Write "default" constructor
 
     def write_default(state: State, children: List[State]):
-        if isinstance(state, HistoryState):
-            return None # we got no time for pseudo-states!
-
         # 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.
 
@@ -129,16 +125,10 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
         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("      %s: Default::default()," % (ident_field(child)))
             w.writeln("    }")
         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.type.default_state)))
-            else:
-                # Basic state
-                w.writeln("    Self{}")
+            w.writeln("    Self::%s(Default::default())" % (ident_enum_variant(state.type.default_state)))
 
         w.writeln("  }")
         w.writeln("}")
@@ -148,9 +138,6 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
     # Write "enter/exit state" functions
 
     def write_enter_exit(state: State, children: List[State]):
-        if isinstance(state, HistoryState):
-            return None # we got no time for pseudo-states!
-
         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)
@@ -171,26 +158,21 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
         w.writeln("    %s::enter_actions(%s);" % (ident_type(state), IDENT_OC))
         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.type.default_state is not None:
-                w.writeln("    %s::enter_default(%s);" % (ident_type(state.type.default_state), IDENT_OC))
+                w.writeln("    %s::enter_default(%s);" % (ident_type(child), IDENT_OC))
+        elif isinstance(state.type, OrState):
+            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.type, AndState):
             for child in children:
-                if child is not None:
-                    w.writeln("    self.%s.exit_current(%s);" % (ident_field(child), IDENT_OC))
-        else:
-            if len(children) > 0:
-                w.writeln("    match self {")
-                for child in children:
-                    if child is not None:
-                        w.writeln("      Self::%s(s) => { s.exit_current(%s); }," % (ident_enum_variant(child), IDENT_OC))
-                w.writeln("    }")
+                w.writeln("    self.%s.exit_current(%s);" % (ident_field(child), IDENT_OC))
+        elif isinstance(state.type, OrState):
+            w.writeln("    match self {")
+            for child in children:
+                w.writeln("      Self::%s(s) => { s.exit_current(%s); }," % (ident_enum_variant(child), IDENT_OC))
+            w.writeln("    }")
         # Our own exit actions
         w.writeln("    %s::exit_actions(%s);" % (ident_type(state), IDENT_OC))
         w.writeln("  }")
@@ -201,22 +183,19 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
         # Our own enter actions
         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))
-        else:
-            if len(children) > 0:
-                w.writeln("    match self {")
-                for child in children:
-                    if child is not None:
-                        w.writeln("      Self::%s(s) => { s.enter_current(%s); }," % (ident_enum_variant(child), IDENT_OC))
-                w.writeln("    }")
+                w.writeln("    self.%s.enter_current(%s);" % (ident_field(child), IDENT_OC))
+        elif isinstance(state.type, OrState):
+            w.writeln("    match self {")
+            for child in children:
+                w.writeln("      Self::%s(s) => { s.enter_current(%s); }," % (ident_enum_variant(child), IDENT_OC))
+            w.writeln("    }")
         w.writeln("  }")
 
         w.writeln("}")
         w.writeln()
         return state
 
-    visit_tree(tree.root, lambda s: s.children,
+    visit_tree(tree.root, lambda s: s.real_children,
         child_first=[
             write_state_type,
             write_default,
@@ -289,8 +268,6 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
 
 
     def write_transitions(state: State):
-        if isinstance(state, HistoryState):
-            return None # we got no time for pseudo-states!
 
         # Many of the states to exit can be computed statically (i.e. they are always the same)
         # The one we cannot compute statically are:
@@ -327,9 +304,7 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                             else:
                                 w.writeln("%s.exit_current(%s);" % (ident_var(c), IDENT_OC))
                     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
+                        write_exit(exit_path[1:]) # continue recursively with the next child on the exit path
 
                     # Exit s:
                     w.writeln("%s::exit_actions(%s);" % (ident_type(s), IDENT_OC))
@@ -342,10 +317,10 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                     _, h = s.shallow_history
                     if isinstance(s.type, AndState):
                         raise Exception("Shallow history makes no sense for And-state!")
+                    # Or-state:
                     w.writeln("sc.%s = match %s { // Store shallow history" % (ident_history_field(h), ident_var(s)))
-                    for c in s.children:
-                        if not isinstance(c, HistoryState):
-                            w.writeln("  %s::%s(_) => %s::%s(%s::default())," % (ident_type(s), ident_enum_variant(c), ident_type(s), ident_enum_variant(c), ident_type(c)))
+                    for c in s.real_children:
+                        w.writeln("  %s::%s(_) => %s::%s(%s::default())," % (ident_type(s), ident_enum_variant(c), ident_type(s), ident_enum_variant(c), ident_type(c)))
                     w.writeln("};")
                     # w.writeln("println!(\"recorded history\");")
 
@@ -367,8 +342,6 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                     if isinstance(s.type, AndState):
                         for c in s.children:
                             if enter_path[1] is c:
-                                # if not isinstance(c, HistoryState):
-                                #     w.writeln("%s::enter_actions(%s);" % (ident_type(c), IDENT_OC))
                                 write_enter(enter_path[1:]) # continue recursively
                             else:
                                 w.writeln("%s::enter_default(%s);" % (ident_type(c), IDENT_OC))
@@ -420,12 +393,12 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
 
                 if t.trigger is not EMPTY_TRIGGER:
                     if len(t.trigger.enabling) > 1:
-                        raise Exception("Multi-event triggers currently unsupported")
+                        raise UnsupportedFeature("Multi-event trigger")
                     w.writeln("if let Some(Event::%s) = _event {" % t.trigger.enabling[0].name)
                     w.indent()
 
                 if t.guard is not None:
-                    raise Exception("Guard conditions currently unsupported")
+                    raise UnsupportedFeature("Guard conditions currently unsupported")
 
                 # 1. Execute transition's actions
 
@@ -473,11 +446,10 @@ 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.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)
+                for child in state.real_children:
+                    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.type, OrState):
                 if state.type.default_state is not None:
                     if syntactic_maximality and state in arenas:
@@ -487,9 +459,7 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
                     w.writeln("'%s: loop {" % ident_arena_label(state))
                     w.indent()
                     w.writeln("match %s {" % ident_var(state))
-                    for child in state.children:
-                        if isinstance(child, HistoryState):
-                            continue
+                    for child in state.real_children:
                         w.indent()
                         w.writeln("%s::%s(%s) => {" % (ident_type(state), ident_enum_variant(child), ident_var(child)))
                         w.indent()
@@ -517,7 +487,7 @@ def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
             parent()
             child()
         else:
-            raise Exception("Unsupported semantics %s" % sc.semantics.hierarchical_priority)
+            raise UnsupportedFeature("Priority semantics %s" % sc.semantics.hierarchical_priority)
 
     write_transitions(tree.root)
 

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

@@ -2,8 +2,10 @@ import threading
 import subprocess
 import tempfile
 import os
+from unittest import SkipTest
 from typing import *
 from sccd.test.static.syntax import TestVariant
+from sccd.statechart.codegen.rust import UnsupportedFeature
 from sccd.test.codegen.rust import compile_test
 from sccd.util.indenting_writer import IndentingWriter
 from sccd.util.debug import *
@@ -34,7 +36,10 @@ def run_variants(variants: List[TestVariant], unittest):
 
         w = IndentingWriter(out=PipeWriter(pipe))
 
-        compile_test(variants, w)
+        try:
+            compile_test(variants, w)
+        except UnsupportedFeature as e:
+            raise SkipTest("unsupported feature: " + str(e))
 
         pipe.stdin.close()