Jelajahi Sumber

Fix some things + add test

Joeri Exelmans 4 tahun lalu
induk
melakukan
aa5490f614

+ 8 - 0
notes.txt

@@ -37,6 +37,14 @@ Long-term vision:
 
 Random notes:
 
+  - Explore per-state data models
+      - Right now, there's a single datamodel for the entire statechart. All of the statechart's variables are declared there.
+      - We could have datamodels per state: Variables declared in state A's enter actions would belong to A, and be readable/writable by any transition (guard, actions) sourcing from A's substates, as well as the enter- and exit actions of A's substates.
+      - The benefit would be composability: 2 statechart models could be merged without having conflicting variables
+        - The same non-conflicting merge could also be achieved by automatic renaming, though.
+      - Another benefit would be savings on memory consumption: The memory consumed by the datamodel of 2 non-orthogonal states would only be the size of the biggest datamodel, instead of the sum of the 2 datamodels.
+      - Conclusion: Could be interesting for very large, composed, statecharts
+
   - Durations in action language are a single type, consisting of an integer value and a unit. Perhaps it would be better to have a separate duration type for every unit. Especially in Rust it is better to leave the units to the type system, and only work with 'the numbers' in the machine code :)
 
   - Statechart interface:

+ 12 - 8
src/sccd/action_lang/codegen/rust.py

@@ -78,6 +78,17 @@ class ScopeHelper():
         self.current().committed = end
         return type_name
 
+    def write_rvalue(self, name, offset, writer):
+        if offset < 0:
+            writer.wno("parent%d." % self.current().scope.nested_levels(offset))
+            writer.wno(ident_local(name))
+        elif offset < self.current().committed:
+            writer.wno("scope.")
+            writer.wno(ident_local(name))
+        else:
+            writer.wno(ident_local(name))
+
+
 class ActionLangRustGenerator(Visitor):
     def __init__(self, w):
         self.w = w
@@ -309,14 +320,7 @@ class ActionLangRustGenerator(Visitor):
             self.w.wno("let mut ")
             self.w.wno(ident_local(lval.name))
         else:
-            if lval.offset < 0:
-                self.w.wno("parent%d." % self.scope.current().scope.nested_levels(lval.offset))
-                self.w.wno(ident_local(lval.name))
-            elif lval.offset < self.scope.current().committed:
-                self.w.wno("scope.")
-                self.w.wno(ident_local(lval.name))
-            else:
-                self.w.wno(ident_local(lval.name))
+            self.scope.write_rvalue(lval.name, lval.offset, self.w)
 
     def visit_SCCDClosureObject(self, type):
         self.w.wno("(%s, " % self.scope.type(type.scope, type.scope.size()))

+ 4 - 3
src/sccd/cd/parser/xml.py

@@ -1,17 +1,18 @@
-from sccd.action_lang.parser import text as action_lang_parser
+from sccd.statechart.parser.text import *
 from sccd.statechart.parser.xml import *
 from sccd.cd.static.cd import *
 
 def cd_parser_rules(statechart_parser_rules, default_delta = duration(100, Microsecond)):
   globals = Globals()
-  sc_rules = statechart_parser_rules(globals)
+  text_parser = TextParser(globals)
+  sc_rules = statechart_parser_rules(globals, text_parser=text_parser)
   delta = default_delta
 
   def parse_single_instance_cd(el):
 
     def parse_delta(el):
       nonlocal delta
-      delta_expr = action_lang_parser.parse_expression(el.text)
+      delta_expr = text_parser.parse_expr(el.text)
       delta = delta_expr.eval(None)
 
     def finish_single_instance_cd(statechart):

+ 22 - 18
src/sccd/statechart/codegen/rust.py

@@ -91,13 +91,27 @@ class StatechartRustGenerator(ActionLangRustGenerator):
             self.parallel_state_cache[state] = parallel_states
             return parallel_states
 
-    def get_parallel_states_tuple(self):
-        parallel_states = self.get_parallel_states(self.state_stack[-1])
+    def get_parallel_states_tuple(self, state):
+        parallel_states = self.get_parallel_states(state)
         if len(parallel_states) > 0:
             return "(" + ", ".join("*"+ident_var(s) for s in parallel_states) + ", )"
         else:
             return "()"
 
+    def get_parallel_states_tuple_type(self, state):
+        parallel_states = self.get_parallel_states(state)
+        if len(parallel_states) > 0:
+            return "(%s, )" % ", ".join(ident_type(s) for s in parallel_states)
+        else:
+            return "()"
+
+    def get_parallel_states_pattern_matched(self, state):
+        parallel_states = self.get_parallel_states(state)
+        if len(parallel_states) > 0:
+            return "(%s, )" % ", ".join("%s: %s" % (ident_var(s), ident_type(s)) for s in parallel_states)
+        else:
+            return "()"
+
     def visit_InStateMacroExpansion(self, instate):
         source = instate.ref.source
         target = instate.ref.target
@@ -114,7 +128,9 @@ class StatechartRustGenerator(ActionLangRustGenerator):
             self.w.wno("ref ")
             self.w.wno(ident_var(parent))
             self.w.wno(", ")
-        self.w.wnoln(") = %s;" % ident_local("@conf"))
+        self.w.wno(") = ")
+        self.scope.write_rvalue("@conf", instate.offset, self.w)
+        self.w.wnoln(";")
 
         for parent in parents + [source]:
             if is_ancestor(parent=target, child=parent):
@@ -151,14 +167,8 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.dedent()
         self.w.write("}")
 
-        # self.w.wno("false")
-
     def visit_SCCDStateConfiguration(self, type):
-        parallel_states = self.get_parallel_states(type.state)
-        if len(parallel_states) > 0:
-            self.w.wno("(%s, )" % ", ".join(ident_type(s) for s in parallel_states))
-        else:
-            self.w.wno("()")
+        self.w.wno(self.get_parallel_states_tuple_type(type.state))
 
     def visit_RaiseOutputEvent(self, a):
         # TODO: evaluate event parameters
@@ -177,7 +187,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
 
         self.w.write()
         a.block.accept(self) # block is a function
-        self.w.wno("(%s, scope);" % self.get_parallel_states_tuple()) # call it!
+        self.w.wno("(%s, scope);" % self.get_parallel_states_tuple(self.state_stack[-1])) # call it!
         self.w.wnoln()
 
     def visit_State(self, state):
@@ -385,12 +395,6 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         for arena, bm in arenas.items():
             self.w.writeln("const %s: Arenas = %s;" % (ident_arena_const(arena), bin(bm)))
         self.w.writeln("const ARENA_UNSTABLE: Arenas = %s; // indicates any transition fired with an unstable target" % bin(2**len(arenas.items())))
-        # else:
-        #     self.w.writeln("type Arenas = bool;")
-        #     self.w.writeln("const ARENA_NONE: Arenas = false;")
-        #     for arena, bm in arenas.items():
-        #         self.w.writeln("const %s: Arenas = true;" % ident_arena_const(arena))
-        #     self.w.writeln("const ARENA_UNSTABLE: Arenas = false; // inapplicable to chosen semantics - all transition targets considered stable")
         self.w.writeln()
 
         # Write statechart type
@@ -586,7 +590,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
                         self.w.write("if ")
                         t.guard.accept(self) # guard is a function...
                         self.w.wno("(") # call it!
-                        self.w.wno(self.get_parallel_states_tuple())
+                        self.w.wno(self.get_parallel_states_tuple(t.source))
                         self.w.wno(", ")
                         # TODO: write event parameters here
                         self.write_parent_call_params(t.guard.scope)

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

@@ -17,7 +17,7 @@ class InStateMacroExpansion(Expression):
 
     def eval(self, memory: MemoryInterface):
         state_configuration = memory.load(self.offset)
-        return self.ref.target.state_id_bitmap & state_configuration
+        return bool(self.ref.target.state_id_bitmap & state_configuration)
 
     def render(self):
         return "@in(" + self.ref.target.full_name + ')'

+ 43 - 0
test_files/features/instate/test_instate_nested.xml

@@ -0,0 +1,43 @@
+<test>
+  <statechart>
+    <root>
+      <parallel id="p">
+        <state id="o0" initial="a">
+          <state id="a">
+            <transition port="in" event="try" target="../b" cond='func { return @in("/p/o1/d");  }()'>
+              <raise port="out" event="yes"/>
+            </transition>
+            <transition port="in" event="try" target="." cond='not @in("/p/o1/d")'>
+              <raise port="out" event="no"/>
+            </transition>
+          </state>
+          <state id="b">
+          </state>
+        </state>
+
+        <state id="o1" initial="c">
+          <state id="c">
+            <transition port="in" event="to_d" target="../d"/>
+          </state>
+          <state id="d">
+          </state>
+        </state>
+      </parallel>
+    </root>
+  </statechart>
+
+  <input>
+    <event port="in" name="try" time="0 d"/>
+    <event port="in" name="to_d" time="1 s"/>
+    <event port="in" name="try" time="2 s"/>
+  </input>
+
+  <output>
+    <big_step>
+      <event port="out" name="no"/>
+    </big_step>
+    <big_step>
+      <event port="out" name="yes"/>
+    </big_step>  
+  </output>
+</test>