Bläddra i källkod

Use python library 'dataclasses' + comprehensive small/combo/big step stdout trace with environment variable SCCDDEBUG

Joeri Exelmans 5 år sedan
förälder
incheckning
79a67cb8b4

+ 6 - 1
src/sccd/compiler/generic_generator.py

@@ -143,8 +143,9 @@ class GenericGenerator(Visitor):
         self.writer.beginClass(compiled_class_name(class_node.name))
         self.writer.beginConstructor()
         self.writer.beginMethodBody()
+        self.writer.addAssignment(GLC.SelfProperty("name"), GLC.String(class_node.name))
         if class_node.statechart:
-            self.writer.addAssignment(GLC.SelfProperty("statechart"), GLC.NewExpression("Statechart_"+class_node.name))
+            self.writer.addAssignment(GLC.SelfProperty("statechart"), GLC.NewExpression("Statechart_"+class_node.name, [GLC.SelfExpression()]))
         self.writer.endMethodBody()
         self.writer.endConstructor()
         self.writer.endClass()
@@ -152,8 +153,12 @@ class GenericGenerator(Visitor):
     def visit_StateChart(self, statechart):
         # We are in the Statechart_ClassName class here:
         self.writer.beginConstructor()
+        self.writer.addFormalParameter("_class")
         self.writer.beginMethodBody()
 
+        self.writer.addAssignment(GLC.SelfProperty("_class"), GLC.Identifier("_class"))
+        self.writer.addVSpace()
+
         self.writer.addAssignment(GLC.SelfProperty("semantics"), GLC.NewExpression("StatechartSemantics"))
         if statechart.big_step_maximality == "take_one":
             self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "big_step_maximality"), GLC.Property("StatechartSemantics", "TakeOne"))

+ 6 - 12
src/sccd/runtime/controller.py

@@ -1,5 +1,5 @@
-import threading
 import queue
+import dataclasses
 from typing import Dict, List
 from sccd.runtime.event_queue import EventQueue, EventQueueDeque, Timestamp
 from sccd.runtime.event import *
@@ -10,17 +10,11 @@ from sccd.runtime.infinity import INFINITY
 # Threads, integration with existing event loop, game loop, test framework, ...
 # The Controller class itself is NOT thread-safe.
 class Controller:
-    # Data class
-    class EventQueueEntry:
-        def __init__(self, event: Event, targets: List[Instance]):
-            self.event = event
-            self.targets = targets
-
-        def __str__(self):
-            return "(event:"+str(self.event)+",targets:"+str(self.targets)+")"
 
-        def __repr__(self):
-            return self.__str__()
+    @dataclasses.dataclass(eq=False, frozen=True)
+    class EventQueueEntry:
+        event: Event
+        targets: List[Instance]
 
     def __init__(self, model):
         self.model = model
@@ -53,7 +47,7 @@ class Controller:
 
     # Run until given timestamp.
     # Simulation continues until there are no more due events wrt timestamp and until all instances are stable.
-    # Output generated while running is written to 'pipe'.
+    # Output generated while running is written to 'pipe' so it can be heard by another thread.
     def run_until(self, now: Timestamp, pipe: queue.Queue):
 
         unstable = []

+ 6 - 26
src/sccd/runtime/event.py

@@ -1,32 +1,13 @@
+import dataclasses
 from abc import ABC, abstractmethod
-from typing import List
+from typing import List, Any
 from sccd.runtime.event_queue import Timestamp
 
-# Data class.
+@dataclasses.dataclass(frozen=True)
 class Event:
-    def __init__(self, name, port = "", parameters = []):
-        self._name = name
-        self._port = port
-        self._parameters = parameters
-
-    @property
-    def name(self):
-        return self._name
-
-    @property
-    def port(self):
-        return self._port
-
-    @property
-    def parameters(self):
-        return self._parameters
-    
-    def __repr__(self):
-        s = "Event (name: " + str(self._name) + "; port: " + str(self._port)
-        if self._parameters:
-            s += "; parameters: " + str(self._parameters)
-        s += ")"
-        return s
+    name: str
+    port: str = ""
+    parameters: List[Any] = dataclasses.field(default_factory=list)
 
 # Abstract class.
 class EventTarget(ABC):
@@ -59,4 +40,3 @@ class OutputPortTarget(EventTarget):
 class InstancesTarget(EventTarget):
     def __init__(self, instances: List[Instance]):
         self.instances = instances
-

+ 3 - 3
src/sccd/runtime/event_queue.py

@@ -10,9 +10,9 @@ Item = TypeVar('Item')
 
 class EventQueue(Generic[Item]):
     def __init__(self):
-        self.queue = []
+        self.queue: List[Tuple[Timestamp, int, Item]] = []
         self.counters = {} # mapping from timestamp to number of items at timestamp
-        self.removed = set()
+        self.removed: Set[Item] = set()
     
     def __str__(self):
         return str([entry for entry in self.queue if entry[2] not in self.removed])
@@ -49,7 +49,7 @@ class EventQueue(Generic[Item]):
             self.counters[timestamp] -= 1
             if not self.counters[timestamp]:
                 del self.counters[timestamp]
-            if item not in self.removed:
+            if item[2] not in self.removed:
                 return (timestamp, item[2])
 
     # Safe to call on empty queue

+ 12 - 5
src/sccd/runtime/statecharts_core.py

@@ -2,15 +2,15 @@
 The classes and functions needed to run (compiled) SCCD models.
 """
 
-# import traceback
-# import math
+import os
+import termcolor
 from typing import List
 from sccd.runtime.infinity import INFINITY
 from sccd.runtime.event_queue import Timestamp
 from sccd.runtime.event import Event, OutputEvent, Instance, InstancesTarget
 from collections import Counter
 
-DEBUG = False
+DEBUG = os.environ['SCCDDEBUG']
 ELSE_GUARD = "ELSE_GUARD"
 
 def print_debug(msg):
@@ -276,8 +276,10 @@ class Transition:
                 if isinstance(h, DeepHistoryState):
                     f = lambda s0: not s0.descendants and s0 in s.descendants
                 instance.history_values[h.state_id] = list(filter(f, instance.configuration))
+        print_debug('')
+        print_debug(termcolor.colored('transition %s:  %s 🡪 %s'%(instance.model._class.name, self.source.name, self.targets[0].name), 'green'))
         for s in exit_set:
-            print_debug('EXIT: %s::%s' % (instance.__class__.__name__, s.name))
+            print_debug(termcolor.colored('  EXIT %s' % s.name, 'green'))
             instance.eventless_states -= s.has_eventless_transitions
             # execute exit action(s)
             if s.exit:
@@ -296,7 +298,7 @@ class Transition:
         targets = __getEffectiveTargetStates()
         enter_set = __enterSet(targets)
         for s in enter_set:
-            print_debug('ENTER: %s::%s' % (instance.__class__.__name__, s.name))
+            print_debug(termcolor.colored('  ENTER %s' % s.name, 'green'))
             instance.eventless_states += s.has_eventless_transitions
             instance.configuration_bitmap |= 2**s.state_id
             # execute enter action(s)
@@ -389,10 +391,14 @@ class StatechartInstance(Instance):
         self._small_step.reset()
 
         while self.combo_step():
+            print_debug(termcolor.colored('completed combo step', 'yellow'))
             self._big_step.has_stepped = True
             if self.model.semantics.big_step_maximality == StatechartSemantics.TakeOne:
                 break # Take One -> only one combo step allowed
 
+        if self._big_step.has_stepped:
+            print_debug(termcolor.colored('completed big step', 'red'))
+
         # can the next big step still contain transitions, even if there are no input events?
         self.stable = not self.eventless_states or (not filtered and not self._big_step.has_stepped)
         return self._big_step.output_events
@@ -400,6 +406,7 @@ class StatechartInstance(Instance):
     def combo_step(self):
         self._combo_step.next()
         while self.small_step():
+            print_debug(termcolor.colored("completed small step", "blue"))
             self._combo_step.has_stepped = True
         return self._combo_step.has_stepped
 

+ 23 - 10
src/sccd/runtime/test_event_queue.py

@@ -7,10 +7,18 @@ def add_pop(q, unit):
   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')
   unit.assertEqual(q.earliest_timestamp(), 10)
   unit.assertEqual(q.pop(), (10,'a'))
   unit.assertEqual(q.pop(), (11,'b'))
-  unit.assertEqual(q.pop(), (11,'c'))
+  unit.assertEqual(q.pop(), (11,'d'))
+  unit.assertEqual(q.pop(), (11,'e'))
+  unit.assertEqual(q.pop(), (11,'f'))
+  unit.assertEqual(q.pop(), (11,'g'))
   unit.assertEqual(q.earliest_timestamp(), INFINITY)
 
 def add_remove(q, unit):
@@ -21,15 +29,20 @@ def add_remove(q, unit):
       X.n += 1
     def __repr__(self):
       return "x%d"%self.x
-  Xs = []
-  for i in range(10):
-    x = X()
-    Xs.append(x)
-    q.add(i, x)
-  unit.assertFalse(q.is_empty())
-  for x in Xs:
-    q.remove(x)
-  unit.assertTrue(q.is_empty())
+  def testrange(N):
+    Xs = []
+    for i in range(10):
+      x = X()
+      Xs.append(x)
+      q.add(i, x)
+    unit.assertFalse(q.is_empty())
+    for x in Xs:
+      q.remove(x)
+    unit.assertTrue(q.is_empty())
+  testrange(1)
+  testrange(10)
+  testrange(100)
+  testrange(1000)
 
 def due(q, unit):
   q.add(10, 'a')