Browse Source

More progress with Rust. Ported digital watch GUI to HTML + SVG.

Joeri Exelmans 4 years ago
parent
commit
b190e2fd4f

+ 3 - 0
examples/digitalwatch/README.txt

@@ -0,0 +1,3 @@
+The python script run.py runs the example.
+
+There's also run.html, which will be the browser version of the example. This will involve generating Rust code from the statechart model, and compiling Rust to WebAssembly.

BIN
examples/digitalwatch/font.ttf


+ 66 - 0
examples/digitalwatch/run.html

@@ -0,0 +1,66 @@
+<html>
+<head>
+  <title>DWatch</title>
+  <style type="text/css">
+    body {
+      background-color: #000;
+      color: #fff;
+    }
+    #canvas{
+      /* center on page */
+      position: absolute;
+      top:0; left:0; bottom:0; right:0;
+      margin: auto;
+    }
+    @font-face{
+      font-family: 'digital-font';
+      src: url('font.ttf');
+    }
+    #displayText{
+      font: 28px digital-font;
+    }
+    /* disable text selection */
+    svg text {
+      cursor: default;
+        -webkit-user-select: none;
+           -moz-user-select: none;
+            -ms-user-select: none;
+                user-select: none;
+    }
+  </style>
+</head>
+<body>
+  <svg version="1.1"
+     baseProfile="full"
+     width="222" height="236"
+     xmlns="http://www.w3.org/2000/svg"
+     id="canvas">
+
+    <image width="222" height="236" xlink:href="watch.gif"/>
+
+      <rect id="display" x="51" y="95" width="120" height="55" rx="2" fill="#DCDCDC">
+      </rect>
+      <text id="displayText" x="111" y="126" dominant-baseline="middle" text-anchor="middle">12:00:00</text>
+
+    <rect id="topLeft" x="3" y="62", width="10", height="10" fill="#fff" fill-opacity="0.2" />
+    <rect id="topRight" x="209" y="60", width="10", height="10" fill="#fff" fill-opacity="0.2" />
+    <rect id="bottomLeft" x="2" y="161", width="10", height="10" fill="#fff" fill-opacity="0.2" />
+    <rect id="bottomRight" x="211" y="161", width="10", height="10" fill="#fff" fill-opacity="0.2" />
+
+    <image id="note" x="54" y="96" xlink:href="noteSmall.gif"/>
+  </svg>
+  <p id="log">
+  </p>
+
+  <script type="text/javascript">
+    function log(msg) {
+      // document.getElementById("log").innerText += msg + '\n';
+    }
+
+    ["topLeft", "topRight", "bottomLeft", "bottomRight"].forEach(button => {
+      document.getElementById(button).onmousedown = () => log(button + " pressed");
+      document.getElementById(button).onmouseup = () => log(button + " released");
+    })
+  </script>
+</body>
+</html>

+ 6 - 0
src/sccd/cd/codegen/rust.py

@@ -0,0 +1,6 @@
+from sccd.statechart.codegen.rust import *
+
+class ClassDiagramRustGenerator(StatechartRustGenerator):
+
+    def visit_SingleInstanceCD(self, cd):
+        cd.statechart.accept(self)

+ 1 - 1
src/sccd/cd/static/cd.py

@@ -5,7 +5,7 @@ from sccd.statechart.static.statechart import *
 from sccd.statechart.static.globals import *
 
 @dataclass
-class AbstractCD(ABC):
+class AbstractCD(ABC, Visitable):
   globals: Globals
 
   @abstractmethod

+ 0 - 61
src/sccd/controller/test_event_queue.py

@@ -1,61 +0,0 @@
-import unittest
-from event_queue import EventQueue
-  
-
-class TestEventQueue(unittest.TestCase):
-
-  def test_add_pop(self):
-    q = EventQueue()
-    self.assertEqual(q.earliest_timestamp(), None)
-    q.add(10, 'a')
-    q.add(11, 'b')
-    q.add(11, 'c')
-    q.add(11, 'd')
-    q.add(11, 'e')
-    q.remove('c')
-    q.add(11, 'f')
-    q.add(11, 'g')
-    self.assertEqual(q.earliest_timestamp(), 10)
-    self.assertEqual(q.pop(), (10,'a'))
-    self.assertEqual(q.pop(), (11,'b'))
-    self.assertEqual(q.pop(), (11,'d'))
-    self.assertEqual(q.pop(), (11,'e'))
-    self.assertEqual(q.pop(), (11,'f'))
-    self.assertEqual(q.pop(), (11,'g'))
-    self.assertEqual(q.earliest_timestamp(), None)
-
-  def test_add_remove(self):
-    q = EventQueue()
-
-    class X:
-      n = 0
-      def __init__(self):
-        self.x = X.n
-        X.n += 1
-      def __repr__(self):
-        return "x%d"%self.x
-
-    def testrange(N):
-      Xs = []
-      for i in range(10):
-        x = X()
-        Xs.append(x)
-        q.add(i, x)
-      for x in Xs:
-        q.remove(x)
-
-    testrange(1)
-    testrange(10)
-    testrange(100)
-    testrange(1000)
-
-  def test_due(self):
-    q = EventQueue()
-    q.add(10, 'a')
-    q.add(11, 'b')
-    q.add(12, 'c')
-    q.add(20, 'd')
-    ctr = 0
-    for x in q.due(15):
-      ctr += 1
-    self.assertEqual(ctr, 3)

+ 0 - 0
src/sccd/legacy/__init__.py


+ 0 - 23
src/sccd/legacy/sccd-core.xsd

@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<xsd:schema
-  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
-  xmlns:sccd="msdl.uantwerpen.be/sccd"
-  targetNamespace="msdl.uantwerpen.be/sccd"
-  elementFormDefault="qualified"
-  attributeFormDefault="unqualified">
-
-  <xsd:element name="scxml">
-    <xsd:complexType>
-      <xsd:complexContent>
-        <xsd:extension base="sccd:orState">
-          <xsd:attributeGroup ref="sccd:semanticOptions"/>
-        </xsd:extension>
-      </xsd:complexContent>
-    </xsd:complexType>
-    <xsd:unique name="uniqueScxmlChildren">
-      <xsd:selector xpath="state|parallel|history"/>
-      <xsd:field xpath="@id"/>
-    </xsd:unique>
-  </xsd:element>
-  
-</xsd:schema>

+ 0 - 349
src/sccd/legacy/sccd.xsd

@@ -1,349 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<xsd:schema
-  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
-  xmlns:sccd="msdl.uantwerpen.be/sccd"
-  targetNamespace="msdl.uantwerpen.be/sccd"
-  elementFormDefault="qualified"
-  attributeFormDefault="unqualified">
-
-  <xsd:include schemaLocation="sccd-core.xsd"/>
-
-  <!-- First, some type declarations... -->
-
-  <xsd:simpleType name="identifier">
-    <xsd:restriction base="xsd:string">
-      <xsd:pattern value="([A-Za-z0-9]|_)+"/>
-    </xsd:restriction>
-  </xsd:simpleType>
-
-  <xsd:simpleType name="stateId">
-    <xsd:restriction base="sccd:identifier"/>
-    <!-- <xsd:restriction base="xsd:ID"/> -->
-  </xsd:simpleType>
-
-  <xsd:simpleType name="portName">
-    <xsd:restriction base="sccd:identifier"/>
-  </xsd:simpleType>
-
-  <xsd:simpleType name="className">
-    <xsd:restriction base="sccd:identifier"/>
-  </xsd:simpleType>
-
-  <xsd:simpleType name="eventName">
-    <xsd:restriction base="sccd:identifier"/>
-  </xsd:simpleType>
-
-  <xsd:simpleType name="type">
-    <xsd:restriction base="xsd:string">
-      <xsd:enumeration value="string"/>
-      <xsd:enumeration value="int"/>
-    </xsd:restriction>
-  </xsd:simpleType>
-
-  <xsd:simpleType name="expression">
-    <xsd:restriction base="xsd:string">
-      <!-- todo -->
-    </xsd:restriction>
-  </xsd:simpleType>
-
-  <!-- maybe use xsd:duration or some other type that supports units in the future -->
-  <xsd:simpleType name="duration">
-    <xsd:restriction base="xsd:float"/>
-  </xsd:simpleType>
-
-  <xsd:complexType name="method">
-    <xsd:sequence>
-      <xsd:element name="parameter" minOccurs="0" maxOccurs="unbounded">
-        <xsd:complexType>
-          <xsd:attribute name="name" type="sccd:identifier"/>
-        </xsd:complexType>
-      </xsd:element>
-      <xsd:element name="body" type="xsd:string"/>
-    </xsd:sequence>
-  </xsd:complexType>
-
-  <!-- Used for 'onentry', 'onexit' and 'transition' -->
-  <xsd:group name="action">
-    <xsd:choice>
-      <xsd:element name="raise">
-        <xsd:complexType>
-          <xsd:sequence>
-            <xsd:element name="parameter" minOccurs="0" maxOccurs="unbounded">
-              <xsd:complexType>
-                <xsd:attribute name="expr" type="sccd:expression"/>
-              </xsd:complexType>
-            </xsd:element>
-          </xsd:sequence>
-          <xsd:attribute name="event" type="sccd:eventName" use="required"/>
-          <xsd:attribute name="target" type="sccd:expression"/>
-          <xsd:attribute name="port" type="sccd:portName"/>
-          <xsd:attribute name="scope">
-            <xsd:simpleType>
-              <xsd:restriction base="xsd:string">
-                <xsd:enumeration value="cd"/>
-                <xsd:enumeration value="narrow"/>
-                <xsd:enumeration value="broad"/>
-              </xsd:restriction>
-            </xsd:simpleType>
-          </xsd:attribute>
-        </xsd:complexType>
-      </xsd:element>
-      <xsd:element name="assign">
-        <xsd:complexType>
-          <xsd:attribute name="ident" type="sccd:expression"/>
-          <xsd:attribute name="expr" type="sccd:expression"/>
-        </xsd:complexType>
-      </xsd:element>
-      <xsd:element name="script" type="xsd:string"/>
-      <xsd:element name="log" type="xsd:string"/>
-    </xsd:choice>
-  </xsd:group>
-
-  <xsd:complexType name="actionSequence">
-    <xsd:group ref="sccd:action" maxOccurs="unbounded"/>
-  </xsd:complexType>
-
-  <xsd:complexType name="pseudoState" abstract="true">
-    <xsd:attribute name="id" type="sccd:stateId"/>
-  </xsd:complexType>
-
-  <xsd:complexType name="commonState">
-    <xsd:complexContent>
-      <xsd:extension base="sccd:pseudoState">
-        <xsd:sequence>
-          <xsd:element name="onentry" minOccurs="0" type="sccd:actionSequence"/>
-          <xsd:element name="onexit" minOccurs="0" type="sccd:actionSequence"/>
-          <xsd:choice minOccurs="0" maxOccurs="unbounded">
-            <xsd:element name="state" type="sccd:orState">
-              <xsd:unique name="uniqueOrStateChildren">
-                <xsd:selector xpath="state|parallel|history"/>
-                <xsd:field xpath="@id"/>
-              </xsd:unique>
-            </xsd:element>
-            <xsd:element name="parallel" type="sccd:commonState">
-              <xsd:unique name="uniqueAndStateChildren">
-                <xsd:selector xpath="state|parallel|history"/>
-                <xsd:field xpath="@id"/>
-              </xsd:unique>
-            </xsd:element>
-            <xsd:element name="history">
-              <xsd:complexType>
-                <xsd:complexContent>
-                  <xsd:extension base="sccd:pseudoState">
-                    <xsd:sequence>
-                      <!-- history pseudostate can have 1 <transition> element to indicate default history state -->
-                      <!-- this is not an actual transition -->
-                      <xsd:element name="transition" minOccurs="0">
-                        <xsd:complexType>
-                          <xsd:attribute name="target" type="xsd:string"/>
-                        </xsd:complexType>
-                      </xsd:element>
-                    </xsd:sequence>
-                    <!-- <xsd:attribute name="id" type="sccd:stateId" use="required"/> -->
-                    <xsd:attribute name="type">
-                      <xsd:simpleType>
-                        <xsd:restriction base="xsd:string">
-                          <xsd:enumeration value="shallow"/>
-                          <xsd:enumeration value="deep"/>
-                        </xsd:restriction>
-                      </xsd:simpleType>
-                    </xsd:attribute>
-                  </xsd:extension>
-                </xsd:complexContent>
-              </xsd:complexType>
-            </xsd:element>
-            <xsd:element name="transition">
-              <xsd:complexType>
-                <xsd:sequence>
-                  <xsd:element name="parameter" minOccurs="0" maxOccurs="unbounded">
-                    <xsd:complexType>
-                      <xsd:attribute name="name" type="sccd:identifier"/>
-                      <xsd:attribute name="type" type="sccd:type"/>
-                    </xsd:complexType>
-                  </xsd:element>
-                  <xsd:group ref="sccd:action" minOccurs="0" maxOccurs="unbounded"/>
-                </xsd:sequence>
-                <xsd:attribute name="target" type="xsd:string" use="required"/>
-                <xsd:attribute name="event" type="sccd:eventName"/>
-                <xsd:attribute name="after" type="sccd:duration"/>
-                <xsd:attribute name="cond" type="sccd:expression"/>
-                <xsd:attribute name="port" type="sccd:portName"/>
-              </xsd:complexType>
-            </xsd:element>
-          </xsd:choice>
-        </xsd:sequence>
-      </xsd:extension>
-    </xsd:complexContent>
-  </xsd:complexType>
-
-  <xsd:complexType name="orState">
-    <xsd:complexContent>
-      <xsd:extension base="sccd:commonState">
-        <xsd:attribute name="initial" type="sccd:stateId"/>
-      </xsd:extension>
-    </xsd:complexContent>
-  </xsd:complexType>
-
-  <xsd:attributeGroup name="semanticOptions">
-    <xsd:attribute name="big_step_maximality">
-      <xsd:simpleType>
-        <xsd:restriction base="xsd:string">
-          <xsd:enumeration value="take_one"/>
-          <xsd:enumeration value="take_many"/>
-        </xsd:restriction>
-      </xsd:simpleType>
-    </xsd:attribute>
-    <xsd:attribute name="combo_step_maximality">
-      <xsd:simpleType>
-        <xsd:restriction base="xsd:string">
-          <xsd:enumeration value="combo_take_one"/>
-          <xsd:enumeration value="combo_take_many"/>
-        </xsd:restriction>
-      </xsd:simpleType>
-    </xsd:attribute>
-    <xsd:attribute name="priority">
-      <xsd:simpleType>
-        <xsd:restriction base="xsd:string">
-          <xsd:enumeration value="source_parent"/>
-          <xsd:enumeration value="source_child"/>
-        </xsd:restriction>
-      </xsd:simpleType>
-    </xsd:attribute>
-    <xsd:attribute name="internal_event_lifeline">
-      <xsd:simpleType>
-        <xsd:restriction base="xsd:string">
-          <xsd:enumeration value="next_small_step"/>
-          <xsd:enumeration value="next_combo_step"/>
-          <xsd:enumeration value="queue"/>
-        </xsd:restriction>
-      </xsd:simpleType>
-    </xsd:attribute>
-    <xsd:attribute name="input_event_lifeline">
-      <xsd:simpleType>
-        <xsd:restriction base="xsd:string">
-          <xsd:enumeration value="whole"/>
-          <xsd:enumeration value="first_small_step"/>
-          <xsd:enumeration value="first_combo_step"/>
-        </xsd:restriction>
-      </xsd:simpleType>
-    </xsd:attribute>
-    <xsd:attribute name="concurrency">
-      <xsd:simpleType>
-        <xsd:restriction base="xsd:string">
-          <xsd:enumeration value="single"/>
-          <xsd:enumeration value="many"/>
-        </xsd:restriction>
-      </xsd:simpleType>
-    </xsd:attribute>
-  </xsd:attributeGroup>
-
-  <xsd:complexType name="portDecl">
-    <xsd:attribute name="name" type="sccd:portName"/>
-  </xsd:complexType>
-
-  <!-- Root element -->
-  <xsd:element name="diagram">
-    <xsd:complexType>
-      <xsd:sequence>
-        <xsd:element name="description" type="xsd:string" minOccurs="0"/>
-        <xsd:element name="inport" minOccurs="0" maxOccurs="unbounded" type="sccd:portDecl"/>
-        <xsd:element name="outport" minOccurs="0" maxOccurs="unbounded" type="sccd:portDecl"/>
-
-        <xsd:element name="class" maxOccurs="unbounded">
-          <xsd:complexType>
-            <xsd:sequence>
-              <xsd:element name="relationships" minOccurs="0">
-                <xsd:complexType>
-                  <xsd:choice maxOccurs="unbounded">
-                    <xsd:element name="inheritance">
-                      <xsd:complexType>
-                        <xsd:attribute name="class" type="sccd:className" use="required"/>
-                      </xsd:complexType>
-                    </xsd:element>
-                    <xsd:element name="association">
-                      <xsd:complexType>
-                        <xsd:attribute name="class" type="sccd:className" use="required"/>
-                        <xsd:attribute name="name" type="sccd:identifier" use="required"/>
-                        <xsd:attribute name="min" type="xsd:integer"/>
-                        <xsd:attribute name="max" type="xsd:integer"/>
-                      </xsd:complexType>
-                    </xsd:element>
-                  </xsd:choice>
-                </xsd:complexType>
-              </xsd:element>
-              <xsd:element name="attribute" minOccurs="0" maxOccurs="unbounded">
-                <xsd:complexType>
-                  <xsd:attribute name="name" type="sccd:identifier" use="required"/>
-                  <xsd:attribute name="type" type="sccd:type"/>
-                  <xsd:attribute name="init-value" type="sccd:expression"/>
-                </xsd:complexType>
-              </xsd:element>
-              <xsd:element name="constructor" minOccurs="0" type="sccd:method"/>
-              <xsd:element ref="sccd:scxml" minOccurs="0">
-              </xsd:element>
-            </xsd:sequence>
-            <xsd:attribute name="name" type="sccd:className" use="required"/>
-            <xsd:attribute name="default" type="xsd:boolean"/>
-          </xsd:complexType>
-        </xsd:element>
-
-        <xsd:element name="test" minOccurs="0">
-          <xsd:complexType>
-            <xsd:sequence>
-              <xsd:element name="input" minOccurs="0">
-                <xsd:complexType>
-                  <xsd:sequence>
-                    <xsd:element name="event" minOccurs="0" maxOccurs="unbounded">
-                      <xsd:complexType>
-                        <xsd:attribute name="name" type="sccd:eventName"/>
-                        <xsd:attribute name="port" type="sccd:portName"/>
-                        <xsd:attribute name="time" type="sccd:duration"/>
-                      </xsd:complexType>
-                    </xsd:element>
-                  </xsd:sequence>
-                </xsd:complexType>
-              </xsd:element>
-              <xsd:element name="expected" minOccurs="0">
-                <xsd:complexType>
-                  <xsd:sequence>
-                    <xsd:element name="slot" minOccurs="0" maxOccurs="unbounded">
-                      <xsd:complexType>
-                        <xsd:sequence>
-                          <xsd:element name="event" minOccurs="0" maxOccurs="unbounded">
-                            <xsd:complexType>
-                              <xsd:sequence>
-                                <xsd:element name="parameter" minOccurs="0" maxOccurs="unbounded">
-                                  <xsd:complexType>
-                                    <xsd:attribute name="value" type="sccd:expression"/>
-                                  </xsd:complexType>
-                                </xsd:element>
-                              </xsd:sequence>
-                              <xsd:attribute name="name" type="sccd:eventName"/>
-                              <xsd:attribute name="port" type="sccd:portName"/>
-                            </xsd:complexType>
-                          </xsd:element>
-                        </xsd:sequence>
-                      </xsd:complexType>
-                    </xsd:element>
-                  </xsd:sequence>
-                </xsd:complexType>
-              </xsd:element>
-            </xsd:sequence>
-          </xsd:complexType>
-        </xsd:element>
-      </xsd:sequence>
-
-      <xsd:attribute name="author" type="xsd:string"/>
-      <xsd:attribute name="name" type="xsd:string"/>
-      <xsd:attribute name="language">
-        <xsd:simpleType>
-          <xsd:restriction base="xsd:string">
-            <xsd:enumeration value="python"/>
-            <xsd:enumeration value="javascript"/>
-          </xsd:restriction>
-        </xsd:simpleType>
-      </xsd:attribute>
-    </xsd:complexType>
-  </xsd:element>
-
-</xsd:schema>

+ 0 - 145
src/sccd/legacy/socket2event.py

@@ -1,145 +0,0 @@
-"""
-Socket 2 Event wrapper
-
-Author: Yentl Van Tendeloo
-
-This maps socket communication to events, and vice versa, allowing for a
-Statechart to use (blocking) sockets. It sends events to the socket_in port,
-and listens for commands on the socket_out port.  As this runs on its own
-thread, you will need to start the code by running
-"boot_translation_service(controller)" before using the ports.
-"""
-
-import threading
-from sccd.runtime.statecharts_core import Event
-import socket
-import sys
-
-send_data_queues = {}
-send_events = {}
-recv_events = {}
-run_sockets = {}
-
-def start_socket_threads(controller, sock):
-    recv_events[sock] = recv_event = threading.Event()
-    send_events[sock] = send_event = threading.Event()
-    send_data_queues[sock] = send_data_queue = []
-    run_sockets[sock] = True
-
-    thrd = threading.Thread(target=receive_from_socket, args=[controller, sock, recv_event])
-    thrd.daemon = True
-    thrd.start()
-
-    thrd = threading.Thread(target=send_to_socket, args=[controller, sock, send_data_queue, send_event])
-    thrd.daemon = True
-    thrd.start()
-
-def receive_from_socket(controller, sock, recv_event):
-    try:
-        while 1:
-            recv_event.wait()
-            recv_event.clear()
-            if not run_sockets[sock]:
-                break
-            data = sock.recv(2**16)
-            controller.addInput(Event("received_socket", "socket_in", [sock, data]))
-    except socket.error as e:
-        controller.addInput(Event("error_socket", "socket_in", [sock, e]))
-        
-
-def send_to_socket(controller, sock, data_queue, send_event):
-    while run_sockets[sock]:
-        send_event.wait()
-        send_event.clear()
-        while data_queue:
-            data = data_queue.pop(0)
-            if sys.version_info[0] > 2:
-                if isinstance(data, str):
-                    data = data.encode()
-            send = sock.send(data)
-
-            controller.addInput(Event("sent_socket", "socket_in", [sock, send]))
-        if not run_sockets[sock]:
-            break
-
-def _accept(controller, sock):
-    conn, addr = sock.accept()
-    start_socket_threads(controller, conn)
-    controller.addInput(Event("accepted_socket", "socket_in", [sock, conn]))
-
-def _connect(controller, sock, destination):
-    sock.connect(destination)
-    controller.addInput(Event("connected_socket", "socket_in", [sock]))
-
-def _close(controller, sock):
-    run_sockets[sock] = False
-    send_events[sock].set()
-    recv_events[sock].set()
-    sock.close()
-    controller.addInput(Event("closed_socket", "socket_in", [sock]))
-
-def _bind(controller, sock, addr):
-    sock.bind(addr)
-    controller.addInput(Event("bound_socket", "socket_in", [sock]))
-
-def _listen(controller, sock):
-    sock.listen(1)
-    controller.addInput(Event("listened_socket", "socket_in", [sock]))
-
-def _wrapper_func(*args):
-    func = args[0]
-    controller = args[1]
-    sock = args[2]
-    try:
-        func(*args[1:])
-    except socket.error as e:
-        #print("ERROR " + str(e))
-        controller.addInput(Event("error_socket", "socket_in", [sock, e]))
-    except Exception as e:
-        print("UNKNOWN ERROR " + str(e))
-        controller.addInput(Event("unknown_error_socket", "socket_in", [sock, e]))
-        raise
-
-def _start_on_daemon_thread(func, args):
-    new_args = [func]
-    new_args.extend(args)
-    args = new_args
-    thrd = threading.Thread(target=_wrapper_func, args=args)
-    thrd.daemon = True
-    thrd.start()
-
-def boot_translation_service(controller):
-    socket_out = controller.addOutputListener("socket_out")
-    _start_on_daemon_thread(_poll, [controller, socket_out])
-
-def _poll(controller, socket_out):
-    while 1:
-        evt = socket_out.fetch(-1)
-        name, params = evt.getName(), evt.getParameters()
-        if name == "accept_socket":
-            _start_on_daemon_thread(_accept, [controller, params[0]])
-        elif name == "recv_socket":
-            recv_events[params[0]].set()
-        elif name == "connect_socket":
-            _start_on_daemon_thread(_connect, [controller, params[0], params[1]])
-        elif name == "create_socket":
-            sock = socket.socket()
-            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-            start_socket_threads(controller, sock)
-            if len(params) == 1:
-                # In case we get an ID to prove which socket it is
-                controller.addInput(Event("created_socket", "socket_in", [sock, params[0]]))
-            else:
-                # Don't care and just send out the socket
-                controller.addInput(Event("created_socket", "socket_in", [sock]))
-        elif name == "close_socket":
-            _start_on_daemon_thread(_close, [controller, params[0]])
-        elif name == "send_socket":
-            send_data_queues[params[0]].append(params[1])
-            send_events[params[0]].set()
-        elif name == "bind_socket":
-            _start_on_daemon_thread(_bind, [controller, params[0], params[1]])
-        elif name == "listen_socket":
-            _start_on_daemon_thread(_listen, [controller, params[0]])
-        elif name == "stop":
-            break

+ 0 - 240
src/sccd/legacy/xml_loader.py

@@ -1,240 +0,0 @@
-# Legacy model and test loader
-
-import os
-import dataclasses
-from typing import List, Any, Optional
-import lxml.etree as ET
-from lark import Lark
-
-from sccd.statechart.static.statechart import *
-from sccd.controller.controller import *
-import sccd.schema
-
-schema_dir = os.path.dirname(sccd.schema.__file__)
-
-# Schema for XML validation
-schema_path = os.path.join(schema_dir, "sccd.xsd")
-schema = ET.XMLSchema(ET.parse(schema_path))
-
-# Grammar for parsing state references and expressions
-grammar = open(os.path.join(schema_dir,"grammar.g"))
-parser = Lark(grammar, parser="lalr", start=["state_ref", "expr"])
-
-@dataclass
-class Test:
-  input_events: List[InputEvent]
-  expected_events: List[Event]
-
-def load_model(src_file) -> Tuple[MultiInstanceModel, Optional[Test]]:
-  tree = ET.parse(src_file)
-  schema.assertValid(tree)
-  root = tree.getroot()
-
-  namespace = Namespace()
-  model = MultiInstanceModel(namespace, classes={}, default_class=None)
-
-  classes = root.findall(".//class", root.nsmap)
-  for c in classes:
-    class_name = c.get("name")
-    default = c.get("default", "")
-
-    scxml_node = c.find("scxml", root.nsmap)
-    statechart = load_statechart(scxml_node, model.globals)
-
-    model.classes[class_name] = statechart
-    if default or len(classes) == 1:
-      model.default_class = class_name
-
-  def find_ports(element_path, add_function):
-    elements = root.findall(element_path, root.nsmap)
-    for e in elements:
-      port = e.get("port")
-      if port != None:
-        add_function(port)
-  # Any 'port' attribute of a <transition> element is an input port
-  find_ports(".//transition", namespace.add_inport)
-  # Any 'port' attribute of a <raise> element is an output port
-  find_ports(".//raise", namespace.add_outport)
-
-  test = None
-  test_node = root.find(".//test", root.nsmap)
-  if test_node is not None:
-    input_events = []
-    expected_events = []
-    input_node = test_node.find("input", root.nsmap)
-    if input_node is not None:
-      for event_node in input_node:
-        name = event_node.get("name")
-        port = event_node.get("port")
-        time = int(event_node.get("time"))
-        input_events.append(InputEvent(name, port, [], time))
-    slots = test_node.findall("expected/slot", root.nsmap)
-    for s in slots:
-      slot = []
-      events = s.findall("event", root.nsmap)
-      for e in events:
-        name = e.get("name")
-        port = e.get("port")
-        params = [] # todo: read params
-        slot.append(Event(id=0, name=name, port=port, params=params))
-      expected_events.append(slot)
-    test = Test(input_events, expected_events)
-
-  return (model, test)
-
-def load_statechart(scxml_node, namespace: Namespace) -> Statechart:
-
-  def load_action(action_node) -> Optional[Action]:
-    tag = ET.QName(action_node).localname
-    if tag == "raise":
-      event = action_node.get("event")
-      port = action_node.get("port")
-      if not port:
-        return RaiseInternalEvent(name=event, params=[], event_id=namespace.assign_event_id(event))
-      else:
-        return RaiseOutputEvent(name=event, params=[], outport=port, time_offset=0)
-    else:
-      raise None
-
-  # parent_node: XML node containing any number of action nodes as direct children
-  def load_actions(parent_node) -> List[Action]:
-    return list(filter(lambda x: x is not None, map(lambda child: load_action(child), parent_node)))
-
-  transitions: List[Tuple[Any, State]] = [] # List of (<transition>, State) tuples
-
-  # Recursively create state hierarchy from XML node
-  # Adding <transition> elements to the 'transitions' list as a side effect
-  def build_tree(xml_node) -> Optional[State]:
-    state = None
-    name = xml_node.get("id", "")
-    tag = ET.QName(xml_node).localname
-    if tag == "scxml" or tag == "state":
-        state = State(name)
-    elif tag == "parallel" : 
-        state = ParallelState(name)
-    elif tag == "history":
-      is_deep = xml_node.get("type", "shallow") == "deep"
-      if is_deep:
-        state = DeepHistoryState(name)
-      else:
-        state = ShallowHistoryState(name)
-    else:
-      return None
-
-    initial = xml_node.get("initial", "")
-    for xml_child in xml_node.getchildren():
-        child = build_tree(xml_child) # may throw
-        if child:
-          state.addChild(child)
-          if child.short_name == initial:
-            state.default_state = child
-    if not initial and len(state.children) == 1:
-        state.default_state = state.children[0]
-
-    for xml_t in xml_node.findall("transition", xml_node.nsmap):
-      transitions.append((xml_t, state))
-
-    # Parse enter/exit actions
-    def _get_enter_exit(tag, setter):
-      node = xml_node.find(tag, xml_node.nsmap)
-      if node is not None:
-        actions = load_actions(node)
-        setter(actions)
-
-    _get_enter_exit("onentry", state.setEnter)
-    _get_enter_exit("onexit", state.setExit)
-
-    return state
-
-  # Get tree from XML
-  root = build_tree(scxml_node)
-
-  # Add transitions
-  next_after_id = 0
-  for xml_t, source in transitions:
-    # Parse and find target state
-    target_string = xml_t.get("target", "")
-    parse_tree = parser.parse(target_string, start="state_ref")
-    def find_state(sequence) -> State:
-      if sequence.data == "relative_path":
-        el = source
-      elif sequence.data == "absolute_path":
-        el = root
-      for item in sequence.children:
-        if item.type == "PARENT_NODE":
-          el = el.parent
-        elif item.type == "CURRENT_NODE":
-          continue
-        elif item.type == "IDENTIFIER":
-          el = [x for x in el.children if x.short_name == item.value][0]
-      return el
-    targets = [find_state(seq) for seq in parse_tree.children]
-
-    transition = Transition(source, targets)
-
-    # Trigger
-    event = xml_t.get("event")
-    port = xml_t.get("port")
-    after = xml_t.get("after")
-    if after is not None:
-      event = "_after%d" % next_after_id # transition gets unique event name
-      next_after_id += 1
-      trigger = AfterTrigger(namespace.assign_event_id(event), event, IntLiteral(int(after)))
-    elif event is not None:
-      trigger = EventTrigger(namespace.assign_event_id(event), event, port)
-    else:
-      trigger = None
-    transition.setTrigger(trigger)
-    # Actions
-    actions = load_actions(xml_t)
-    transition.setActions(actions)
-    # Guard
-    cond = xml_t.get("cond")
-    if cond is not None:
-      parse_tree = parser.parse(cond, start="expr")
-      # print(parse_tree)
-      # print(parse_tree.pretty())
-      cond_expr = load_expression(parse_tree)
-      transition.setGuard(cond_expr)
-    source.addTransition(transition)
-
-  # Calculate stuff like list of ancestors, descendants, etc.
-  # Also get depth-first ordered lists of states and transitions (by source)
-  states: Dict[str, State] = {}
-  state_list: List[State] = []
-  transition_list: List[Transition] = []
-  root.init_tree(0, "", states, state_list, transition_list)
-
-  for t in transition_list:
-    t.optimize()
-
-  # Semantics - We use reflection to find the xml attribute names and values
-  semantics = Semantics()
-  for aspect in dataclasses.fields(Semantics):
-    key = scxml_node.get(aspect.name)
-    if key is not None:
-      value = aspect.type[key.upper()]
-      setattr(semantics, aspect.name, value)
-
-  return Statechart(
-    tree=StateTree(root=root, states=states, state_list=state_list, transition_list=transition_list),
-    datamodel=DataModel(),
-    semantics=semantics)
-
-class ParseError(Exception):
-  def __init__(self, msg):
-    self.msg = msg
-
-def load_expression(parse_node) -> Expression:
-  if parse_node.data == "func_call":
-    function = load_expression(parse_node.children[0])
-    params = [load_expression(e) for e in parse_node.children[1].children]
-    return FunctionCall(function, params)
-  elif parse_node.data == "string":
-    return StringLiteral(parse_node.children[0].value[1:-1])
-  elif parse_node.data == "identifier":
-    return Identifier(parse_node.children[0].value)
-  elif parse_node.data == "array":
-    elements = [load_expression(e) for e in parse_node.children]
-    return Array(elements)
-  raise ParseError("Can't handle expression type: "+parse_node.data)

+ 18 - 8
src/sccd/statechart/cmd/gen_rust.py

@@ -4,7 +4,7 @@ import os
 
 # Output can be piped to Rust compiler as follows:
 #
-# For statecharts (build library):
+# For statecharts, class diagrams (build library):
 #  python -m sccd.statechart.cmd.gen_rust <path/to/statechart.xml> | rustc --crate-type=lib -
 #
 # For tests (build executable):
@@ -29,21 +29,31 @@ if __name__ == "__main__":
 
     rules = {
         "statechart": sc_parser_rules(globals),
+        "single_instance_cd": cd_parser_rules(sc_parser_rules),
         "test": test_parser_rules(sc_parser_rules),
     }
 
-    statechart_or_test = parse_f(src, rules)
+    parsed = parse_f(src, rules)
+
+    sys.stderr.write("Parsing finished.\n")
 
     w = IndentingWriter()
 
-    if isinstance(statechart_or_test, Statechart):
-        sys.stderr.write("Loaded statechart.\n")
+    if isinstance(parsed, Statechart):
         
-        from sccd.statechart.codegen.rust import compile_statechart
-        compile_statechart(statechart_or_test, globals, w)
+        from sccd.statechart.codegen.rust import StatechartRustGenerator
+
+        gen = StatechartRustGenerator(w, globals)
+        gen.accept(parsed)
+
+    elif isinstance(parsed, AbstractCD):
+        from sccd.cd.codegen.rust import ClassDiagramRustGenerator
+
+        gen = ClassDiagramRustGenerator(w, globals)
+        gen.accept(parsed)
 
-    elif isinstance(statechart_or_test, list) and reduce(lambda x,y: x and y, (isinstance(test, TestVariant) for test in statechart_or_test)):
+    elif isinstance(parsed, list) and reduce(lambda x,y: x and y, (isinstance(test, TestVariant) for test in parsed)):
         sys.stderr.write("Loaded test.\n")
 
         from sccd.test.codegen.rust import compile_test
-        compile_test(statechart_or_test, w)
+        compile_test(parsed, w)

+ 33 - 40
src/sccd/statechart/codegen/libstatechart.rs

@@ -4,7 +4,7 @@ use std::cmp::Reverse;
 
 
 #[derive(Default)]
-struct SameRoundLifeline<InternalType> {
+pub struct SameRoundLifeline<InternalType> {
   current: InternalType,
 }
 
@@ -21,7 +21,7 @@ impl<InternalType: Default> SameRoundLifeline<InternalType> {
 }
 
 #[derive(Default)]
-struct NextRoundLifeline<InternalType> {
+pub struct NextRoundLifeline<InternalType> {
   one: InternalType,
   two: InternalType,
 
@@ -45,28 +45,18 @@ impl<InternalType: Default> NextRoundLifeline<InternalType> {
   }
 }
 
-// pub trait State<TimersType, ControllerType> {
-//   // Execute enter actions of only this state
-//   fn enter_actions(timers: &mut TimersType, c: &mut ControllerType);
-//   // Execute exit actions of only this state
-//   fn exit_actions(timers: &mut TimersType, c: &mut ControllerType);
-
-//   // Execute enter actions of this state and its 'default' child(ren), recursively
-//   fn enter_default(timers: &mut TimersType, c: &mut ControllerType);
-
-//   // Execute enter actions as if the configuration recorded in this state is being entered
-//   fn enter_current(&self, timers: &mut TimersType, c: &mut ControllerType);
-//   // Execute exit actions as if the configuration recorded in this state is being exited
-//   fn exit_current(&self, timers: &mut TimersType, c: &mut ControllerType);
-// }
+pub trait Scheduler<EventType> {
+  fn set_timeout(&mut self, delay: Timestamp, event: EventType) -> EntryId;
+  fn unset_timeout(&mut self, id: EntryId);
+}
 
-pub trait SC<EventType, ControllerType> {
-  fn init(&mut self, c: &mut ControllerType);
-  fn big_step(&mut self, event: Option<EventType>, c: &mut ControllerType);
+pub trait SC<EventType, Sched: Scheduler<EventType>, OutputCallback> {
+  fn init(&mut self, sched: &mut Sched, output: &mut OutputCallback);
+  fn big_step(&mut self, event: Option<EventType>, sched: &mut Sched, output: &mut OutputCallback);
 }
 
-type Timestamp = u32;
-type TimerId = u16;
+pub type Timestamp = u32;
+pub type TimerId = u16;
 
 #[derive(Default, Copy, Clone, Ord, PartialOrd, PartialEq, Eq)]
 pub struct EntryId {
@@ -101,12 +91,24 @@ pub struct OutEvent {
   event: &'static str,
 }
 
-pub struct Controller<EventType, OutputCallback> {
+pub struct Controller<EventType> {
   simtime: Timestamp,
   next_id: TimerId,
   queue: BinaryHeap<Reverse<QueueEntry<EventType>>>,
   removed: BinaryHeap<Reverse<EntryId>>,
-  output: OutputCallback,
+}
+
+impl<EventType> Scheduler<EventType> for Controller<EventType> {
+  fn set_timeout(&mut self, delay: Timestamp, event: EventType) -> EntryId {
+    let id = EntryId{ timestamp: self.simtime + delay, n: self.next_id };
+    let entry = QueueEntry::<EventType>{ id, event };
+    self.queue.push(Reverse(entry));
+    self.next_id += 1; // TODO: will overflow eventually :(
+    return id
+  }
+  fn unset_timeout(&mut self, id: EntryId) {
+    self.removed.push(Reverse(id));
+  }
 }
 
 pub enum Until {
@@ -114,28 +116,17 @@ pub enum Until {
   Eternity,
 }
 
-impl<EventType: Copy, OutputCallback: FnMut(OutEvent)>
-Controller<EventType, OutputCallback> {
-  fn new(output: OutputCallback) -> Self {
+impl<EventType: Copy>
+Controller<EventType> {
+  fn new() -> Self {
     Self {
       simtime: 0,
       next_id: 0,
       queue: BinaryHeap::with_capacity(8),
       removed: BinaryHeap::with_capacity(4),
-      output,
     }
   }
-  fn set_timeout(&mut self, delay: Timestamp, event: EventType) -> EntryId {
-    let id = EntryId{ timestamp: self.simtime + delay, n: self.next_id };
-    let entry = QueueEntry::<EventType>{ id, event };
-    self.queue.push(Reverse(entry));
-    self.next_id += 1; // TODO: will overflow eventually :(
-    return id
-  }
-  fn unset_timeout(&mut self, id: EntryId) {
-    self.removed.push(Reverse(id));
-  }
-  fn run_until<StatechartType: SC<EventType, Controller<EventType, OutputCallback>>>(&mut self, sc: &mut StatechartType, until: Until) {
+  fn run_until<StatechartType: SC<EventType, Controller<EventType>, OutputCallback>, OutputCallback: FnMut(OutEvent)>(&mut self, sc: &mut StatechartType, until: Until, output: &mut OutputCallback) {
     'running: loop {
       if let Some(Reverse(entry)) = self.queue.peek() {
         // Check if event was removed
@@ -156,7 +147,7 @@ Controller<EventType, OutputCallback> {
         // OK, handle event
         self.simtime = entry.id.timestamp;
         // eprintln!("time is now {}", self.simtime);
-        sc.big_step(Some(entry.event), self);
+        sc.big_step(Some(entry.event), self, output);
         self.queue.pop();
       }
       else {
@@ -172,6 +163,7 @@ use std::ops::DerefMut;
 // This macro lets a struct "inherit" the data members of another struct
 // The inherited struct is added as a struct member and the Deref and DerefMut
 // traits are implemented to return a reference to the base struct
+#[macro_export]
 macro_rules! inherit_struct {
     ($name: ident ($base: ty) { $($element: ident: $ty: ty),* $(,)? } ) => {
         #[derive(Copy, Clone)]
@@ -195,11 +187,12 @@ macro_rules! inherit_struct {
 
 // "Base struct" for all scopes
 #[derive(Copy, Clone)]
-struct Empty{}
+pub struct Empty{}
 
 // A closure object is a pair of a functions first argument and that function.
 // The call may be part of an larger expression, and therefore we cannot just write 'let' statements to assign the pair's elements to identifiers which we need for the call.
 // This macro does exactly that, in an anonymous Rust closure, which is immediately called.
+#[macro_export]
 macro_rules! call_closure {
   ($closure: expr, $($param: expr),*  $(,)?) => {
     (||{

+ 34 - 34
src/sccd/statechart/codegen/rust.py

@@ -74,7 +74,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
 
     def visit_RaiseOutputEvent(self, a):
         # TODO: evaluate event parameters
-        self.w.writeln("(ctrl.output)(OutEvent{port:\"%s\", event:\"%s\"});" % (a.outport, a.name))
+        self.w.writeln("(output)(OutEvent{port:\"%s\", event:\"%s\"});" % (a.outport, a.name))
 
     def visit_RaiseInternalEvent(self, a):
         self.w.writeln("internal.raise().%s = Some(%s{});" % (ident_event_field(a.name), (ident_event_type(a.name))))
@@ -118,7 +118,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.writeln("impl %s {" % ident_type(state))
 
         # Enter actions: Executes enter actions of only this state
-        self.w.writeln("  fn enter_actions<OutputCallback: FnMut(OutEvent)>(timers: &mut Timers, data: &mut DataModel, internal: &mut InternalLifeline, ctrl: &mut Controller<InEvent, OutputCallback>) {")
+        self.w.writeln("  fn enter_actions<Sched: Scheduler<InEvent>, OutputCallback: FnMut(OutEvent)>(timers: &mut Timers, data: &mut DataModel, internal: &mut InternalLifeline, sched: &mut Sched, output: &mut OutputCallback) {")
         if DEBUG:
             self.w.writeln("    eprintln!(\"enter %s\");" % state.full_name);
         self.w.writeln("    let scope = data;")
@@ -127,14 +127,14 @@ class StatechartRustGenerator(ActionLangRustGenerator):
             a.accept(self)
         self.w.dedent(); self.w.dedent()
         for a in state.after_triggers:
-            self.w.writeln("    timers[%d] = ctrl.set_timeout(%d, InEvent::%s);" % (a.after_id, a.delay.opt, 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_type(a.enabling[0].name)))
         self.w.writeln("  }")
 
         # Enter actions: Executes exit actions of only this state
-        self.w.writeln("  fn exit_actions<OutputCallback: FnMut(OutEvent)>(timers: &mut Timers, data: &mut DataModel, internal: &mut InternalLifeline, ctrl: &mut Controller<InEvent, OutputCallback>) {")
+        self.w.writeln("  fn exit_actions<Sched: Scheduler<InEvent>, OutputCallback: FnMut(OutEvent)>(timers: &mut Timers, data: &mut DataModel, internal: &mut InternalLifeline, sched: &mut Sched, output: &mut OutputCallback) {")
         self.w.writeln("    let scope = data;")
         for a in state.after_triggers:
-            self.w.writeln("    ctrl.unset_timeout(timers[%d]);" % (a.after_id))
+            self.w.writeln("    sched.unset_timeout(timers[%d]);" % (a.after_id))
         self.w.indent(); self.w.indent()
         for a in state.exit:
             a.accept(self)
@@ -144,42 +144,42 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.writeln("  }")
 
         # Enter default: Executes enter actions of entering this state and its default substates, recursively
-        self.w.writeln("  fn enter_default<OutputCallback: FnMut(OutEvent)>(timers: &mut Timers, data: &mut DataModel, internal: &mut InternalLifeline, ctrl: &mut Controller<InEvent, OutputCallback>) {")
-        self.w.writeln("    %s::enter_actions(timers, data, internal, ctrl);" % (ident_type(state)))
+        self.w.writeln("  fn enter_default<Sched: Scheduler<InEvent>, OutputCallback: FnMut(OutEvent)>(timers: &mut Timers, data: &mut DataModel, internal: &mut InternalLifeline, sched: &mut Sched, output: &mut OutputCallback) {")
+        self.w.writeln("    %s::enter_actions(timers, data, internal, sched, output);" % (ident_type(state)))
         if isinstance(state.type, AndState):
             for child in state.real_children:
-                self.w.writeln("    %s::enter_default(timers, data, internal, ctrl);" % (ident_type(child)))
+                self.w.writeln("    %s::enter_default(timers, data, internal, sched, output);" % (ident_type(child)))
         elif isinstance(state.type, OrState):
-            self.w.writeln("    %s::enter_default(timers, data, internal, ctrl);" % (ident_type(state.type.default_state)))
+            self.w.writeln("    %s::enter_default(timers, data, internal, sched, output);" % (ident_type(state.type.default_state)))
         self.w.writeln("  }")
 
         # Exit current: Executes exit actions of this state and current children, recursively
-        self.w.writeln("  fn exit_current<OutputCallback: FnMut(OutEvent)>(&self, timers: &mut Timers, data: &mut DataModel, internal: &mut InternalLifeline, ctrl: &mut Controller<InEvent, OutputCallback>) {")
+        self.w.writeln("  fn exit_current<Sched: Scheduler<InEvent>, OutputCallback: FnMut(OutEvent)>(&self, timers: &mut Timers, data: &mut DataModel, internal: &mut InternalLifeline, sched: &mut Sched, output: &mut OutputCallback) {")
         # first, children (recursion):
         if isinstance(state.type, AndState):
             for child in state.real_children:
-                self.w.writeln("    self.%s.exit_current(timers, data, internal, ctrl);" % (ident_field(child)))
+                self.w.writeln("    self.%s.exit_current(timers, data, internal, sched, output);" % (ident_field(child)))
         elif isinstance(state.type, OrState):
             self.w.writeln("    match self {")
             for child in state.real_children:
-                self.w.writeln("      Self::%s(s) => { s.exit_current(timers, data, internal, ctrl); }," % (ident_enum_variant(child)))
+                self.w.writeln("      Self::%s(s) => { s.exit_current(timers, data, internal, sched, output); }," % (ident_enum_variant(child)))
             self.w.writeln("    }")
         # then, parent:
-        self.w.writeln("    %s::exit_actions(timers, data, internal, ctrl);" % (ident_type(state)))
+        self.w.writeln("    %s::exit_actions(timers, data, internal, sched, output);" % (ident_type(state)))
         self.w.writeln("  }")
 
         # Exit current: Executes enter actions of this state and current children, recursively
-        self.w.writeln("  fn enter_current<OutputCallback: FnMut(OutEvent)>(&self, timers: &mut Timers, data: &mut DataModel, internal: &mut InternalLifeline, ctrl: &mut Controller<InEvent, OutputCallback>) {")
+        self.w.writeln("  fn enter_current<Sched: Scheduler<InEvent>, OutputCallback: FnMut(OutEvent)>(&self, timers: &mut Timers, data: &mut DataModel, internal: &mut InternalLifeline, sched: &mut Sched, output: &mut OutputCallback) {")
         # first, parent:
-        self.w.writeln("    %s::enter_actions(timers, data, internal, ctrl);" % (ident_type(state)))
+        self.w.writeln("    %s::enter_actions(timers, data, internal, sched, output);" % (ident_type(state)))
         # then, children (recursion):
         if isinstance(state.type, AndState):
             for child in state.real_children:
-                self.w.writeln("    self.%s.enter_current(timers, data, internal, ctrl);" % (ident_field(child)))
+                self.w.writeln("    self.%s.enter_current(timers, data, internal, sched, output);" % (ident_field(child)))
         elif isinstance(state.type, OrState):
             self.w.writeln("    match self {")
             for child in state.real_children:
-                self.w.writeln("      Self::%s(s) => { s.enter_current(timers, data, internal, ctrl); }," % (ident_enum_variant(child)))
+                self.w.writeln("      Self::%s(s) => { s.enter_current(timers, data, internal, sched, output); }," % (ident_enum_variant(child)))
             self.w.writeln("    }")
         self.w.writeln("  }")
 
@@ -314,7 +314,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.write_decls()
 
         # Function fair_step: a single "Take One" Maximality 'round' (= nonoverlapping arenas allowed to fire 1 transition)
-        self.w.writeln("fn fair_step<OutputCallback: FnMut(OutEvent)>(sc: &mut Statechart, input: Option<InEvent>, internal: &mut InternalLifeline, ctrl: &mut Controller<InEvent, OutputCallback>, dirty: Arenas) -> Arenas {")
+        self.w.writeln("fn fair_step<Sched: Scheduler<InEvent>, OutputCallback: FnMut(OutEvent)>(sc: &mut Statechart, input: Option<InEvent>, internal: &mut InternalLifeline, sched: &mut Sched, output: &mut OutputCallback, dirty: Arenas) -> Arenas {")
         self.w.writeln("  let mut fired: Arenas = ARENA_NONE;")
         self.w.writeln("  let mut scope = &mut sc.data;")
         self.w.writeln("  let %s = &mut sc.current_state;" % ident_var(tree.root))
@@ -347,7 +347,7 @@ class StatechartRustGenerator(ActionLangRustGenerator):
 
                     if len(exit_path) == 1:
                         # Exit s:
-                        self.w.writeln("%s.exit_current(&mut sc.timers, *parent1, internal, ctrl);" % (ident_var(s)))
+                        self.w.writeln("%s.exit_current(&mut sc.timers, *parent1, internal, sched, output);" % (ident_var(s)))
                     else:
                         # Exit children:
                         if isinstance(s.type, AndState):
@@ -355,12 +355,12 @@ class StatechartRustGenerator(ActionLangRustGenerator):
                                 if exit_path[1] is c:
                                     write_exit(exit_path[1:]) # continue recursively
                                 else:
-                                    self.w.writeln("%s.exit_current(&mut sc.timers, *parent1, internal, ctrl);" % (ident_var(c)))
+                                    self.w.writeln("%s.exit_current(&mut sc.timers, *parent1, internal, sched, output);" % (ident_var(c)))
                         elif isinstance(s.type, OrState):
                             write_exit(exit_path[1:]) # continue recursively with the next child on the exit path
 
                         # Exit s:
-                        self.w.writeln("%s::exit_actions(&mut sc.timers, *parent1, internal, ctrl);" % (ident_type(s)))
+                        self.w.writeln("%s::exit_actions(&mut sc.timers, *parent1, internal, sched, output);" % (ident_type(s)))
 
                     # Store history
                     if s.deep_history:
@@ -384,19 +384,19 @@ class StatechartRustGenerator(ActionLangRustGenerator):
                     if len(enter_path) == 1:
                         # Target state.
                         if isinstance(s, HistoryState):
-                            self.w.writeln("sc.%s.enter_current(&mut sc.timers, *parent1, internal, ctrl); // Enter actions for history state" %(ident_history_field(s)))
+                            self.w.writeln("sc.%s.enter_current(&mut sc.timers, *parent1, internal, sched, output); // Enter actions for history state" %(ident_history_field(s)))
                         else:
-                            self.w.writeln("%s::enter_default(&mut sc.timers, *parent1, internal, ctrl);" % (ident_type(s)))
+                            self.w.writeln("%s::enter_default(&mut sc.timers, *parent1, internal, sched, output);" % (ident_type(s)))
                     else:
                         # Enter s:
-                        self.w.writeln("%s::enter_actions(&mut sc.timers, *parent1, internal, ctrl);" % (ident_type(s)))
+                        self.w.writeln("%s::enter_actions(&mut sc.timers, *parent1, internal, sched, output);" % (ident_type(s)))
                         # Enter children:
                         if isinstance(s.type, AndState):
                             for c in s.children:
                                 if enter_path[1] is c:
                                     write_enter(enter_path[1:]) # continue recursively
                                 else:
-                                    self.w.writeln("%s::enter_default(&mut sc.timers, *parent1, internal, ctrl);" % (ident_type(c)))
+                                    self.w.writeln("%s::enter_default(&mut sc.timers, *parent1, internal, sched, output);" % (ident_type(c)))
                         elif isinstance(s.type, OrState):
                             if len(s.children) > 0:
                                 write_enter(enter_path[1:]) # continue recursively with the next child on the enter path
@@ -596,19 +596,19 @@ class StatechartRustGenerator(ActionLangRustGenerator):
 
         # Write combo step and big step function
         def write_stepping_function(name: str, title: str, maximality: Maximality, substep: str, cycle_input: bool, cycle_internal: bool):
-            self.w.writeln("fn %s<OutputCallback: FnMut(OutEvent)>(sc: &mut Statechart, input: Option<InEvent>, internal: &mut InternalLifeline, ctrl: &mut Controller<InEvent, OutputCallback>, dirty: Arenas) -> Arenas {" % (name))
+            self.w.writeln("fn %s<Sched: Scheduler<InEvent>, OutputCallback: FnMut(OutEvent)>(sc: &mut Statechart, input: Option<InEvent>, internal: &mut InternalLifeline, sched: &mut Sched, output: &mut OutputCallback, dirty: Arenas) -> Arenas {" % (name))
             self.w.writeln("  // %s Maximality: %s" % (title, maximality))
             if maximality == Maximality.TAKE_ONE:
-                self.w.writeln("  %s(sc, input, internal, ctrl, dirty)" % (substep))
+                self.w.writeln("  %s(sc, input, internal, sched, output, dirty)" % (substep))
             else:
                 self.w.writeln("  let mut fired: Arenas = dirty;")
                 self.w.writeln("  let mut e = input;")
                 self.w.writeln("  let mut ctr: u16 = 0;")
                 self.w.writeln("  loop {")
                 if maximality == Maximality.TAKE_MANY:
-                    self.w.writeln("    let just_fired = %s(sc, e, internal, ctrl, ARENA_NONE);" % (substep))
+                    self.w.writeln("    let just_fired = %s(sc, e, internal, sched, output, ARENA_NONE);" % (substep))
                 elif maximality == Maximality.SYNTACTIC:
-                    self.w.writeln("    let just_fired = %s(sc, e, internal, ctrl, fired);" % (substep))
+                    self.w.writeln("    let just_fired = %s(sc, e, internal, sched, output, fired);" % (substep))
                 self.w.writeln("    if just_fired == ARENA_NONE { // did any transition fire? (incl. unstable)")
                 self.w.writeln("      break;")
                 self.w.writeln("    }")
@@ -640,13 +640,13 @@ class StatechartRustGenerator(ActionLangRustGenerator):
         self.w.writeln()
 
         # Implement 'SC' trait
-        self.w.writeln("impl<OutputCallback: FnMut(OutEvent)> SC<InEvent, Controller<InEvent, OutputCallback>> for Statechart {")
-        self.w.writeln("  fn init(&mut self, ctrl: &mut Controller<InEvent, OutputCallback>) {")
-        self.w.writeln("    %s::enter_default(&mut self.timers, &mut self.data, &mut Default::default(), ctrl)" % (ident_type(tree.root)))
+        self.w.writeln("impl<Sched: Scheduler<InEvent>, OutputCallback: FnMut(OutEvent)> SC<InEvent, Sched, OutputCallback> for Statechart {")
+        self.w.writeln("  fn init(&mut self, sched: &mut Sched, output: &mut OutputCallback) {")
+        self.w.writeln("    %s::enter_default(&mut self.timers, &mut self.data, &mut Default::default(), sched, output)" % (ident_type(tree.root)))
         self.w.writeln("  }")
-        self.w.writeln("  fn big_step(&mut self, input: Option<InEvent>, c: &mut Controller<InEvent, OutputCallback>) {")
+        self.w.writeln("  fn big_step(&mut self, input: Option<InEvent>, sched: &mut Sched, output: &mut OutputCallback) {")
         self.w.writeln("    let mut internal: InternalLifeline = Default::default();")
-        self.w.writeln("    big_step(self, input, &mut internal, c, ARENA_NONE);")
+        self.w.writeln("    big_step(self, input, &mut internal, sched, output, ARENA_NONE);")
         self.w.writeln("  }")
         self.w.writeln("}")
         self.w.writeln()

+ 11 - 4
src/sccd/test/codegen/rust.py

@@ -4,7 +4,11 @@ from sccd.util.indenting_writer import *
 
 import os
 import sccd.statechart.codegen
-rustlib = os.path.dirname(sccd.statechart.codegen.__file__) + "/libstatechart.rs"
+rustlib = os.path.dirname(sccd.statechart.codegen.__file__) + "/common.rs"
+
+# class TestRustGenerator(StatechartRustGenerator):
+#     def visit_TestVariant(self, variant):
+
 
 def compile_test(variants: List[TestVariant], w: IndentingWriter):
 
@@ -21,6 +25,9 @@ def compile_test(variants: List[TestVariant], w: IndentingWriter):
     w.writeln("#![allow(dead_code)]")
     w.writeln("#![allow(unused_parens)]")
     w.writeln("#![allow(unused_macros)]")
+    w.writeln("#![allow(non_upper_case_globals)]")
+    w.writeln("#![allow(unused_mut)]")
+    w.writeln("#![allow(unused_imports)]")
 
     with open(rustlib, 'r') as file:
         data = file.read()
@@ -46,9 +53,9 @@ def compile_test(variants: List[TestVariant], w: IndentingWriter):
             w.writeln("  eprintln!(\"^{}:{}\", out.port, out.event);")
         w.writeln("  raised.push(out);")
         w.writeln("};")
-        w.writeln("let mut controller = Controller::<InEvent,_>::new(&mut output);")
+        w.writeln("let mut controller = Controller::<InEvent>::new();")
         w.writeln("let mut sc: Statechart = Default::default();")
-        w.writeln("sc.init(&mut controller);")
+        w.writeln("sc.init(&mut controller, &mut output);")
         for i in v.input:
             if len(i.events) > 1:
                 raise UnsupportedFeature("Multiple simultaneous input events not supported")
@@ -56,7 +63,7 @@ def compile_test(variants: List[TestVariant], w: IndentingWriter):
                 raise UnsupportedFeature("Test declares empty bag of input events")
             w.writeln("controller.set_timeout(%d, InEvent::%s);" % (i.timestamp.opt, ident_event_type(i.events[0].name)))
 
-        w.writeln("controller.run_until(&mut sc, Until::Eternity);")
+        w.writeln("controller.run_until(&mut sc, Until::Eternity, &mut output);")
         w.writeln("assert_eq!(raised, [%s]);" % ", ".join('OutEvent{port:"%s", event:"%s"}' % (e.port, e.name) for o in v.output for e in o))
         w.writeln("eprintln!(\"Test variant %d passed\");" % n)
 

+ 3 - 0
src/sccd/test/dynamic/test_rust.py

@@ -66,5 +66,8 @@ def run_variants(variants: List[TestVariant], unittest):
 
         status = binary.wait()
 
+        if DEBUG:
+            print(binarystderr)
+
         if status != 0:
             unittest.fail("Test status %d. Stderr:\n%s" % (status, binarystderr))

+ 1 - 1
src/sccd/util/test_duration.py

@@ -1,5 +1,5 @@
 import unittest
-from duration import *
+from sccd.util.duration import *
 
 class TestDuration(unittest.TestCase):