Procházet zdrojové kódy

Fix Python (interpreted) version of digital watch example

Joeri Exelmans před 4 roky
rodič
revize
f169cbe3f4

+ 2 - 0
examples/digitalwatch/README.txt

@@ -18,6 +18,8 @@ webassembly/
 
         Then navigate your browser to http://localhost:8000/webassembly/
 
+        Note that this WebAssembly demo is only a proof-of-concept. Currently the package "wasm_bindgen", which generates bindings between WebAssembly and JavaScript, has very limited support for Algebraic Data Types, which we use for our input and output events.
+
     codegen/
         Generated code: This is a Rust crate produced by SCCD compiler (module "sccd.test.cmd.to_rust") from 'common/digitalwatch.xml'. Building it with 'cargo' produces a library.
 

+ 5 - 9
examples/digitalwatch/python/run.py

@@ -20,24 +20,20 @@ def main():
     gui.controller.send_event = on_gui_event
 
     def on_output(event: OutputEvent):
-        if event.port == "out":
+        # if event.port == "out":
             # print("out:", e.name)
             # the output event names happen to be functions on our GUI controller:
-            method = getattr(gui.controller, event.name)
+        method = getattr(gui.controller, event.name, None)
+        if method is not None:
             method()
 
     cd = load_cd("../common/digitalwatch.xml")
 
-    # from sccd.statechart.static import tree
-    # tree.concurrency_arena_orthogonal( cd.statechart.tree )
-    # # tree.concurrency_src_dst_orthogonal( cd.statechart.tree )
-    # exit()
-
     controller = Controller(cd, output_callback=on_output)
     eventloop = EventLoop(controller, TkInterImplementation(tk))
 
-    eventloop.start()
-    tk.mainloop()
+    eventloop.start() # Just marks the current wall-clock time as "time zero", and schedules the first controller wakeup with Tk.
+    tk.mainloop() # This actually runs Tk's eventloop in the current thread, with our own event loop interleaved.
 
 if __name__ == '__main__':
     main()

+ 6 - 1
notes.txt

@@ -37,13 +37,18 @@ Long-term vision:
 
 Random notes:
 
+  - Refactor XML parsing code to use domain-specific types for parsing rules instead of tuples, dicts and lists.
+
+  - (DONE) Currently event IDs are unique within a CD model. They only have to be unique within a statechart model.
+      DONE: Events are matched by name in the interpreter, and event names become enum variant identifiers in generated Rust code.
+
   - Explore per-state data models
       - Right now, there's a single datamodel for the entire statechart. All of the statechart's variables are declared there.
       - We could have datamodels per state: Variables declared in state A's enter actions would belong to A, and be readable/writable by any transition (guard, actions) sourcing from A's substates, as well as the enter- and exit actions of A's substates.
       - The benefit would be composability: 2 statechart models could be merged without having conflicting variables
         - The same non-conflicting merge could also be achieved by automatic renaming, though.
       - Another benefit would be savings on memory consumption: The memory consumed by the datamodel of 2 non-orthogonal states would only be the size of the biggest datamodel, instead of the sum of the 2 datamodels.
-      - Conclusion: Could be interesting for very large, composed, statecharts
+      - An argument against this, is that variables often serve as "communication" between states, or to "remember" stuff for as long as the statechart runs. Therefore, in many models, they would only be declared at the "root" state. There may not be a need to compose statecharts by merging them into one statechart, if we can instantiate and destroy instances of statechart models at runtime (the "CD" part of SCCD).
 
   - Durations in action language are a single type, consisting of an integer value and a unit. Perhaps it would be better to have a separate duration type for every unit. Especially in Rust it is better to leave the units to the type system, and only work with 'the numbers' in the machine code :)
 

+ 23 - 0
python/sccd/action_lang/static/types.py

@@ -2,6 +2,7 @@ from abc import *
 from dataclasses import *
 from typing import *
 import termcolor
+from sccd.util.duration import Duration
 from sccd.util.visitable import *
 from functools import reduce
 
@@ -87,10 +88,12 @@ class _SCCDSimpleType(SCCDType):
     def exp(self, other):
         return self.__dict_lookup(self.exp_dict, other)
 
+    # Can this type be '=='ed with other type?
     def is_eq(self, other):
         if other is self:
             return self.eq
 
+    # Can this type by compared to other type?
     def is_ord(self, other):
         if other is self:
             return self.ord
@@ -147,6 +150,26 @@ class SCCDClosureObject(SCCDType):
         return "Closure(scope=%s, func=%s)" % (self.scope.name, self.function_type._str())
 
 
+@dataclass(frozen=True, repr=False)
+class SCCDDurationFixedUnit(SCCDType):
+    unit: Duration
+
+    def _str(self):
+        return "dur_fixed(%s)" % self.unit
+
+    def is_summable(self, other):
+        return isinstance(other, SCCDDurationFixedUnit) and self.unit == other.unit
+
+    def is_eq(self, other):
+        return self.is_summable(other)
+
+    def is_ord(self, other):
+        return self.is_summable(other)
+
+    def mult(self, other):
+        return other is SCCDInt
+
+
 SCCDBool = _SCCDSimpleType("bool", eq=True, bool_cast=True)
 SCCDInt = _SCCDSimpleType("int", neg=True, summable=True, eq=True, ord=True, bool_cast=True)
 SCCDFloat = _SCCDSimpleType("float", neg=True, summable=True, eq=True, ord=True)

+ 2 - 0
python/sccd/cd/codegen/rust.py

@@ -8,3 +8,5 @@ class ClassDiagramRustGenerator:
         from sccd.statechart.codegen.rust import StatechartRustGenerator
         gen = StatechartRustGenerator(self.w, cd.globals)
         cd.statechart.accept(gen)
+
+        # self.w.write()

+ 10 - 9
python/sccd/controller/controller.py

@@ -61,10 +61,10 @@ class Controller:
     # Low-level utility function, intended to map a port name to a list of instances
     # For now, all known ports map to all instances (i.e. all ports are broadcast ports)
     def inport_to_instances(self, port: str) -> List[Instance]:
-        try:
-            self.cd.globals.inports.get_id(port)
-        except KeyError as e:
-            raise Exception("No such port: '%s'" % port) from e
+        # try:
+        #     self.cd.globals.inports.get_id(port)
+        # except KeyError as e:
+        #     raise Exception("No such port: '%s'" % port) from e
 
         # For now, we just broadcast all input events.
         # We don't even check if the event is allowed on the input port.
@@ -77,13 +77,14 @@ class Controller:
     # Higher-level way of adding an event to the queue.
     # See also method 'schedule'
     def add_input(self, timestamp: int, port: str, event_name: str, params = []):
-        try:
-            event_id = self.cd.globals.events.get_id(event_name)
-        except KeyError as e:
-            raise Exception("No such event: '%s'" % event_name) from e
+        # try:
+        #     event_id = self.cd.globals.events.get_id(event_name)
+        # except KeyError as e:
+        #     raise Exception("No such event: '%s'" % event_name) from e
 
         instances = self.inport_to_instances(port)
-        event = InternalEvent(event_id, event_name, params)
+        # event = InternalEvent(event_id, event_name, params)
+        event = InternalEvent(event_name, params)
 
         self.schedule(timestamp, [event], instances)
 

+ 1 - 2
python/sccd/util/duration.py

@@ -21,8 +21,7 @@ class _Unit:
       self.conv_dict[unit] = rel_size
       larger = unit.larger
 
-Day = _Unit("D")
-Hour = _Unit("h", (24, Day))
+Hour = _Unit("h")
 Minute = _Unit("m", (60, Hour))
 Second = _Unit("s", (60, Minute))
 Millisecond = _Unit("ms", (1000, Second))