浏览代码

Declare event parameters for in/out events in statechart interface. Simplify interpreter: Events no longer have integer ID's, use string compare instead. Implemented event parameters in Rust.

Joeri Exelmans 4 年之前
父节点
当前提交
91a048eedb
共有 35 个文件被更改,包括 448 次插入365 次删除
  1. 5 2
      code_generation.txt
  2. 4 4
      examples/digitalwatch/webassembly/index.html
  3. 20 13
      examples/digitalwatch/webassembly/wasm/lib.rs
  4. 二进制
      examples/digitalwatch/webassembly/wasm/pkg/dwatch_bg.wasm
  5. 4 2
      python/sccd/action_lang/codegen/rust.py
  6. 2 1
      python/sccd/action_lang/parser/action_lang.g
  7. 13 8
      python/sccd/cd/static/cd.py
  8. 76 44
      python/sccd/statechart/codegen/rust.py
  9. 53 54
      python/sccd/statechart/dynamic/candidate_generator.py
  10. 6 7
      python/sccd/statechart/dynamic/event.py
  11. 2 2
      python/sccd/statechart/dynamic/round.py
  12. 6 30
      python/sccd/statechart/dynamic/statechart_execution.py
  13. 4 4
      python/sccd/statechart/dynamic/statechart_instance.py
  14. 6 3
      python/sccd/statechart/parser/text.py
  15. 53 65
      python/sccd/statechart/parser/xml.py
  16. 4 5
      python/sccd/statechart/static/action.py
  17. 2 2
      python/sccd/statechart/static/globals.py
  18. 29 34
      python/sccd/statechart/static/statechart.py
  19. 18 46
      python/sccd/statechart/static/tree.py
  20. 14 5
      python/sccd/test/codegen/rust.py
  21. 5 5
      python/sccd/test/dynamic/test_interpreter.py
  22. 15 5
      python/sccd/test/parser/xml.py
  23. 2 2
      rust/src/action_lang.rs
  24. 13 13
      rust/src/controller.rs
  25. 1 0
      test_files/day_atlee/statechart_fig10_counter.xml
  26. 3 1
      test_files/day_atlee/statechart_fig1_redialer.xml
  27. 4 1
      test_files/day_atlee/statechart_fig20_invar.xml
  28. 6 2
      test_files/day_atlee/statechart_fig7_dialer.xml
  29. 4 1
      test_files/day_atlee/statechart_fig9_trafficlight.xml
  30. 8 2
      test_files/day_atlee/test_19_swaptwice_combo.xml
  31. 5 1
      test_files/day_atlee/test_20_chemplant_combo.xml
  32. 3 1
      test_files/features/action_lang/test_guard_readonly.xml
  33. 38 0
      test_files/features/event_params/test_internalparam.xml
  34. 10 0
      test_files/features/instate/test_instate.xml
  35. 10 0
      test_files/features/instate/test_instate_nested.xml

+ 5 - 2
code_generation.txt

@@ -3,15 +3,17 @@
 Code generation with Rust
 Code generation with Rust
 =========================
 =========================
 
 
-50 tests currently passing (flag --rust):
+53 tests currently passing (flag --rust):
 
 
   features/instate/test_instate.xml
   features/instate/test_instate.xml
   features/instate/test_instate_nested.xml
   features/instate/test_instate_nested.xml
+  features/event_params/test_internalparam.xml
   features/action_lang/test_closure.xml
   features/action_lang/test_closure.xml
   features/action_lang/test_cond.xml
   features/action_lang/test_cond.xml
   features/action_lang/test_expressions.xml
   features/action_lang/test_expressions.xml
   features/action_lang/test_functions2.xml
   features/action_lang/test_functions2.xml
   features/action_lang/test_guard_action.xml
   features/action_lang/test_guard_action.xml
+  features/action_lang/test_guard_readonly.xml
   features/action_lang/test_nested.xml
   features/action_lang/test_nested.xml
   features/history/test_composite_shallow.xml
   features/history/test_composite_shallow.xml
   features/history/test_deep.xml
   features/history/test_deep.xml
@@ -21,13 +23,14 @@ Code generation with Rust
   features/after/test_after.xml
   features/after/test_after.xml
   features/after/test_after_reentry.xml
   features/after/test_after_reentry.xml
   features/parallel/test_parallel.xml
   features/parallel/test_parallel.xml
+  day_atlee/test_01_dialer_takemany.xml
+  day_atlee/test_01_dialer_takeone.xml
   day_atlee/test_02_counter_takeone.xml
   day_atlee/test_02_counter_takeone.xml
   day_atlee/test_03_trafficlight_single.xml
   day_atlee/test_03_trafficlight_single.xml
   day_atlee/test_04_counter_single.xml
   day_atlee/test_04_counter_single.xml
   day_atlee/test_06_counter_lifeline.xml
   day_atlee/test_06_counter_lifeline.xml
   day_atlee/test_13_invar_rhs_smallstep.xml
   day_atlee/test_13_invar_rhs_smallstep.xml
   day_atlee/test_21_counter_combo.xml
   day_atlee/test_21_counter_combo.xml
-  day_atlee/test_22_invar_combo_one.xml
   semantics/priority/test_explicit_flat.xml
   semantics/priority/test_explicit_flat.xml
   semantics/priority/test_explicit_ortho.xml
   semantics/priority/test_explicit_ortho.xml
   semantics/priority/test_source_child.xml
   semantics/priority/test_source_child.xml

+ 4 - 4
examples/digitalwatch/webassembly/index.html

@@ -255,8 +255,8 @@
         const status = rust.run_until(handle, realtime() + purposefully_behind, outHandler);
         const status = rust.run_until(handle, realtime() + purposefully_behind, outHandler);
         simtime = status.simtime;
         simtime = status.simtime;
 
 
-        if (status.reschedule) {
-          let sleep_duration = status.at - realtime();
+        if (!status.next_wakeup_infinity) {
+          let sleep_duration = status.next_wakeup - realtime();
           if (sleep_duration < 0) {
           if (sleep_duration < 0) {
             purposefully_behind = sleep_duration;
             purposefully_behind = sleep_duration;
             sleep_duration = 0;
             sleep_duration = 0;
@@ -273,12 +273,12 @@
       ["topLeft", "topRight", "bottomLeft", "bottomRight"].forEach(button => {
       ["topLeft", "topRight", "bottomLeft", "bottomRight"].forEach(button => {
         document.getElementById(button).onmousedown = () => {
         document.getElementById(button).onmousedown = () => {
           // console.log(button + "Pressed");
           // console.log(button + "Pressed");
-          const nextWakeup = rust.add_event(handle, realtime() - simtime, rust.InEvent["E_"+button+"Pressed"]);
+          rust.add_event(handle, realtime() - simtime, rust.InEvent["E_"+button+"Pressed"]);
           wakeup();
           wakeup();
         }
         }
         document.getElementById(button).onmouseup = () => {
         document.getElementById(button).onmouseup = () => {
           // console.log(button + "Released");
           // console.log(button + "Released");
-          const nextWakeup = rust.add_event(handle, realtime() - simtime, rust.InEvent["E_"+button+"Released"]);
+          rust.add_event(handle, realtime() - simtime, rust.InEvent["E_"+button+"Released"]);
           wakeup();
           wakeup();
         }
         }
       })
       })

+ 20 - 13
examples/digitalwatch/webassembly/wasm/lib.rs

@@ -44,31 +44,38 @@ pub fn setup(out: &OutputHandler) -> Handle {
 }
 }
 
 
 #[wasm_bindgen]
 #[wasm_bindgen]
-pub fn add_event(h: &mut Handle, delay: statechart::Timestamp, i: digitalwatch::InEvent) -> RunUntilResult {
+pub fn add_event(h: &mut Handle, delay: statechart::Timestamp, i: digitalwatch::InEvent) {
   h.controller.set_timeout(delay, i);
   h.controller.set_timeout(delay, i);
-  RunUntilResult::from_controller(&h.controller)
 }
 }
 
 
+// Wasm_bindgen cannot yet create bindings for enums with values (such as controller::Until) or tuples, so we translate it to a simple struct
 #[wasm_bindgen]
 #[wasm_bindgen]
 pub struct RunUntilResult {
 pub struct RunUntilResult {
   pub simtime: statechart::Timestamp,
   pub simtime: statechart::Timestamp,
-  pub reschedule: bool,
-  pub at: statechart::Timestamp,
+  pub next_wakeup_eternity: bool,
+  pub next_wakeup: statechart::Timestamp,
 }
 }
 
 
 impl RunUntilResult {
 impl RunUntilResult {
-  fn from_controller(c: &controller::Controller::<digitalwatch::InEvent>) -> Self {
-    let mut result = match c.get_earliest() {
-      controller::Until::Timestamp(t) => RunUntilResult{reschedule: true, at: t, simtime: 0},
-      controller::Until::Eternity => RunUntilResult{reschedule: false, at: 0, simtime: 0},
-    };
-    result.simtime = c.get_simtime();
-    result
+  fn new(simtime: statechart::Timestamp, next_wakeup: controller::Until) -> Self {
+    match next_wakeup {
+      controller::Until::Timestamp(t) => Self{
+        simtime,
+        next_wakeup_eternity: false,
+        next_wakeup: t,
+      },
+      controller::Until::Eternity => Self{
+        simtime,
+        next_wakeup_eternity: true,
+        next_wakeup: 0,
+      },
+    }
   }
   }
 }
 }
 
 
 #[wasm_bindgen]
 #[wasm_bindgen]
 pub fn run_until(h: &mut Handle, t: statechart::Timestamp, out: &OutputHandler) -> RunUntilResult {
 pub fn run_until(h: &mut Handle, t: statechart::Timestamp, out: &OutputHandler) -> RunUntilResult {
-  h.controller.run_until(&mut h.statechart, controller::Until::Timestamp(t), &mut |e|{ out.handle_output(e) });
-  RunUntilResult::from_controller(&h.controller)
+  let (simtime, next_wakeup) = h.controller.run_until(&mut h.statechart, controller::Until::Timestamp(t), &mut |e|{ out.handle_output(e) });
+
+  RunUntilResult::new(simtime, next_wakeup)
 }
 }

二进制
examples/digitalwatch/webassembly/wasm/pkg/dwatch_bg.wasm


+ 4 - 2
python/sccd/action_lang/codegen/rust.py

@@ -108,7 +108,7 @@ class ActionLangRustGenerator(Visitor):
     def write_parent_params(self, scope, with_identifiers=True):
     def write_parent_params(self, scope, with_identifiers=True):
         args = []
         args = []
         ctr = 1
         ctr = 1
-        while scope is not self.scope.root() and ctr <= scope.deepest_lookup:
+        while scope is not self.scope.root() and scope.deepest_lookup > 0:
             arg = ""
             arg = ""
             if with_identifiers:
             if with_identifiers:
                 arg += "parent%d: " % ctr
                 arg += "parent%d: " % ctr
@@ -122,8 +122,9 @@ class ActionLangRustGenerator(Visitor):
     def write_parent_call_params(self, scope, skip: int = 0):
     def write_parent_call_params(self, scope, skip: int = 0):
         args = []
         args = []
         ctr = 0
         ctr = 0
-        while scope is not self.scope.root() and ctr < scope.deepest_lookup:
+        while scope is not self.scope.root() and scope.deepest_lookup > 0:
             if ctr == skip:
             if ctr == skip:
+                # args.append("&mut scope")
                 args.append("&mut scope")
                 args.append("&mut scope")
             elif ctr > skip:
             elif ctr > skip:
                 args.append("parent%d" % (ctr-skip))
                 args.append("parent%d" % (ctr-skip))
@@ -346,4 +347,5 @@ class ActionLangRustGenerator(Visitor):
         self.w.write(type.name
         self.w.write(type.name
             .replace("int", "i32")
             .replace("int", "i32")
             .replace("float", "f64")
             .replace("float", "f64")
+            .replace("str", "&'static str")
         )
         )

+ 2 - 1
python/sccd/action_lang/parser/action_lang.g

@@ -57,7 +57,7 @@ array_indexed: atom "[" expr "]"
 func_decl: "func" params_decl stmt
 func_decl: "func" params_decl stmt
 params_decl: ( "(" param_decl ("," param_decl)* ")" )?
 params_decl: ( "(" param_decl ("," param_decl)* ")" )?
 ?param_decl: IDENTIFIER ":" type_annot
 ?param_decl: IDENTIFIER ":" type_annot
-type_annot: TYPE_INT | TYPE_STR | TYPE_DUR | TYPE_FLOAT
+type_annot: TYPE_INT | TYPE_STR | TYPE_DUR | TYPE_FLOAT | TYPE_BOOL
           | "func" param_types? return_type? -> func_type
           | "func" param_types? return_type? -> func_type
 
 
 param_types: "(" type_annot ( "," type_annot )* ")"
 param_types: "(" type_annot ( "," type_annot )* ")"
@@ -67,6 +67,7 @@ TYPE_INT: "int"
 TYPE_STR: "str"
 TYPE_STR: "str"
 TYPE_DUR: "dur"
 TYPE_DUR: "dur"
 TYPE_FLOAT: "float"
 TYPE_FLOAT: "float"
+TYPE_BOOL: "bool"
 
 
 array: "[" (expr ("," expr)*)? "]"
 array: "[" (expr ("," expr)*)? "]"
 
 

+ 13 - 8
python/sccd/cd/static/cd.py

@@ -33,11 +33,16 @@ class SingleInstanceCD(AbstractCD):
 
 
   def print(self):
   def print(self):
     print("%d states. %d transitions." % (len(self.statechart.tree.state_list), len(self.statechart.tree.transition_list)))
     print("%d states. %d transitions." % (len(self.statechart.tree.state_list), len(self.statechart.tree.transition_list)))
-    print("Internal events:")
-    for event_id in bm_items(self.statechart.internal_events):
-      print("  %s" % self.globals.events.get_name(event_id))
-    for outport in self.globals.outports.names:
-      print("Outport \"%s\" events:" % outport)
-      for event_name, port in self.statechart.event_outport.items():
-        if port == outport:
-          print("  %s" % event_name)
+    # if self.statechart.in_events:
+    #   print("Input events:")
+    #   for event_name in self.statechart.in_events:
+    #     print("  %s" % event_name)
+    # if len(self.statechart.out_events) > 0:
+    #   print("Output events:")
+    #   for event_name in self.statechart.out_events:
+    #     print("  %s" % event_name)
+    # if self.statechart.internal_events:
+    #   print("Internal events:")
+    #   for event_name in self.statechart.internal_events:
+    #     print("  %s" % event_name)
+    print()

+ 76 - 44
python/sccd/statechart/codegen/rust.py

@@ -1,5 +1,6 @@
 from typing import *
 from typing import *
 import io
 import io
+import itertools
 from sccd.action_lang.codegen.rust import *
 from sccd.action_lang.codegen.rust import *
 from sccd.statechart.static.tree import *
 from sccd.statechart.static.tree import *
 from sccd.util.visit_tree import *
 from sccd.util.visit_tree import *
@@ -85,6 +86,8 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.tree = None
         self.tree = None
         self.state_stack = []
         self.state_stack = []
 
 
+        self.trigger = None
+
     def get_parallel_states(self, state):
     def get_parallel_states(self, state):
         try:
         try:
             return self.parallel_state_cache[state]
             return self.parallel_state_cache[state]
@@ -178,30 +181,49 @@ class StatechartRustGenerator(ActionLangRustGenerator):
     def visit_SCCDStateConfiguration(self, type):
     def visit_SCCDStateConfiguration(self, type):
         self.w.write(self.get_parallel_states_tuple_type(type.state))
         self.w.write(self.get_parallel_states_tuple_type(type.state))
 
 
-    def visit_RaiseOutputEvent(self, event):
-        # TODO: evaluate event parameters
+    def write_event_params(self, event):
+        for param_eval_func in event.params:
+            param_eval_func.accept(self) # param eval is a function
+            self.w.write("(")
+            if self.trigger is not None:
+                self.w.write("%s, " % (self.get_parallel_states_tuple(self.state_stack[-1])))
+                self.write_trigger_params()
+            self.write_parent_call_params(param_eval_func.scope, skip=0) # call it!
+            self.w.write("), ")
+
+    def write_trigger_params(self):
+        for e in self.trigger.enabling:
+            for p in e.params_decl:
+                self.w.write("*%s, " % p.name)
+
+    def visit_RaiseOutputEvent(self, raise_event):
         if DEBUG:
         if DEBUG:
-            self.w.writeln("eprintln!(\"raise out %s:%s\");" % (event.outport, event.name))
-        # self.w.writeln("(output)(OutEvent::%s(%s{}));" % (ident_event_enum_variant(event.name), ident_event_type(event.name)))
-        self.w.writeln("(output)(OutEvent::%s);" % (ident_event_enum_variant(event.name)))
+            self.w.writeln("eprintln!(\"raise out %s\");" % (raise_event.name))
+        self.w.write("(output)(OutEvent::%s(" % (ident_event_enum_variant(raise_event.name)))
+        self.write_event_params(raise_event)
+        self.w.writeln("));")
 
 
-    def visit_RaiseInternalEvent(self, event):
+    def visit_RaiseInternalEvent(self, raise_event):
         if DEBUG:
         if DEBUG:
-            self.w.writeln("eprintln!(\"raise internal %s\");" % (event.name))
+            self.w.writeln("eprintln!(\"raise internal %s\");" % (raise_event.name))
         if self.internal_queue:
         if self.internal_queue:
-            # self.w.writeln("sched.set_timeout(%d, InEvent::%s(%s{}));" % (0, ident_event_enum_variant(event.name), ident_event_type(event.name)))
-            self.w.writeln("sched.set_timeout(%d, InEvent::%s);" % (0, ident_event_enum_variant(event.name)))
+            self.w.write("sched.set_timeout(%d, InEvent::%s(" % (0, ident_event_enum_variant(raise_event.name)))
+            self.write_event_params(raise_event)
+            self.w.writeln("));")
         else:
         else:
-            self.w.writeln("internal.raise().%s = Some(%s{});" % (ident_event_field(event.name), (ident_event_type(event.name))))
+            self.w.write("internal.raise().%s = Some(%s(" % (ident_event_field(raise_event.name), (ident_event_type(raise_event.name))))
+            self.write_event_params(raise_event)
+            self.w.writeln("));")
 
 
     def visit_Code(self, a):
     def visit_Code(self, a):
-        if a.block.scope.size() > 1:
-            raise UnsupportedFeature("Event parameters")
-
         self.w.write()
         self.w.write()
         a.block.accept(self) # block is a function
         a.block.accept(self) # block is a function
-        self.w.write("(%s, scope);" % self.get_parallel_states_tuple(self.state_stack[-1])) # call it!
-        self.w.writeln()
+        self.w.write("(") # call it...
+        if self.trigger is not None:
+            self.w.write("%s, " % self.get_parallel_states_tuple(self.state_stack[-1]))
+            self.write_trigger_params()
+        self.write_parent_call_params(a.block.scope, skip=0) # call it!
+        self.w.writeln(");")
 
 
     def visit_State(self, state):
     def visit_State(self, state):
         self.state_stack.append(state)
         self.state_stack.append(state)
@@ -244,19 +266,18 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.writeln("  fn enter_actions<Sched: statechart::Scheduler<InEvent=InEvent>>(timers: &mut Timers<Sched::TimerId>, data: &mut DataModel, internal: &mut InternalLifeline, sched: &mut Sched, output: &mut impl FnMut(OutEvent)) {")
         self.w.writeln("  fn enter_actions<Sched: statechart::Scheduler<InEvent=InEvent>>(timers: &mut Timers<Sched::TimerId>, data: &mut DataModel, internal: &mut InternalLifeline, sched: &mut Sched, output: &mut impl FnMut(OutEvent)) {")
         if DEBUG:
         if DEBUG:
             self.w.writeln("    eprintln!(\"enter %s\");" % state.full_name);
             self.w.writeln("    eprintln!(\"enter %s\");" % state.full_name);
-        self.w.writeln("    let scope = data;")
+        self.w.writeln("    let mut scope = data;")
         self.w.indent(); self.w.indent()
         self.w.indent(); self.w.indent()
         for a in state.enter:
         for a in state.enter:
             a.accept(self)
             a.accept(self)
         self.w.dedent(); self.w.dedent()
         self.w.dedent(); self.w.dedent()
         for a in state.after_triggers:
         for a in state.after_triggers:
-            # self.w.writeln("    timers[%d] = sched.set_timeout(%d, InEvent::%s(%s{}));" % (a.after_id, a.delay.opt, ident_event_enum_variant(a.enabling[0].name), ident_event_type(a.enabling[0].name)))
-            self.w.writeln("    timers[%d] = sched.set_timeout(%d, InEvent::%s);" % (a.after_id, a.delay.opt, ident_event_enum_variant(a.enabling[0].name)))
+            self.w.writeln("    timers[%d] = sched.set_timeout(%d, InEvent::%s());" % (a.after_id, a.delay.opt, ident_event_enum_variant(a.enabling[0].name)))
         self.w.writeln("  }")
         self.w.writeln("  }")
 
 
         # Enter actions: Executes exit actions of only this state
         # Enter actions: Executes exit actions of only this state
         self.w.writeln("  fn exit_actions<Sched: statechart::Scheduler<InEvent=InEvent>>(timers: &mut Timers<Sched::TimerId>, data: &mut DataModel, internal: &mut InternalLifeline, sched: &mut Sched, output: &mut impl FnMut(OutEvent)) {")
         self.w.writeln("  fn exit_actions<Sched: statechart::Scheduler<InEvent=InEvent>>(timers: &mut Timers<Sched::TimerId>, data: &mut DataModel, internal: &mut InternalLifeline, sched: &mut Sched, output: &mut impl FnMut(OutEvent)) {")
-        self.w.writeln("    let scope = data;")
+        self.w.writeln("    let mut scope = data;")
         for a in state.after_triggers:
         for a in state.after_triggers:
             self.w.writeln("    sched.unset_timeout(&timers[%d]);" % (a.after_id))
             self.w.writeln("    sched.unset_timeout(&timers[%d]);" % (a.after_id))
         self.w.indent(); self.w.indent()
         self.w.indent(); self.w.indent()
@@ -340,14 +361,11 @@ class StatechartRustGenerator(ActionLangRustGenerator):
 
 
         # Write event types
         # Write event types
         if self.internal_queue:
         if self.internal_queue:
-            input_events = sc.internal_events
-            internal_events = Bitmap()
+            input_events = {key: val for key,val in itertools.chain(sc.in_events.items(), sc.internal_events.items())}
+            internal_events = {}
         else:
         else:
-            input_events = sc.internal_events & ~sc.internally_raised_events
-            internal_events = sc.internally_raised_events
-
-        input_event_names = [self.globals.events.names[i] for i in bm_items(input_events)]
-        internal_event_names = [self.globals.events.names[i] for i in bm_items(internal_events)]
+            input_events = sc.in_events
+            internal_events = sc.internal_events
 
 
         internal_same_round = (
         internal_same_round = (
             sc.semantics.internal_event_lifeline == InternalEventLifeline.REMAINDER or
             sc.semantics.internal_event_lifeline == InternalEventLifeline.REMAINDER or
@@ -357,21 +375,27 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.writeln("#[cfg_attr(target_arch = \"wasm32\", wasm_bindgen)]")
         self.w.writeln("#[cfg_attr(target_arch = \"wasm32\", wasm_bindgen)]")
         self.w.writeln("#[derive(Copy, Clone, Debug)]")
         self.w.writeln("#[derive(Copy, Clone, Debug)]")
         self.w.writeln("pub enum InEvent {")
         self.w.writeln("pub enum InEvent {")
-        for event_name in input_event_names:
-            self.w.writeln("  %s," % (ident_event_enum_variant(event_name)))
+        for event_name in input_events:
+            self.w.write("  %s(" % (ident_event_enum_variant(event_name)))
+            for param_type in input_events[event_name]:
+                param_type.accept(self)
+                self.w.write(", ")
+            self.w.writeln("),")
         self.w.writeln("}")
         self.w.writeln("}")
         self.w.writeln()
         self.w.writeln()
 
 
         self.w.writeln("// Internal Events")
         self.w.writeln("// Internal Events")
-        for event_name in internal_event_names:
-            self.w.writeln("struct %s {" % ident_event_type(event_name))
-            self.w.writeln("  // TODO: internal event parameters")
-            self.w.writeln("}")
+        for event_name in internal_events:
+            self.w.write("struct %s(" % ident_event_type(event_name))
+            for param_type in internal_events[event_name]:
+                param_type.accept(self)
+                self.w.write(", ")
+            self.w.writeln(");")
 
 
         # Implement internal events as a set
         # Implement internal events as a set
         self.w.writeln("#[derive(Default)]")
         self.w.writeln("#[derive(Default)]")
         self.w.writeln("struct Internal {")
         self.w.writeln("struct Internal {")
-        for event_name in internal_event_names:
+        for event_name in internal_events:
             self.w.writeln("  %s: Option<%s>," % (ident_event_field(event_name), ident_event_type(event_name)))
             self.w.writeln("  %s: Option<%s>," % (ident_event_field(event_name), ident_event_type(event_name)))
         self.w.writeln("}")
         self.w.writeln("}")
 
 
@@ -386,13 +410,17 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.writeln()
         self.w.writeln()
 
 
         # Output events
         # Output events
-        output_event_names = self.globals.out_events.names
+        # output_event_names = self.globals.out_events.names
         self.w.writeln("// Output Events")
         self.w.writeln("// Output Events")
         self.w.writeln("#[cfg_attr(target_arch = \"wasm32\", wasm_bindgen)]")
         self.w.writeln("#[cfg_attr(target_arch = \"wasm32\", wasm_bindgen)]")
         self.w.writeln("#[derive(Copy, Clone, Debug, PartialEq, Eq)]")
         self.w.writeln("#[derive(Copy, Clone, Debug, PartialEq, Eq)]")
         self.w.writeln("pub enum OutEvent {")
         self.w.writeln("pub enum OutEvent {")
-        for event_name in output_event_names:
-            self.w.writeln("  %s," % (ident_event_enum_variant(event_name)))
+        for event_name in sc.out_events:
+            self.w.write("  %s(" % (ident_event_enum_variant(event_name)))
+            for param_type in sc.out_events[event_name]:
+                param_type.accept(self)
+                self.w.write(", ")
+            self.w.writeln("),")
         self.w.writeln("}")
         self.w.writeln("}")
         self.w.writeln()
         self.w.writeln()
 
 
@@ -582,6 +610,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
 
 
             def parent():
             def parent():
                 for i, t in enumerate(state.transitions):
                 for i, t in enumerate(state.transitions):
+                    self.trigger = t.trigger
                     self.w.writeln("// Outgoing transition %d" % i)
                     self.w.writeln("// Outgoing transition %d" % i)
 
 
                     # If a transition with an overlapping arena that is an ancestor of ours, we wouldn't arrive here because of the "break 'arena_label" statements.
                     # If a transition with an overlapping arena that is an ancestor of ours, we wouldn't arrive here because of the "break 'arena_label" statements.
@@ -600,25 +629,27 @@ class StatechartRustGenerator(ActionLangRustGenerator):
                     if t.trigger is not EMPTY_TRIGGER:
                     if t.trigger is not EMPTY_TRIGGER:
                         condition = []
                         condition = []
                         for e in t.trigger.enabling:
                         for e in t.trigger.enabling:
-                            if bit(e.id) & input_events:
-                                # condition.append("let Some(InEvent::%s(%s{})) = &input" % (ident_event_enum_variant(e.name), ident_event_type(e.name)))
-                                condition.append("let Some(InEvent::%s) = &input" % (ident_event_enum_variant(e.name)))
-                            elif bit(e.id) & internal_events:
-                                condition.append("let Some(%s) = &internal.current().%s" % (ident_event_type(e.name), ident_event_field(e.name)))
+                            if e.name in input_events:
+                                condition.append("let Some(InEvent::%s(%s)) = &input" % (ident_event_enum_variant(e.name), ", ".join(p.name for p in e.params_decl)))
+                            elif e.name in internal_events:
+                                condition.append("let Some(%s(%s)) = &internal.current().%s" % (ident_event_type(e.name), ", ".join(p.name for p in e.params_decl), ident_event_field(e.name)))
                             else:
                             else:
+                                print(e.name)
+                                print(input_events)
+                                print(internal_events)
                                 raise Exception("Illegal event ID - Bug in SCCD :(")
                                 raise Exception("Illegal event ID - Bug in SCCD :(")
                         self.w.writeln("if %s {" % " && ".join(condition))
                         self.w.writeln("if %s {" % " && ".join(condition))
                         self.w.indent()
                         self.w.indent()
 
 
+
+
                     if t.guard is not None:
                     if t.guard is not None:
-                        if t.guard.scope.size() > 1:
-                            raise UnsupportedFeature("Event parameters")
                         self.w.write("if ")
                         self.w.write("if ")
                         t.guard.accept(self) # guard is a function...
                         t.guard.accept(self) # guard is a function...
                         self.w.write("(") # call it!
                         self.w.write("(") # call it!
                         self.w.write(self.get_parallel_states_tuple(t.source))
                         self.w.write(self.get_parallel_states_tuple(t.source))
                         self.w.write(", ")
                         self.w.write(", ")
-                        # TODO: write event parameters here
+                        self.write_trigger_params()
                         self.write_parent_call_params(t.guard.scope)
                         self.write_parent_call_params(t.guard.scope)
                         self.w.write(")")
                         self.w.write(")")
                         self.w.writeln(" {")
                         self.w.writeln(" {")
@@ -686,6 +717,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
                         self.w.writeln("}")
                         self.w.writeln("}")
 
 
                     transitions_written.append(t)
                     transitions_written.append(t)
+                    self.trigger = None
 
 
             def child():
             def child():
                 # Here is were we recurse and write the transition code for the children of our 'state'.
                 # Here is were we recurse and write the transition code for the children of our 'state'.

+ 53 - 54
python/sccd/statechart/dynamic/candidate_generator.py

@@ -31,15 +31,15 @@ class GeneratorStrategy(ABC):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def key(self, execution, events_bitmap, forbidden_arenas):
+    def key(self, execution, events, forbidden_arenas):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def generate(self, execution, events_bitmap, forbidden_arenas):
+    def generate(self, execution, events, forbidden_arenas):
         pass
         pass
 
 
     @abstractmethod
     @abstractmethod
-    def filter_f(self, execution, enabled_events, events_bitmap):
+    def filter_f(self, execution, events):
         pass
         pass
 
 
 
 
@@ -49,63 +49,63 @@ class CurrentConfigStrategy(GeneratorStrategy):
     def cache_init(self):
     def cache_init(self):
         return {}
         return {}
 
 
-    def key(self, execution, events_bitmap, forbidden_arenas):
+    def key(self, execution, events, forbidden_arenas):
         return (execution.configuration, forbidden_arenas)
         return (execution.configuration, forbidden_arenas)
 
 
-    def generate(self, execution, events_bitmap, forbidden_arenas):
+    def generate(self, execution, events, forbidden_arenas):
         return [ t for t in self.priority_ordered_transitions
         return [ t for t in self.priority_ordered_transitions
                       if (t.source.state_id_bitmap & execution.configuration)
                       if (t.source.state_id_bitmap & execution.configuration)
                        and (not forbidden_arenas & t.arena_bitmap) ]
                        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)
+    def filter_f(self, execution, events):
+        return lambda t: (not t.trigger or t.trigger.check(events)) and execution.check_guard(t, events)
 
 
-# First filter based on current events, then filter on current state
-class EnabledEventsStrategy(GeneratorStrategy):
-    __slots__ = ["statechart"]
-    def __init__(self, priority_ordered_transitions, statechart):
-        super().__init__(priority_ordered_transitions)
-        self.statechart = statechart
+# # First filter based on current events, then filter on current state
+# class EnabledEventsStrategy(GeneratorStrategy):
+#     __slots__ = ["statechart"]
+#     def __init__(self, priority_ordered_transitions, statechart):
+#         super().__init__(priority_ordered_transitions)
+#         self.statechart = statechart
 
 
-    def cache_init(self):
-        cache = {}
-        cache[(0, 0)] = self.generate(None, 0, 0)
-        for event_id in bm_items(self.statechart.internal_events):
-            events_bitmap = bit(event_id)
-            cache[(events_bitmap, 0)] = self.generate(None, events_bitmap, 0)
-        return cache
+#     def cache_init(self):
+#         cache = {}
+#         cache[(0, 0)] = self.generate(None, 0, 0)
+#         for event_id in bm_items(self.statechart.internal_events):
+#             events_bitmap = bit(event_id)
+#             cache[(events_bitmap, 0)] = self.generate(None, events_bitmap, 0)
+#         return cache
 
 
-    def key(self, execution, events_bitmap, forbidden_arenas):
-        return (events_bitmap, forbidden_arenas)
+#     def key(self, execution, events, forbidden_arenas):
+#         return (events_bitmap, forbidden_arenas)
 
 
-    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.arena_bitmap) ]
+#     def generate(self, execution, events, forbidden_arenas):
+#         return [ t for t in self.priority_ordered_transitions
+#                       if (not t.trigger or t.trigger.check(events))
+#                       and (not forbidden_arenas & t.arena_bitmap) ]
 
 
-    def filter_f(self, execution, enabled_events, events_bitmap):
-        return lambda t: (execution.configuration & t.source.state_id_bitmap) and execution.check_guard(t, enabled_events)
+#     def filter_f(self, execution, events):
+#         return lambda t: (execution.configuration & t.source.state_id_bitmap) and execution.check_guard(t, enabled_events)
 
 
-class CurrentConfigAndEnabledEventsStrategy(GeneratorStrategy):
-    __slots__ = ["statechart"]
-    def __init__(self, priority_ordered_transitions, statechart):
-        super().__init__(priority_ordered_transitions)
-        self.statechart = statechart
+# class CurrentConfigAndEnabledEventsStrategy(GeneratorStrategy):
+#     __slots__ = ["statechart"]
+#     def __init__(self, priority_ordered_transitions, statechart):
+#         super().__init__(priority_ordered_transitions)
+#         self.statechart = statechart
 
 
-    def cache_init(self):
-        return {}
+#     def cache_init(self):
+#         return {}
 
 
-    def key(self, execution, events_bitmap, forbidden_arenas):
-        return (execution.configuration, events_bitmap, forbidden_arenas)
+#     def key(self, execution, events, forbidden_arenas):
+#         return (execution.configuration, events_bitmap, forbidden_arenas)
 
 
-    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.state_id_bitmap & execution.configuration)
-                      and (not forbidden_arenas & t.arena_bitmap) ]
+#     def generate(self, execution, events, forbidden_arenas):
+#         return [ t for t in self.priority_ordered_transitions
+#                       if (not t.trigger or t.trigger.check(events))
+#                       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)
+#     def filter_f(self, execution, events):
+#         return lambda t: execution.check_guard(t, enabled_events)
 
 
 
 
 class CandidateGenerator:
 class CandidateGenerator:
@@ -116,17 +116,17 @@ class CandidateGenerator:
 
 
     def generate(self, execution, enabled_events: List[InternalEvent], forbidden_arenas: Bitmap) -> List[Transition]:
     def generate(self, execution, enabled_events: List[InternalEvent], forbidden_arenas: Bitmap) -> List[Transition]:
         with timer.Context("generate candidates"):
         with timer.Context("generate candidates"):
-            events_bitmap = bm_from_list(e.id for e in enabled_events)
-            key = self.strategy.key(execution, events_bitmap, forbidden_arenas)
+            # events_bitmap = bm_from_list(e.id for e in enabled_events)
+            key = self.strategy.key(execution, enabled_events, forbidden_arenas)
 
 
             try:
             try:
                 candidates = self.cache[key]
                 candidates = self.cache[key]
                 ctr.cache_hits += 1
                 ctr.cache_hits += 1
             except KeyError:
             except KeyError:
-                candidates = self.cache[key] = self.strategy.generate(execution, events_bitmap, forbidden_arenas)
+                candidates = self.cache[key] = self.strategy.generate(execution, enabled_events, forbidden_arenas)
                 ctr.cache_misses += 1
                 ctr.cache_misses += 1
 
 
-            candidates = filter(self.strategy.filter_f(execution, enabled_events, events_bitmap), candidates)
+            candidates = filter(self.strategy.filter_f(execution, enabled_events), candidates)
 
 
             if DEBUG:
             if DEBUG:
                 candidates = list(candidates) # convert generator to list (gotta do this, otherwise the generator will be all used up by our debug printing
                 candidates = list(candidates) # convert generator to list (gotta do this, otherwise the generator will be all used up by our debug printing
@@ -153,23 +153,22 @@ class ConcurrentCandidateGenerator:
 
 
     def generate(self, execution, enabled_events: List[InternalEvent], forbidden_arenas: Bitmap) -> List[Transition]:
     def generate(self, execution, enabled_events: List[InternalEvent], forbidden_arenas: Bitmap) -> List[Transition]:
         with timer.Context("generate candidates"):
         with timer.Context("generate candidates"):
-            events_bitmap = bm_from_list(e.id for e in enabled_events)
+            events = list(enabled_events)
             transitions = []
             transitions = []
             while True:
             while True:
-                key = self.strategy.key(execution, events_bitmap, forbidden_arenas)
+                key = self.strategy.key(execution, events, forbidden_arenas)
                 try:
                 try:
                     candidates = self.cache[key]
                     candidates = self.cache[key]
                     ctr.cache_hits += 1
                     ctr.cache_hits += 1
                 except KeyError:
                 except KeyError:
-                    candidates = self.cache[key] = self.strategy.generate(execution, events_bitmap, forbidden_arenas)
+                    candidates = self.cache[key] = self.strategy.generate(execution, events, forbidden_arenas)
                     ctr.cache_misses += 1
                     ctr.cache_misses += 1
-                candidates = filter(self.strategy.filter_f(execution, enabled_events, events_bitmap), candidates)
+                candidates = filter(self.strategy.filter_f(execution, events), candidates)
                 t = next(candidates, None)
                 t = next(candidates, None)
                 if t:
                 if t:
                     transitions.append(t)
                     transitions.append(t)
                 else:
                 else:
                     break
                     break
-                if self.synchronous:
-                    events_bitmap |= t.raised_events
+                events.extend(InternalEvent(name=event_name, params=[]) for event_name in t.raised_events)
                 forbidden_arenas |= t.arena_bitmap
                 forbidden_arenas |= t.arena_bitmap
             return transitions
             return transitions

+ 6 - 7
python/sccd/statechart/dynamic/event.py

@@ -8,9 +8,8 @@ from sccd.util.duration import *
 # Input events are internal events too.
 # Input events are internal events too.
 @dataclass
 @dataclass
 class InternalEvent:
 class InternalEvent:
-    __slots__ = ["id", "name", "params"]
+    __slots__ = ["name", "params"]
 
 
-    id: int
     name: str # solely used for pretty printing
     name: str # solely used for pretty printing
     params: List[Any]
     params: List[Any]
 
 
@@ -22,22 +21,22 @@ class InternalEvent:
         return termcolor.colored(s, 'yellow')
         return termcolor.colored(s, 'yellow')
 
 
     __repr__ = __str__
     __repr__ = __str__
-    
 
 
 @dataclass
 @dataclass
 class OutputEvent:
 class OutputEvent:
-    __slots__ = ["port", "name", "params"]
+    __slots__ = ["name", "params"]
 
 
-    port: str
+    # port: str
     name: str
     name: str
     params: List[Any]
     params: List[Any]
 
 
     # Compare by value
     # Compare by value
     def __eq__(self, other):
     def __eq__(self, other):
-        return self.port == other.port and self.name == other.name and self.params == other.params
+        # return self.port == other.port and self.name == other.name and self.params == other.params
+        return self.name == other.name and self.params == other.params
 
 
     def __str__(self):
     def __str__(self):
-        s = "OutputEvent("+self.port+"."+self.name
+        s = "OutputEvent("+self.name
         if self.params:
         if self.params:
             s += str(self.params)
             s += str(self.params)
         s += ")"
         s += ")"

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

@@ -129,7 +129,7 @@ class SmallStep(Round):
     def _run(self, forbidden_arenas: Bitmap) -> RoundResult:
     def _run(self, forbidden_arenas: Bitmap) -> RoundResult:
         enabled_events = self.enabled_events()
         enabled_events = self.enabled_events()
         # The cost of sorting our enabled events is smaller than the benefit gained by having to loop less often over it in our transition execution code:
         # The cost of sorting our enabled events is smaller than the benefit gained by having to loop less often over it in our transition execution code:
-        enabled_events.sort(key=lambda e: e.id)
+        # enabled_events.sort(key=lambda e: e.id)
 
 
         transitions = self.generator.generate(self.execution, enabled_events, forbidden_arenas)
         transitions = self.generator.generate(self.execution, enabled_events, forbidden_arenas)
 
 
@@ -143,6 +143,6 @@ class SmallStep(Round):
             if t.target_stable:
             if t.target_stable:
                 stable_arenas |= arena
                 stable_arenas |= arena
             enabled_events = self.enabled_events()
             enabled_events = self.enabled_events()
-            enabled_events.sort(key=lambda e: e.id)
+            # enabled_events.sort(key=lambda e: e.id)
 
 
         return (dirty_arenas, stable_arenas)
         return (dirty_arenas, stable_arenas)

+ 6 - 30
python/sccd/statechart/dynamic/statechart_execution.py

@@ -118,27 +118,14 @@ class StatechartExecution:
         for after in triggers:
         for after in triggers:
             delay: Duration = after.delay.eval(
             delay: Duration = after.delay.eval(
                 EvalContext(memory=self.gc_memory, execution=self, params=[]))
                 EvalContext(memory=self.gc_memory, execution=self, params=[]))
-            self.timer_ids[after.after_id] = self.schedule_callback(delay, InternalEvent(id=after.id, name=after.name, params=[]), [self.instance])
+            self.timer_ids[after.after_id] = self.schedule_callback(delay, InternalEvent(name=after.name, params=[]), [self.instance])
 
 
     def _cancel_timers(self, triggers: List[AfterTrigger]):
     def _cancel_timers(self, triggers: List[AfterTrigger]):
         for after in triggers:
         for after in triggers:
             if self.timer_ids[after.after_id] is not None:
             if self.timer_ids[after.after_id] is not None:
                 self.cancel_callback(self.timer_ids[after.after_id])
                 self.cancel_callback(self.timer_ids[after.after_id])
                 self.timer_ids[after.after_id] = None
                 self.timer_ids[after.after_id] = None
-
-    # 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].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)
-        # if in_state:
-        #     print_debug("in state"+str(state_strings))
-        # else:
-        #     print_debug("not in state"+str(state_strings))
-        return in_state
-
+                
 
 
 def _perform_actions(ctx: EvalContext, actions: List[Action]):
 def _perform_actions(ctx: EvalContext, actions: List[Action]):
     for a in actions:
     for a in actions:
@@ -146,20 +133,9 @@ def _perform_actions(ctx: EvalContext, actions: List[Action]):
 
 
 
 
 def get_event_params(events: List[InternalEvent], trigger) -> List[any]:
 def get_event_params(events: List[InternalEvent], trigger) -> List[any]:
-    # Both 'events' and 'self.enabling' are sorted by event ID,
-    # this way we have to iterate over each of both lists at most once.
-    iterator = iter(trigger.enabling)
     params = []
     params = []
-    try:
-        event_decl = next(iterator)
-        for e in events:
-            if e.id < event_decl.id:
-                continue
-            else:
-                while e.id > event_decl.id:
-                    event_decl = next(iterator)
-                for p in e.params:
-                    params.append(p)
-    except StopIteration:
-        pass
+    for e in trigger.enabling:
+        for f in events:
+            if e.name == f.name:
+                params.extend(f.params)
     return params
     return params

+ 4 - 4
python/sccd/statechart/dynamic/statechart_instance.py

@@ -41,8 +41,8 @@ class StatechartInstance(Instance):
         with timer.Context("priority"):
         with timer.Context("priority"):
             priority_ordered_transitions = priority.priority_and_concurrency(statechart)
             priority_ordered_transitions = priority.priority_and_concurrency(statechart)
 
 
-        # strategy = CurrentConfigStrategy(priority_ordered_transitions)
-        strategy = EnabledEventsStrategy(priority_ordered_transitions, statechart)
+        strategy = CurrentConfigStrategy(priority_ordered_transitions)
+        # strategy = EnabledEventsStrategy(priority_ordered_transitions, statechart)
         # strategy = CurrentConfigAndEnabledEventsStrategy(priority_ordered_transitions, statechart)
         # strategy = CurrentConfigAndEnabledEventsStrategy(priority_ordered_transitions, statechart)
 
 
         if semantics.concurrency == Concurrency.SINGLE:
         if semantics.concurrency == Concurrency.SINGLE:
@@ -165,7 +165,7 @@ class StatechartInstance(Instance):
     # enter default states, generating a set of output events
     # enter default states, generating a set of output events
     def initialize(self):
     def initialize(self):
         self.execution.initialize()
         self.execution.initialize()
-        self.output_callback(OutputEvent(port="trace", name="big_step_completed", params=self.self_list))
+        self.output_callback(OutputEvent(name="big_step_completed", params=self.self_list))
 
 
     # perform a big step. generating a set of output events
     # perform a big step. generating a set of output events
     def big_step(self, input_events: List[InternalEvent]):
     def big_step(self, input_events: List[InternalEvent]):
@@ -175,4 +175,4 @@ class StatechartInstance(Instance):
             self.set_input(input_events)
             self.set_input(input_events)
             self._big_step.run_and_cycle_events()
             self._big_step.run_and_cycle_events()
 
 
-        self.output_callback(OutputEvent(port="trace", name="big_step_completed", params=self.self_list))
+        self.output_callback(OutputEvent(name="big_step_completed", params=self.self_list))

+ 6 - 3
python/sccd/statechart/parser/text.py

@@ -72,8 +72,7 @@ class Transformer(action_lang.Transformer):
 
 
   def event_decl(self, node):
   def event_decl(self, node):
     event_name = node[0].value
     event_name = node[0].value
-    event_id = self.globals.events.assign_id(event_name)
-    return EventDecl(id=event_id, name=event_name, params_decl=node[1])
+    return EventDecl(name=event_name, params_decl=node[1])
 
 
 import os, pathlib
 import os, pathlib
 grammar_dir = os.path.dirname(__file__)
 grammar_dir = os.path.dirname(__file__)
@@ -89,7 +88,7 @@ class TextParser(action_lang.TextParser):
   def __init__(self, globals):
   def __init__(self, globals):
     # Building the parser is actually the slowest step of parsing a statechart model.
     # Building the parser is actually the slowest step of parsing a statechart model.
     # Doesn't have to happen every time, so should find a way to speed this up.
     # Doesn't have to happen every time, so should find a way to speed this up.
-    parser = lark.Lark(grammar, parser="lalr", start=["expr", "block", "event_decl_list", "path", "semantic_choice"], transformer=Transformer(globals), cache=cache_file)
+    parser = lark.Lark(grammar, parser="lalr", start=["expr", "block", "type_annot", "event_decl_list", "path", "semantic_choice"], transformer=Transformer(globals), cache=cache_file)
     super().__init__(parser)
     super().__init__(parser)
 
 
   def parse_semantic_choice(self, text: str):
   def parse_semantic_choice(self, text: str):
@@ -100,3 +99,7 @@ class TextParser(action_lang.TextParser):
 
 
   def parse_path(self, text: str):
   def parse_path(self, text: str):
     return self.parser.parse(text, start="path")
     return self.parser.parse(text, start="path")
+
+  def parse_type(self, text: str):
+    return self.parser.parse(text, start="type_annot")
+    

+ 53 - 65
python/sccd/statechart/parser/xml.py

@@ -28,16 +28,7 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
   def parse_statechart(el):
   def parse_statechart(el):
     ext_file = el.get("src")
     ext_file = el.get("src")
     if ext_file is None:
     if ext_file is None:
-      statechart = Statechart(
-        semantics=SemanticConfiguration(),
-        scope=Scope("statechart", parent=BuiltIn),
-        datamodel=None,
-        internal_events=Bitmap(),
-        internally_raised_events=Bitmap(),
-        inport_events={},
-        event_outport={},
-        tree=None,
-      )
+      statechart = Statechart(scope=Scope("statechart", parent=None))
     else:
     else:
       if not load_external:
       if not load_external:
         raise SkipFile("Parser configured not to load statecharts from external files.")
         raise SkipFile("Parser configured not to load statecharts from external files.")
@@ -64,24 +55,24 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
       body.init_stmt(statechart.scope)
       body.init_stmt(statechart.scope)
       statechart.datamodel = body
       statechart.datamodel = body
 
 
-    def parse_inport(el):
-      port_name = require_attribute(el, "name")
-      globals.inports.assign_id(port_name)
-      def parse_event(el):
-        event_name = require_attribute(el, "name")
-        event_id = globals.events.assign_id(event_name)
-        port_events = statechart.inport_events.setdefault(port_name, Bitmap())
-        port_events |= bit(event_id)
-        statechart.inport_events[port_name] = port_events
-        statechart.internal_events |= bit(event_id)
-      return [("event+", parse_event)]
-
-    def parse_outport(el):
-      port_name = require_attribute(el, "name")
-      def parse_event(el):
-        event_name = require_attribute(el, "name")
-        statechart.event_outport[event_name] = port_name
-      return [("event+", parse_event)]
+    def parse_event_param(el):
+      type_text = require_attribute(el, "type")
+      param_type = text_parser.parse_type(type_text)
+      def finish_param():
+        return param_type
+      return ([], finish_param)
+
+    def get_port_parser(add_to):
+      def parse_port(el):
+        def parse_event(el):
+          event_name = require_attribute(el, "name")
+          if event_name in add_to:
+            raise XmlError("event already declared earlier: %s" % event_name)
+          def finish_event(*params):
+            add_to[event_name] = list(params)
+          return ([("param*", parse_event_param)], finish_event)
+        return [("event*", parse_event)]
+      return parse_port
 
 
     def parse_root(el):
     def parse_root(el):
       if el.get("id") is not None:
       if el.get("id") is not None:
@@ -118,7 +109,7 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
       def state_child_rules(parent, sibling_dict: Dict[str, State]):
       def state_child_rules(parent, sibling_dict: Dict[str, State]):
 
 
         # A transition's guard expression and action statements can read the transition's event parameters, and also possibly the current state configuration. We therefore now wrap these into a function with a bunch of parameters for those values that we want to bring into scope.
         # A transition's guard expression and action statements can read the transition's event parameters, and also possibly the current state configuration. We therefore now wrap these into a function with a bunch of parameters for those values that we want to bring into scope.
-        def wrap_transition_params(expr_or_stmt, trigger: Trigger):
+        def wrap_transition_params(expr_or_stmt, transition: Transition):
           if isinstance(expr_or_stmt, Statement):
           if isinstance(expr_or_stmt, Statement):
             # Transition's action code
             # Transition's action code
             body = expr_or_stmt
             body = expr_or_stmt
@@ -128,50 +119,54 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
           else:
           else:
             raise Exception("Unexpected error in parser")
             raise Exception("Unexpected error in parser")
           # The joy of writing expressions in abstract syntax:
           # The joy of writing expressions in abstract syntax:
-          wrapped = FunctionDeclaration(
-            params_decl=
-              # The param '@conf' (which, on purpose, is an illegal identifier in textual concrete syntax, to prevent naming collisions) will contain the statechart's configuration as a bitmap (SCCDInt). This parameter is currently only used in the expansion of the INSTATE-macro.
-              [ParamDecl(name="@conf", formal_type=SCCDStateConfiguration(state=parent))]
-              # Plus all the parameters of the enabling events of the transition's trigger:
-              + [param for event in trigger.enabling for param in event.params_decl],
-            body=body)
+          if transition is None:
+            wrapped = FunctionDeclaration(params_decl=[], body=body)
+          else:
+            wrapped = FunctionDeclaration(
+              params_decl=
+                # The param '@conf' (which, on purpose, is an illegal identifier in textual concrete syntax, to prevent naming collisions) will contain the statechart's configuration as a bitmap (SCCDInt). This parameter is currently only used in the expansion of the INSTATE-macro.
+                [ParamDecl(name="@conf", formal_type=SCCDStateConfiguration(state=parent))]
+                # Plus all the parameters of the enabling events of the transition's trigger:
+                + [param for event in transition.trigger.enabling for param in event.params_decl],
+              body=body)
           return wrapped
           return wrapped
 
 
-        def actions_rules(scope, wrap_trigger: Trigger = EMPTY_TRIGGER):
+        def actions_rules(scope, transition: Transition=None):
 
 
           def parse_raise(el):
           def parse_raise(el):
+            event_name = require_attribute(el, "event")
             params = []
             params = []
             def parse_param(el):
             def parse_param(el):
+              # Every event parameter becomes a function, with the event trigger's parameters as parameters
               expr_text = require_attribute(el, "expr")
               expr_text = require_attribute(el, "expr")
               expr = text_parser.parse_expr(expr_text)
               expr = text_parser.parse_expr(expr_text)
-              function = wrap_transition_params(expr, trigger=wrap_trigger)
+              function = wrap_transition_params(expr, transition=transition)
               function.init_expr(scope)
               function.init_expr(scope)
               function.scope.name = "event_param"
               function.scope.name = "event_param"
               params.append(function)
               params.append(function)
 
 
             def finish_raise():
             def finish_raise():
-              event_name = require_attribute(el, "event")
+              param_types = [p.return_type for p in params]
               try:
               try:
-                port = statechart.event_outport[event_name]
+                formal_param_types = statechart.out_events[event_name]
+                result = RaiseOutputEvent(name=event_name, params=params)
               except KeyError:
               except KeyError:
-                # Legacy fallback: read port from attribute
-                port = el.get("port")
-              if port is None:
-                # internal event
-                event_id = globals.events.assign_id(event_name)
-                statechart.internally_raised_events |= bit(event_id)
-                return RaiseInternalEvent(event_id=event_id, name=event_name, params=params)
-              else:
-                statechart.event_outport[event_name] = port
-                globals.outports.assign_id(port)
-                globals.out_events.assign_id(event_name)
-                return RaiseOutputEvent(name=event_name, params=params, outport=port)
+                try:
+                  formal_param_types = statechart.internal_events[event_name]
+                except KeyError:
+                  formal_param_types = param_types
+                  statechart.internal_events[event_name] = formal_param_types
+                result = RaiseInternalEvent(name=event_name, params=params)
+              if param_types != formal_param_types:
+                raise XmlError("Event '%s': Parameter types %s don't match earlier %s" % (event_name, param_types, formal_param_types))
+              return result
             return ([("param*", parse_param)], finish_raise)
             return ([("param*", parse_param)], finish_raise)
 
 
           def parse_code(el):
           def parse_code(el):
             def finish_code():
             def finish_code():
+              # Every block of code becomes a function, with the event trigger's parameters as parameters
               block = text_parser.parse_stmt(el.text)
               block = text_parser.parse_stmt(el.text)
-              function = wrap_transition_params(block, trigger=wrap_trigger)
+              function = wrap_transition_params(block, transition=transition)
               function.init_expr(scope)
               function.init_expr(scope)
               function.scope.name = "code"
               function.scope.name = "code"
               return Code(function)
               return Code(function)
@@ -244,7 +239,6 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
           # INSTATE-macro allowed in transition's guard and actions
           # INSTATE-macro allowed in transition's guard and actions
           text_parser.parser.options.transformer.set_macro("@in", macro_in_state)
           text_parser.parser.options.transformer.set_macro("@in", macro_in_state)
 
 
-
           if parent is root:
           if parent is root:
             raise XmlError("Root cannot be source of a transition.")
             raise XmlError("Root cannot be source of a transition.")
 
 
@@ -265,16 +259,10 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
 
 
             positive_events, negative_events = text_parser.parse_events_decl(event)
             positive_events, negative_events = text_parser.parse_events_decl(event)
 
 
-            # Optimization: sort events by ID
-            # Allows us to save time later.
-            positive_events.sort(key=lambda e: e.id)
-
             if not negative_events:
             if not negative_events:
               transition.trigger = Trigger(positive_events)
               transition.trigger = Trigger(positive_events)
-              statechart.internal_events |= transition.trigger.enabling_bitmap
             else:
             else:
               transition.trigger = NegatedTrigger(positive_events, negative_events)
               transition.trigger = NegatedTrigger(positive_events, negative_events)
-              statechart.internal_events |= transition.trigger.enabling_bitmap
 
 
           def parse_attr_after(after):
           def parse_attr_after(after):
             nonlocal after_id
             nonlocal after_id
@@ -286,14 +274,14 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
             # After-events should only be generated by the runtime.
             # After-events should only be generated by the runtime.
             # By putting a '+' in the event name (which isn't an allowed character in the parser), we ensure that the user will never accidentally (careless) or purposefully (evil) generate a valid after-event.
             # By putting a '+' in the event name (which isn't an allowed character in the parser), we ensure that the user will never accidentally (careless) or purposefully (evil) generate a valid after-event.
             event_name = "+%d" % after_id
             event_name = "+%d" % after_id
-            transition.trigger = AfterTrigger(globals.events.assign_id(event_name), event_name, after_id, after_expr)
-            statechart.internal_events |= transition.trigger.enabling_bitmap
+            statechart.in_events[event_name] = []
+            transition.trigger = AfterTrigger(event_name, after_id, after_expr)
             after_id += 1
             after_id += 1
 
 
           def parse_attr_cond(cond):
           def parse_attr_cond(cond):
             # Transition's guard expression
             # Transition's guard expression
             guard_expr = text_parser.parse_expr(cond)
             guard_expr = text_parser.parse_expr(cond)
-            guard_function = wrap_transition_params(guard_expr, transition.trigger)
+            guard_function = wrap_transition_params(expr_or_stmt=guard_expr, transition=transition)
             guard_type = guard_function.init_expr(statechart.scope)
             guard_type = guard_function.init_expr(statechart.scope)
             guard_function.scope.name = "guard"
             guard_function.scope.name = "guard"
 
 
@@ -313,7 +301,7 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
             # INSTATE-macro not allowed outside of transition's guard or actions
             # INSTATE-macro not allowed outside of transition's guard or actions
             text_parser.parser.options.transformer.unset_macro("@in")
             text_parser.parser.options.transformer.unset_macro("@in")
 
 
-          return (actions_rules(scope=statechart.scope, wrap_trigger=transition.trigger), finish_transition)
+          return (actions_rules(scope=statechart.scope, transition=transition), finish_transition)
 
 
         return {"state": parse_state, "parallel": parse_parallel, "history": parse_history, "onentry": parse_onentry, "onexit": parse_onexit, "transition": parse_transition}
         return {"state": parse_state, "parallel": parse_parallel, "history": parse_history, "onentry": parse_onentry, "onexit": parse_onexit, "transition": parse_transition}
 
 
@@ -336,7 +324,7 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
       return statechart
       return statechart
 
 
     if ext_file is None:
     if ext_file is None:
-      return ([("semantics?", parse_semantics), ("datamodel?", parse_datamodel), ("inport*", parse_inport), ("outport*", parse_outport), ("root", parse_root)], finish_statechart)
+      return ([("semantics?", parse_semantics), ("datamodel?", parse_datamodel), ("inport*", get_port_parser(statechart.in_events)), ("outport*", get_port_parser(statechart.out_events)), ("root", parse_root)], finish_statechart)
     else:
     else:
       return ([("override_semantics?", parse_semantics)], finish_statechart)
       return ([("override_semantics?", parse_semantics)], finish_statechart)
 
 

+ 4 - 5
python/sccd/statechart/static/action.py

@@ -50,7 +50,6 @@ class RaiseEvent(Action):
 
 
 @dataclass
 @dataclass
 class RaiseInternalEvent(RaiseEvent):
 class RaiseInternalEvent(RaiseEvent):
-    event_id: int
 
 
     def render(self) -> str:
     def render(self) -> str:
         return '^'+self.name
         return '^'+self.name
@@ -58,19 +57,19 @@ class RaiseInternalEvent(RaiseEvent):
     def exec(self, ctx: EvalContext):
     def exec(self, ctx: EvalContext):
         params = self._eval_params(ctx)
         params = self._eval_params(ctx)
         ctx.execution.raise_internal(
         ctx.execution.raise_internal(
-            InternalEvent(id=self.event_id, name=self.name, params=params))
+            InternalEvent(name=self.name, params=params))
 
 
 @dataclass
 @dataclass
 class RaiseOutputEvent(RaiseEvent):
 class RaiseOutputEvent(RaiseEvent):
-    outport: str
+    # outport: str
 
 
     def exec(self, ctx: EvalContext):
     def exec(self, ctx: EvalContext):
         params = self._eval_params(ctx)
         params = self._eval_params(ctx)
         ctx.execution.raise_output(
         ctx.execution.raise_output(
-            OutputEvent(port=self.outport, name=self.name, params=params))
+            OutputEvent(name=self.name, params=params))
 
 
     def render(self) -> str:
     def render(self) -> str:
-        return '^'+self.outport + '.' + self.name
+        return '^' + self.name
 
 
 @dataclass
 @dataclass
 class Code(Action):
 class Code(Action):

+ 2 - 2
python/sccd/statechart/static/globals.py

@@ -8,8 +8,8 @@ from sccd.common.exceptions import *
 class Globals:
 class Globals:
   def __init__(self):
   def __init__(self):
     # All the event names in the model
     # All the event names in the model
-    self.events = Namespace()
-    self.out_events = Namespace()
+    # self.events = Namespace()
+    # self.out_events = Namespace()
 
 
     self.inports = Namespace()
     self.inports = Namespace()
     self.outports = Namespace()
     self.outports = Namespace()

+ 29 - 34
python/sccd/statechart/static/statechart.py

@@ -2,45 +2,40 @@ from sccd.statechart.static.tree import *
 from sccd.action_lang.static.scope import *
 from sccd.action_lang.static.scope import *
 from sccd.statechart.static.semantic_configuration import *
 from sccd.statechart.static.semantic_configuration import *
 
 
-@dataclass
-class Statechart(Freezable, Visitable):
-  __slots__ = ["semantics", "scope", "datamodel", "internal_events", "internally_raised_events", "inport_events", "event_outport", "tree"]
+@dataclass(eq=False)
+class Statechart(Visitable):
+    # __slots__ = ["semantics", "scope", "datamodel", "in_events", "out_events", "internal_events", "tree"]
 
 
-  def __init__(self, semantics: SemanticConfiguration, scope: Scope, datamodel: Optional[Block], internal_events: Bitmap, internally_raised_events: Bitmap, inport_events: Dict[str, Set[int]], event_outport: Dict[str, str], tree: StateTree):
-    
-    super().__init__()
-  
     # Semantic configuration for statechart execution
     # Semantic configuration for statechart execution
-    self.semantics: SemanticConfiguration = semantics
+    semantics: SemanticConfiguration = field(default_factory=SemanticConfiguration)
 
 
     # Instance scope, the set of variable names (and their types and offsets in memory) that belong to the statechart
     # Instance scope, the set of variable names (and their types and offsets in memory) that belong to the statechart
-    self.scope: Scope = scope
+    scope: Scope = None # for datamodel
 
 
     # Block of statements setting up the datamodel (variables in instance scope)
     # Block of statements setting up the datamodel (variables in instance scope)
-    self.datamodel: Optional[Block] = datamodel
+    datamodel: Optional[Block] = None
 
 
-    # Union of internally raised and input events. Basically all the events that a transition could be triggered by.
-    self.internal_events: Bitmap = internal_events
+    # Mapping from event name to event parameter types
+    in_events: Dict[str, List[SCCDType]] = field(default_factory=dict)
+    
+    # Mapping from event name to event parameter types
+    out_events: Dict[str, List[SCCDType]] = field(default_factory=dict)
     
     
-    # 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
-
-    # Mapping from inport to set of event IDs - currently unused
-    self.inport_events: Dict[str, Bitmap] = inport_events
-    # Mapping from event name to outport
-    self.event_outport: Dict[str, str] = event_outport
-
-    self.tree: StateTree = tree
-
-  def generate_semantic_variants(self) -> List['Statechart']:
-    return [Statechart(
-        semantics=variant,
-        #  All other fields remain the same.
-        scope=self.scope,
-        datamodel=self.datamodel,
-        internal_events=self.internal_events,
-        internally_raised_events=self.internally_raised_events,
-        inport_events=self.inport_events,
-        event_outport=self.event_outport,
-        tree=self.tree)
-      for variant in self.semantics.generate_variants()]
+    # Mapping from event name to event parameter types
+    internal_events: Dict[str, List[SCCDType]] = field(default_factory=dict)
+
+    tree: StateTree = None
+
+
+    def generate_semantic_variants(self) -> List['Statechart']:
+        return [
+            Statechart(
+                semantics=variant,
+                #  All other fields remain the same.
+                scope=self.scope,
+                datamodel=self.datamodel,
+                in_events=self.in_events,
+                out_events=self.out_events,
+                internal_events=self.internal_events,
+                tree=self.tree)
+            for variant in self.semantics.generate_variants()]

+ 18 - 46
python/sccd/statechart/static/tree.py

@@ -142,9 +142,8 @@ class HistoryStateType(StateType):
 
 
 @dataclass
 @dataclass
 class EventDecl:
 class EventDecl:
-    __slots__ = ["id", "name", "params_decl"]
+    __slots__ = ["name", "params_decl"]
 
 
-    id: int
     name: str
     name: str
     params_decl: List[ParamDecl]
     params_decl: List[ParamDecl]
 
 
@@ -160,59 +159,38 @@ class Trigger:
 
 
     enabling: List[EventDecl]
     enabling: List[EventDecl]
 
 
-    def __post_init__(self):
-        # Optimization: Require 'enabling' to be sorted!
-        assert sorted(self.enabling, key=lambda e: e.id) == self.enabling
-
-        self.enabling_bitmap = bm_from_list(e.id for e in self.enabling)
-
-    def check(self, events_bitmap: Bitmap) -> bool:
-        return (self.enabling_bitmap & events_bitmap) == self.enabling_bitmap
+    def check(self, events: List[InternalEvent]) -> bool:
+        for e in self.enabling:
+            if e.name not in (e.name for e in events):
+                return False
+        return True
 
 
     def render(self) -> str:
     def render(self) -> str:
         return ' ∧ '.join(e.render() for e in self.enabling)
         return ' ∧ '.join(e.render() for e in self.enabling)
 
 
-    def copy_params_to_stack(self, events: List[InternalEvent], memory: MemoryInterface):
-        # Both 'events' and 'self.enabling' are sorted by event ID,
-        # this way we have to iterate over each of both lists at most once.
-        iterator = iter(self.enabling)
-        try:
-            event_decl = next(iterator)
-            offset = 0
-            for e in events:
-                if e.id < event_decl.id:
-                    continue
-                else:
-                    while e.id > event_decl.id:
-                        event_decl = next(iterator)
-                    for p in e.params:
-                        memory.store(offset, p)
-                        offset += 1
-        except StopIteration:
-            pass
-
 @dataclass
 @dataclass
 class NegatedTrigger(Trigger):
 class NegatedTrigger(Trigger):
     __slots__ = ["disabling", "disabling_bitmap"]
     __slots__ = ["disabling", "disabling_bitmap"]
 
 
     disabling: List[EventDecl]
     disabling: List[EventDecl]
 
 
-    def __post_init__(self):
-        Trigger.__post_init__(self)
-        self.disabling_bitmap = bm_from_list(e.id for e in self.disabling)
-
-    def check(self, events_bitmap: Bitmap) -> bool:
-        return Trigger.check(self, events_bitmap) and not (self.disabling_bitmap & events_bitmap)
+    def check(self, events: List[InternalEvent]) -> bool:
+        if not Trigger.check(self, events):
+            return False
+        for e in self.disabling:
+            if e.name in (e.name for e in events):
+                return False
+        return True
 
 
     def render(self) -> str:
     def render(self) -> str:
         return ' ∧ '.join(itertools.chain((e.render() for e in self.enabling), ('¬'+e.render() for e in self.disabling)))
         return ' ∧ '.join(itertools.chain((e.render() for e in self.enabling), ('¬'+e.render() for e in self.disabling)))
 
 
 class AfterTrigger(Trigger):
 class AfterTrigger(Trigger):
-    def __init__(self, id: int, name: str, after_id: int, delay: Expression):
-        enabling = [EventDecl(id=id, name=name, params_decl=[])]
+    def __init__(self, name: str, after_id: int, delay: Expression):
+        enabling = [EventDecl(name=name, params_decl=[])]
         super().__init__(enabling)
         super().__init__(enabling)
 
 
-        self.id = id
+        # self.id = id
         self.name = name
         self.name = name
         self.after_id = after_id # unique ID for AfterTrigger
         self.after_id = after_id # unique ID for AfterTrigger
         self.delay = delay
         self.delay = delay
@@ -220,12 +198,6 @@ class AfterTrigger(Trigger):
     def render(self) -> str:
     def render(self) -> str:
         return "after("+self.delay.render()+")"
         return "after("+self.delay.render()+")"
 
 
-    # Override.
-    # An 'after'-event also has 1 parameter, but it is not accessible to the user,
-    # hence the override.
-    def copy_params_to_stack(self, events: List[InternalEvent], memory: MemoryInterface):
-        pass
-
 EMPTY_TRIGGER = Trigger(enabling=[])
 EMPTY_TRIGGER = Trigger(enabling=[])
 
 
 @dataclass(eq=False)
 @dataclass(eq=False)
@@ -404,10 +376,10 @@ class StateTree:
                 if isinstance(t.target, State):
                 if isinstance(t.target, State):
                     target_stable = t.target.stable
                     target_stable = t.target.stable
 
 
-                raised_events = Bitmap()
+                raised_events = []
                 for a in t.actions:
                 for a in t.actions:
                     if isinstance(a, RaiseInternalEvent):
                     if isinstance(a, RaiseInternalEvent):
-                        raised_events |= bit(a.event_id)
+                        raised_events.append(a.name)
 
 
                 t.id = t_id
                 t.id = t_id
                 t.exit_mask = arena.descendants
                 t.exit_mask = arena.descendants

+ 14 - 5
python/sccd/test/codegen/rust.py

@@ -19,7 +19,7 @@ class TestRustGenerator(ClassDiagramRustGenerator):
         self.w.writeln("use sccd::statechart::SC;")
         self.w.writeln("use sccd::statechart::SC;")
         self.w.writeln("use sccd::statechart::Scheduler;")
         self.w.writeln("use sccd::statechart::Scheduler;")
         if DEBUG:
         if DEBUG:
-            self.w.writeln("debug_print_sizes::<Controller<InEvent>::TimerId>();")
+            self.w.writeln("debug_print_sizes::<Controller<InEvent>>();")
         self.w.writeln();
         self.w.writeln();
 
 
         self.w.writeln("// Setup ...")
         self.w.writeln("// Setup ...")
@@ -39,16 +39,25 @@ class TestRustGenerator(ClassDiagramRustGenerator):
                 raise UnsupportedFeature("Multiple simultaneous input events not supported")
                 raise UnsupportedFeature("Multiple simultaneous input events not supported")
             elif len(i.events) == 0:
             elif len(i.events) == 0:
                 raise UnsupportedFeature("Test declares empty bag of input events")
                 raise UnsupportedFeature("Test declares empty bag of input events")
-            # self.w.writeln("controller.set_timeout(%d, InEvent::%s(%s{}));" % (i.timestamp.opt, ident_event_enum_variant(i.events[0].name), ident_event_type(i.events[0].name)))
-            self.w.writeln("controller.set_timeout(%d, InEvent::%s);" % (i.timestamp.opt, ident_event_enum_variant(i.events[0].name)))
+
+            # Convert python value to rust literal
+            # Should find a better solution ...
+            def to_rust_literal(value):
+                if isinstance(value, int):
+                    return str(value)
+                elif isinstance(value, str):
+                    return '"%s"' % value
+                else:
+                    raise Exception("Incomplete implementation")
+
+            self.w.write("controller.set_timeout(%d, InEvent::%s(%s));" % (i.timestamp.opt, ident_event_enum_variant(i.events[0].name), ", ".join(to_rust_literal(p) for p in i.events[0].params)))
         self.w.writeln()
         self.w.writeln()
 
 
         self.w.writeln("// Run simulation, as-fast-as-possible")
         self.w.writeln("// Run simulation, as-fast-as-possible")
         self.w.writeln("controller.run_until(&mut sc, Until::Eternity, &mut output);")
         self.w.writeln("controller.run_until(&mut sc, Until::Eternity, &mut output);")
         self.w.writeln()
         self.w.writeln()
         self.w.writeln("// Check if output is correct")
         self.w.writeln("// Check if output is correct")
-        # self.w.writeln("assert_eq!(raised, [%s]);" % ", ".join("OutEvent::%s(%s{})" % (ident_event_enum_variant(e.name), ident_event_type(e.name)) for o in variant.output for e in o))
-        self.w.writeln("assert_eq!(raised, [%s]);" % ", ".join("OutEvent::%s" % (ident_event_enum_variant(e.name)) for o in variant.output for e in o))
+        self.w.writeln("assert_eq!(raised, [%s]);" % ", ".join("OutEvent::%s(%s)" % (ident_event_enum_variant(e.name), ", ".join(to_rust_literal(p) for p in e.params)) for o in variant.output for e in o))
 
 
         self.w.dedent()
         self.w.dedent()
         self.w.writeln("}")
         self.w.writeln("}")

+ 5 - 5
python/sccd/test/dynamic/test_interpreter.py

@@ -17,11 +17,11 @@ def run_variant(test: TestVariant, unittest):
   current_big_step = []
   current_big_step = []
   def on_output(event: OutputEvent):
   def on_output(event: OutputEvent):
     nonlocal current_big_step
     nonlocal current_big_step
-    if event.port == "trace":
-      if event.name == "big_step_completed":
-        if len(current_big_step) > 0:
-          pipe.put(current_big_step)
-        current_big_step = []
+    # if event.port == "trace":
+    if event.name == "big_step_completed":
+      if len(current_big_step) > 0:
+        pipe.put(current_big_step)
+      current_big_step = []
     else:
     else:
       current_big_step.append(event)
       current_big_step.append(event)
 
 

+ 15 - 5
python/sccd/test/parser/xml.py

@@ -28,8 +28,7 @@ def test_parser_rules(statechart_parser_rules):
         return expr
         return expr
 
 
       def make_input_event(name: str, params):
       def make_input_event(name: str, params):
-        event_id = globals.events.get_id(name)
-        return InternalEvent(id=event_id, name=name, params=params)
+        return InternalEvent(name=name, params=params)
 
 
       def parse_input_event(el):
       def parse_input_event(el):
         # port = require_attribute(el, "port")
         # port = require_attribute(el, "port")
@@ -65,9 +64,9 @@ def test_parser_rules(statechart_parser_rules):
 
 
         def parse_output_event(el):
         def parse_output_event(el):
           name = require_attribute(el, "name")
           name = require_attribute(el, "name")
-          port = require_attribute(el, "port")
+          # port = require_attribute(el, "port")
           params = []
           params = []
-          big_step.append(OutputEvent(name=name, port=port, params=params))
+          big_step.append(OutputEvent(name=name, params=params))
 
 
           def parse_param(el):
           def parse_param(el):
             val_text = require_attribute(el, "val")
             val_text = require_attribute(el, "val")
@@ -84,6 +83,17 @@ def test_parser_rules(statechart_parser_rules):
 
 
     def finish_test(statechart):
     def finish_test(statechart):
       globals.init_durations(delta=None)
       globals.init_durations(delta=None)
+      for bag in input:
+        for event in bag.events:
+          if event.name not in statechart.in_events:
+            raise XmlError("No such input event: %s" % event)
+
+      for big_step in output:
+        for event in big_step:
+          if event.name not in statechart.out_events:
+            raise XmlError("No such output event: %s" % event)
+
+
       variants = statechart.generate_semantic_variants()
       variants = statechart.generate_semantic_variants()
 
 
       def variant_description(i, variant) -> str:
       def variant_description(i, variant) -> str:
@@ -103,4 +113,4 @@ def test_parser_rules(statechart_parser_rules):
     sc_rules = statechart_parser_rules(globals, text_parser=text_parser)
     sc_rules = statechart_parser_rules(globals, text_parser=text_parser)
     return ([("statechart", sc_rules), ("input?", parse_input), ("output?", parse_output)], finish_test)
     return ([("statechart", sc_rules), ("input?", parse_input), ("output?", parse_output)], finish_test)
 
 
-  return parse_test
+  return parse_test

+ 2 - 2
rust/src/action_lang.rs

@@ -1,8 +1,8 @@
 
 
 // Requirements for working with time durations in SCCD:
 // Requirements for working with time durations in SCCD:
 //  1) Use of integer types. Floating point types have complex behavior when it comes to precision, mathematical operations introducing roundoff errors that are hard to predict.
 //  1) Use of integer types. Floating point types have complex behavior when it comes to precision, mathematical operations introducing roundoff errors that are hard to predict.
-//  2) The type system should prevent mixing up durations' units (e.g. assuming a value in milliseconds is a value in microsends).
-//  3) Under the hood, duration values should not all be converted to the same "base" unit (e.g. microseconds). There is always a tradeoff between the smallest duration expressable vs. the largest duration expressable, and the optimal tradeoff is model-specific.
+//  2) The type system should prevent mixing up durations' units (e.g. mistaking a value in milliseconds to be a value in microseconds).
+//  3) Under the hood, duration values should not all be converted to the same "base" unit (e.g. microseconds). Because we use integers, there is always a tradeoff between the smallest duration expressable vs. the largest duration expressable, and the optimal tradeoff is model-specific.
 //  4) There should be no additional runtime cost when performing arithmetic on durations of the same unit.
 //  4) There should be no additional runtime cost when performing arithmetic on durations of the same unit.
 
 
 use std::marker::PhantomData;
 use std::marker::PhantomData;

+ 13 - 13
rust/src/controller.rs

@@ -97,15 +97,15 @@ impl<InEvent> Default for Controller<InEvent> {
 }
 }
 
 
 impl<InEvent: Copy> Controller<InEvent> {
 impl<InEvent: Copy> Controller<InEvent> {
-  pub fn get_simtime(&self) -> Timestamp {
-    self.simtime
-  }
-  pub fn get_earliest(&self) -> Until {
-    match self.queue.peek() {
-      None => Until::Eternity,
-      Some(Reverse(entry)) => Until::Timestamp(entry.cmp.timestamp),
-    }
-  }
+  // pub fn get_simtime(&self) -> Timestamp {
+  //   self.simtime
+  // }
+  // pub fn get_earliest(&self) -> Until {
+  //   match self.queue.peek() {
+  //     None => Until::Eternity,
+  //     Some(Reverse(entry)) => Until::Timestamp(entry.cmp.timestamp),
+  //   }
+  // }
   fn cleanup_idx(idxs: &mut HashMap<Timestamp, TimerIndex>, entry: &QueueEntry<InEvent>) {
   fn cleanup_idx(idxs: &mut HashMap<Timestamp, TimerIndex>, entry: &QueueEntry<InEvent>) {
     if let hash_map::Entry::Occupied(o) = idxs.entry(entry.cmp.timestamp) {
     if let hash_map::Entry::Occupied(o) = idxs.entry(entry.cmp.timestamp) {
       if *o.get() == entry.cmp.idx+1 {
       if *o.get() == entry.cmp.idx+1 {
@@ -113,7 +113,7 @@ impl<InEvent: Copy> Controller<InEvent> {
       }
       }
     };
     };
   }
   }
-  pub fn run_until<OutEvent>(&mut self, sc: &mut impl SC<InEvent=InEvent, OutEvent=OutEvent, Sched=Self>, until: Until, output: &mut impl FnMut(OutEvent)) -> Until
+  pub fn run_until<OutEvent>(&mut self, sc: &mut impl SC<InEvent=InEvent, OutEvent=OutEvent, Sched=Self>, until: Until, output: &mut impl FnMut(OutEvent)) -> (Timestamp, Until)
   {
   {
     loop {
     loop {
       let Reverse(entry) = if let Some(peek_mut) = self.queue.peek_mut() {
       let Reverse(entry) = if let Some(peek_mut) = self.queue.peek_mut() {
@@ -128,7 +128,7 @@ impl<InEvent: Copy> Controller<InEvent> {
         // Check if event too far in the future
         // Check if event too far in the future
         if let Until::Timestamp(t) = until {
         if let Until::Timestamp(t) = until {
           if entry.cmp.timestamp > t {
           if entry.cmp.timestamp > t {
-            return until; // return next wakeup
+            return (self.simtime, until); // return next wakeup
           }
           }
         };
         };
         // OK, we'll handle the event
         // OK, we'll handle the event
@@ -139,10 +139,10 @@ impl<InEvent: Copy> Controller<InEvent> {
       };
       };
 
 
       // Handle event
       // Handle event
-      println!("time is now {}", self.simtime);
       self.simtime = entry.cmp.timestamp;
       self.simtime = entry.cmp.timestamp;
       sc.big_step(Some(entry.event), self, output);
       sc.big_step(Some(entry.event), self, output);
     };
     };
-    Until::Eternity
+    // Queue empty
+    (self.simtime, Until::Eternity)
   }
   }
 }
 }

+ 1 - 0
test_files/day_atlee/statechart_fig10_counter.xml

@@ -2,6 +2,7 @@
 <statechart>
 <statechart>
   <inport name="in">
   <inport name="in">
     <event name="tk0"/>
     <event name="tk0"/>
+    <event name="reset"/>
   </inport>
   </inport>
 
 
   <outport name="out">
   <outport name="out">

+ 3 - 1
test_files/day_atlee/statechart_fig1_redialer.xml

@@ -33,7 +33,9 @@
   </inport>
   </inport>
 
 
   <outport name="out">
   <outport name="out">
-    <event name="out"/>
+    <event name="out">
+      <param type="int"/>
+    </event>
   </outport>
   </outport>
 
 
   <root>
   <root>

+ 4 - 1
test_files/day_atlee/statechart_fig20_invar.xml

@@ -13,7 +13,10 @@
   </inport>
   </inport>
 
 
   <outport name="out">
   <outport name="out">
-    <event name="done"/>
+    <event name="done">
+      <param type="int"/>
+      <param type="int"/>
+    </event>
   </outport>
   </outport>
 
 
   <root initial="Invar">
   <root initial="Invar">

+ 6 - 2
test_files/day_atlee/statechart_fig7_dialer.xml

@@ -7,11 +7,15 @@
   </datamodel>
   </datamodel>
 
 
   <inport name="in">
   <inport name="in">
-    <event name="dial"/>
+    <event name="dial">
+      <param type="int"/>
+    </event>
   </inport>
   </inport>
 
 
   <outport name="out">
   <outport name="out">
-    <event name="out"/>
+    <event name="out">
+      <param type="int"/>
+    </event>
   </outport>
   </outport>
 
 
   <root initial="D">
   <root initial="D">

+ 4 - 1
test_files/day_atlee/statechart_fig9_trafficlight.xml

@@ -8,7 +8,10 @@
   </inport>
   </inport>
 
 
   <outport name="out">
   <outport name="out">
-    <event name="set_light"/>
+    <event name="set_light">
+      <param type="str"/>
+      <param type="str"/>
+    </event>
   </outport>
   </outport>
 
 
   <root>
   <root>

+ 8 - 2
test_files/day_atlee/test_19_swaptwice_combo.xml

@@ -16,12 +16,18 @@
     </datamodel>
     </datamodel>
 
 
     <inport name="in">
     <inport name="in">
-      <event name="set_a_b"/>
+      <event name="set_a_b">
+        <param type="int"/>
+        <param type="int"/>
+      </event>
       <event name="swap_twice"/>
       <event name="swap_twice"/>
     </inport>
     </inport>
 
 
     <outport name="out">
     <outport name="out">
-      <event name="swapped"/>
+      <event name="swapped">
+        <param type="int"/>
+        <param type="int"/>
+      </event>
     </outport>
     </outport>
 
 
 
 

+ 5 - 1
test_files/day_atlee/test_20_chemplant_combo.xml

@@ -17,10 +17,14 @@
     <inport name="in">
     <inport name="in">
       <event name="inc_one"/>
       <event name="inc_one"/>
       <event name="inc_two"/>
       <event name="inc_two"/>
+      <event name="end_process"/>
     </inport>
     </inport>
 
 
     <outport name="out">
     <outport name="out">
-      <event name="start_process"/>
+      <event name="start_process">
+        <param type="int"/>
+        <param type="int"/>
+      </event>
     </outport>
     </outport>
 
 
     <root>
     <root>

+ 3 - 1
test_files/features/action_lang/test_guard_readonly.xml

@@ -12,7 +12,9 @@
     </datamodel>
     </datamodel>
 
 
     <inport name="in">
     <inport name="in">
-      <event name="start"/>
+      <event name="start">
+        <param type="int"/>
+      </event>
     </inport>
     </inport>
     <outport name="out">
     <outport name="out">
       <event name="ok"/>
       <event name="ok"/>

+ 38 - 0
test_files/features/event_params/test_internalparam.xml

@@ -0,0 +1,38 @@
+<test>
+  <statechart>
+    <semantics internal_event_lifeline="next_combo_step"/>
+    <datamodel>
+      meaningoflife = 42;
+    </datamodel>
+    <inport>
+      <event name="start"/>
+    </inport>
+    <outport>
+      <event name="success"/>
+    </outport>
+    <root initial="A">
+      <state id="A">
+        <transition event="start" target=".">
+          <raise event="go">
+            <param expr="meaningoflife"/>
+          </raise>
+        </transition>
+        <transition event="go(meaning: int)" target="../B"
+          cond='meaning == 42'/>
+      </state>
+      <state id="B">
+        <onentry>
+          <raise event="success"/>
+        </onentry>
+      </state>
+    </root>
+  </statechart>
+  <input>
+    <event name="start" time="0 d"/>
+  </input>
+  <output>
+    <big_step>
+      <event name="success"/>
+    </big_step>
+  </output>
+</test>

+ 10 - 0
test_files/features/instate/test_instate.xml

@@ -1,5 +1,15 @@
 <test>
 <test>
   <statechart>
   <statechart>
+    <inport>
+      <event name="try"/>
+      <event name="to_d"/>
+    </inport>
+
+    <outport>
+      <event name="yes"/>
+      <event name="no"/>
+    </outport>
+    
     <root>
     <root>
       <parallel id="p">
       <parallel id="p">
         <state id="o0" initial="a">
         <state id="o0" initial="a">

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

@@ -1,6 +1,16 @@
 <test>
 <test>
   <!-- Interestingly, compiling this test with Rust -O3 produces an equally sized binary as test_instate.xml. This means that the anonymous function in the guard of our first transition is being inlined by the compiler. We can conclude that Rust brings zero-cost abstractions to our action language :) -->
   <!-- Interestingly, compiling this test with Rust -O3 produces an equally sized binary as test_instate.xml. This means that the anonymous function in the guard of our first transition is being inlined by the compiler. We can conclude that Rust brings zero-cost abstractions to our action language :) -->
   <statechart>
   <statechart>
+    <inport>
+      <event name="try"/>
+      <event name="to_d"/>
+    </inport>
+
+    <outport>
+      <event name="yes"/>
+      <event name="no"/>
+    </outport>
+    
     <root>
     <root>
       <parallel id="p">
       <parallel id="p">
         <state id="o0" initial="a">
         <state id="o0" initial="a">