|
@@ -9,67 +9,13 @@ from typing import List, Tuple
|
|
|
from enum import Enum
|
|
|
from sccd.runtime.infinity import INFINITY
|
|
|
from sccd.runtime.event_queue import Timestamp
|
|
|
+from sccd.runtime.statechart_syntax import *
|
|
|
from sccd.runtime.event import Event, OutputEvent, Instance, InstancesTarget
|
|
|
from sccd.runtime.debug import print_debug
|
|
|
from collections import Counter
|
|
|
|
|
|
ELSE_GUARD = "ELSE_GUARD"
|
|
|
|
|
|
-
|
|
|
-class Association(object):
|
|
|
- """
|
|
|
- Class representing an SCCD association.
|
|
|
- """
|
|
|
- def __init__(self, to_class, min_card, max_card):
|
|
|
- """
|
|
|
- Constructor
|
|
|
-
|
|
|
- :param to_class: the name of the target class
|
|
|
- :param min_card: the minimal cardinality
|
|
|
- :param max_card: the maximal cardinality
|
|
|
- """
|
|
|
- self.to_class = to_class
|
|
|
- self.min_card = min_card
|
|
|
- self.max_card = max_card
|
|
|
- self.instances = {} # maps index (as string) to instance
|
|
|
- self.instances_to_ids = {}
|
|
|
- self.size = 0
|
|
|
- self.next_id = 0
|
|
|
-
|
|
|
- def allowedToAdd(self):
|
|
|
- return self.max_card == -1 or self.size < self.max_card
|
|
|
-
|
|
|
- def allowedToRemove(self):
|
|
|
- return self.min_card == -1 or self.size > self.min_card
|
|
|
-
|
|
|
- def addInstance(self, instance):
|
|
|
- if self.allowedToAdd():
|
|
|
- new_id = self.next_id
|
|
|
- self.next_id += 1
|
|
|
- self.instances[new_id] = instance
|
|
|
- self.instances_to_ids[instance] = new_id
|
|
|
- self.size += 1
|
|
|
- return new_id
|
|
|
- else:
|
|
|
- raise AssociationException("Not allowed to add the instance to the association.")
|
|
|
-
|
|
|
- def removeInstance(self, instance):
|
|
|
- if self.allowedToRemove():
|
|
|
- index = self.instances_to_ids[instance]
|
|
|
- del self.instances[index]
|
|
|
- del self.instances_to_ids[instance]
|
|
|
- self.size -= 1
|
|
|
- return index
|
|
|
- else:
|
|
|
- raise AssociationException("Not allowed to remove the instance from the association.")
|
|
|
-
|
|
|
- def getInstance(self, index):
|
|
|
- try:
|
|
|
- return self.instances[index]
|
|
|
- except IndexError:
|
|
|
- raise AssociationException("Invalid index for fetching instance(s) from association.")
|
|
|
-
|
|
|
-
|
|
|
class StatechartSemantics:
|
|
|
# Big Step Maximality
|
|
|
TakeOne = 0
|
|
@@ -104,214 +50,8 @@ class StatechartSemantics:
|
|
|
self.input_event_lifeline = self.FirstComboStep
|
|
|
self.priority = self.SourceParent
|
|
|
self.concurrency = self.Single
|
|
|
-
|
|
|
-class State:
|
|
|
- def __init__(self, state_id, name, obj):
|
|
|
- self.state_id = state_id
|
|
|
- self.name = name
|
|
|
- self.obj = obj
|
|
|
-
|
|
|
- self.ancestors = []
|
|
|
- self.descendants = []
|
|
|
- self.descendant_bitmap = 0
|
|
|
- self.children = []
|
|
|
- self.parent = None
|
|
|
- self.enter = None
|
|
|
- self.exit = None
|
|
|
- self.default_state = None
|
|
|
- self.transitions = []
|
|
|
- self.history = []
|
|
|
- self.has_eventless_transitions = False
|
|
|
-
|
|
|
- def getEffectiveTargetStates(self, instance):
|
|
|
- targets = [self]
|
|
|
- if self.default_state:
|
|
|
- targets.extend(self.default_state.getEffectiveTargetStates(instance))
|
|
|
- return targets
|
|
|
-
|
|
|
- def fixTree(self):
|
|
|
- for c in self.children:
|
|
|
- if isinstance(c, HistoryState):
|
|
|
- self.history.append(c)
|
|
|
- c.parent = self
|
|
|
- c.ancestors.append(self)
|
|
|
- c.ancestors.extend(self.ancestors)
|
|
|
- c.fixTree()
|
|
|
- self.descendants.extend(self.children)
|
|
|
- for c in self.children:
|
|
|
- self.descendants.extend(c.descendants)
|
|
|
- for d in self.descendants:
|
|
|
- self.descendant_bitmap |= 2**d.state_id
|
|
|
-
|
|
|
- def addChild(self, child):
|
|
|
- self.children.append(child)
|
|
|
-
|
|
|
- def addTransition(self, transition):
|
|
|
- self.transitions.append(transition)
|
|
|
-
|
|
|
- def setEnter(self, enter):
|
|
|
- self.enter = enter
|
|
|
-
|
|
|
- def setExit(self, exit):
|
|
|
- self.exit = exit
|
|
|
-
|
|
|
- def __repr__(self):
|
|
|
- return "State(%s)" % (self.state_id)
|
|
|
-
|
|
|
-class HistoryState(State):
|
|
|
- def __init__(self, state_id, name, obj):
|
|
|
- State.__init__(self, state_id, name, obj)
|
|
|
-
|
|
|
-class ShallowHistoryState(HistoryState):
|
|
|
- def __init__(self, state_id, name, obj):
|
|
|
- HistoryState.__init__(self, state_id, name, obj)
|
|
|
-
|
|
|
- def getEffectiveTargetStates(self, instance):
|
|
|
- if self.state_id in instance.history_values:
|
|
|
- targets = []
|
|
|
- for hv in instance.history_values[self.state_id]:
|
|
|
- targets.extend(hv.getEffectiveTargetStates(instance))
|
|
|
- return targets
|
|
|
- else:
|
|
|
- # TODO: is it correct that in this case, the parent itself is also entered?
|
|
|
- return self.parent.getEffectiveTargetStates(instance)
|
|
|
-
|
|
|
-class DeepHistoryState(HistoryState):
|
|
|
- def __init__(self, state_id, name, obj):
|
|
|
- HistoryState.__init__(self, state_id, name, obj)
|
|
|
-
|
|
|
- def getEffectiveTargetStates(self, instance):
|
|
|
- if self.state_id in instance.history_values:
|
|
|
- return instance.history_values[self.state_id]
|
|
|
- else:
|
|
|
- # TODO: is it correct that in this case, the parent itself is also entered?
|
|
|
- return self.parent.getEffectiveTargetStates(instance)
|
|
|
-
|
|
|
-class ParallelState(State):
|
|
|
- def __init__(self, state_id, name, obj):
|
|
|
- State.__init__(self, state_id, name, obj)
|
|
|
-
|
|
|
- def getEffectiveTargetStates(self, instance):
|
|
|
- targets = [self]
|
|
|
- for c in self.children:
|
|
|
- if not isinstance(c, HistoryState):
|
|
|
- targets.extend(c.getEffectiveTargetStates(instance))
|
|
|
- return targets
|
|
|
-
|
|
|
-class Transition:
|
|
|
- def __init__(self, source, targets):
|
|
|
- self.guard = None
|
|
|
- self.action = None
|
|
|
- self.trigger = None
|
|
|
- self.source = source
|
|
|
- self.targets = targets
|
|
|
- self.enabled_event = None # the event that enabled this transition
|
|
|
- self.optimize()
|
|
|
-
|
|
|
- def isEnabled(self, instance, events, enabled_transitions):
|
|
|
- if self.trigger is None:
|
|
|
- self.enabled_event = None
|
|
|
- return (self.guard is None) or (self.guard == ELSE_GUARD and not enabled_transitions) or self.guard(instance, [])
|
|
|
- else:
|
|
|
- for event in events:
|
|
|
- if (self.trigger.name == event.name and (not self.trigger.port or self.trigger.port == event.port)) and ((self.guard is None) or (self.guard == ELSE_GUARD and not enabled_transitions) or self.guard(event.parameters)):
|
|
|
- self.enabled_event = event
|
|
|
- return True
|
|
|
|
|
|
- # @profile
|
|
|
- def fire(self, instance):
|
|
|
-
|
|
|
- def __exitSet():
|
|
|
- return [s for s in reversed(self.lca.descendants) if (s in instance.configuration)]
|
|
|
-
|
|
|
- def __enterSet(targets):
|
|
|
- target = targets[0]
|
|
|
- for a in reversed(target.ancestors):
|
|
|
- if a in self.source.ancestors:
|
|
|
- continue
|
|
|
- else:
|
|
|
- yield a
|
|
|
- for target in targets:
|
|
|
- yield target
|
|
|
-
|
|
|
- def __getEffectiveTargetStates():
|
|
|
- targets = []
|
|
|
- for target in self.targets:
|
|
|
- for e_t in target.getEffectiveTargetStates(instance):
|
|
|
- if not e_t in targets:
|
|
|
- targets.append(e_t)
|
|
|
- return targets
|
|
|
-
|
|
|
- # exit states...
|
|
|
- exit_set = __exitSet()
|
|
|
- for s in exit_set:
|
|
|
- # remember which state(s) we were in if a history state is present
|
|
|
- for h in s.history:
|
|
|
- f = lambda s0: s0.ancestors and s0.parent == s
|
|
|
- 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(termcolor.colored(' EXIT %s' % s.name, 'green'))
|
|
|
- instance.eventless_states -= s.has_eventless_transitions
|
|
|
- # execute exit action(s)
|
|
|
- if s.exit:
|
|
|
- s.exit(instance)
|
|
|
- instance.configuration_bitmap &= ~2**s.state_id
|
|
|
-
|
|
|
- # combo state changed area
|
|
|
- instance._combo_step.changed_bitmap |= 2**self.lca.state_id
|
|
|
- instance._combo_step.changed_bitmap |= self.lca.descendant_bitmap
|
|
|
|
|
|
- # execute transition action(s)
|
|
|
- if self.action:
|
|
|
- self.action(instance, self.enabled_event.parameters if self.enabled_event else [])
|
|
|
-
|
|
|
- # enter states...
|
|
|
- targets = __getEffectiveTargetStates()
|
|
|
- enter_set = __enterSet(targets)
|
|
|
- for s in enter_set:
|
|
|
- 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)
|
|
|
- if s.enter:
|
|
|
- s.enter(instance)
|
|
|
- try:
|
|
|
- instance.configuration = instance.config_mem[instance.configuration_bitmap]
|
|
|
- except:
|
|
|
- instance.configuration = instance.config_mem[instance.configuration_bitmap] = sorted([s for s in list(instance.model.states.values()) if 2**s.state_id & instance.configuration_bitmap], key=lambda s: s.state_id)
|
|
|
- self.enabled_event = None
|
|
|
-
|
|
|
- def setGuard(self, guard):
|
|
|
- self.guard = guard
|
|
|
-
|
|
|
- def setAction(self, action):
|
|
|
- self.action = action
|
|
|
-
|
|
|
- def setTrigger(self, trigger):
|
|
|
- self.trigger = trigger
|
|
|
- if self.trigger is None:
|
|
|
- self.source.has_eventless_transitions = True
|
|
|
-
|
|
|
- def optimize(self):
|
|
|
- # the least-common ancestor can be computed statically
|
|
|
- if self.source in self.targets[0].ancestors:
|
|
|
- self.lca = self.source
|
|
|
- else:
|
|
|
- self.lca = self.source.parent
|
|
|
- target = self.targets[0]
|
|
|
- if self.source.parent != target.parent: # external
|
|
|
- for a in self.source.ancestors:
|
|
|
- if a in target.ancestors:
|
|
|
- self.lca = a
|
|
|
- break
|
|
|
-
|
|
|
- def __repr__(self):
|
|
|
- return "Transition(%s, %s)" % (self.source, self.targets[0])
|
|
|
-
|
|
|
class StatechartInstance(Instance):
|
|
|
def __init__(self, model, object_manager):
|
|
|
self.model = model
|
|
@@ -327,7 +67,7 @@ class StatechartInstance(Instance):
|
|
|
self.transition_mem = {}
|
|
|
# mapping from configuration_bitmap (=int) to configuration (=List[State])
|
|
|
self.config_mem = {}
|
|
|
-
|
|
|
+ # mapping from history state id to states to enter if history is target of transition
|
|
|
self.history_values = {}
|
|
|
|
|
|
self._big_step = BigStepState()
|
|
@@ -411,29 +151,93 @@ class StatechartInstance(Instance):
|
|
|
if self.model.semantics.concurrency == StatechartSemantics.Single:
|
|
|
candidate = conflicting[0]
|
|
|
if self.model.semantics.priority == StatechartSemantics.SourceParent:
|
|
|
- candidate[-1].fire(self)
|
|
|
+ self._fire_transition(candidate[-1])
|
|
|
+ # candidate[-1].fire(self)
|
|
|
else:
|
|
|
- candidate[0].fire(self)
|
|
|
+ self._fire_transition(candidate[0])
|
|
|
+ # candidate[0].fire(self)
|
|
|
elif self.model.semantics.concurrency == StatechartSemantics.Many:
|
|
|
pass # TODO: implement
|
|
|
self._small_step.has_stepped = True
|
|
|
return self._small_step.has_stepped
|
|
|
|
|
|
- def getChildren(self, link_name):
|
|
|
- traversal_list = self.controller.object_manager.processAssociationReference(link_name)
|
|
|
- return [i["instance"] for i in self.controller.object_manager.getInstances(self, traversal_list)]
|
|
|
+ # @profile
|
|
|
+ def _fire_transition(self, t: Transition):
|
|
|
|
|
|
- def getSingleChild(self, link_name):
|
|
|
- return self.getChildren(link_nameself.controller)[0] # assume this will return a single child...
|
|
|
+ def __exitSet():
|
|
|
+ return [s for s in reversed(t.lca.descendants) if (s in self.configuration)]
|
|
|
+
|
|
|
+ def __enterSet(targets):
|
|
|
+ target = targets[0]
|
|
|
+ for a in reversed(target.ancestors):
|
|
|
+ if a in t.source.ancestors:
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ yield a
|
|
|
+ for target in targets:
|
|
|
+ yield target
|
|
|
|
|
|
- def processBigStepOutput(self):
|
|
|
- # print("processBigStepOutput:", self._big_step.output_events_port)
|
|
|
- self.controller.outputBigStep(self._big_step.output_events_port)
|
|
|
- for e in self._big_step.output_events_om:
|
|
|
- self.controller.object_manager.addEvent(e)
|
|
|
+ def __getEffectiveTargetStates():
|
|
|
+ targets = []
|
|
|
+ for target in t.targets:
|
|
|
+ for e_t in target.getEffectiveTargetStates(self):
|
|
|
+ if not e_t in targets:
|
|
|
+ targets.append(e_t)
|
|
|
+ return targets
|
|
|
|
|
|
+ # exit states...
|
|
|
+ exit_set = __exitSet()
|
|
|
+ for s in exit_set:
|
|
|
+ # remember which state(s) we were in if a history state is present
|
|
|
+ for h in s.history:
|
|
|
+ f = lambda s0: s0.ancestors and s0.parent == s
|
|
|
+ if isinstance(h, DeepHistoryState):
|
|
|
+ f = lambda s0: not s0.descendants and s0 in s.descendants
|
|
|
+ self.history_values[h.state_id] = list(filter(f, self.configuration))
|
|
|
+ print_debug('')
|
|
|
+ print_debug(termcolor.colored('transition %s: %s 🡪 %s'%(self.model._class.name, t.source.name, t.targets[0].name), 'green'))
|
|
|
+ for s in exit_set:
|
|
|
+ print_debug(termcolor.colored(' EXIT %s' % s.name, 'green'))
|
|
|
+ self.eventless_states -= s.has_eventless_transitions
|
|
|
+ # execute exit action(s)
|
|
|
+ if s.exit:
|
|
|
+ s.exit(self)
|
|
|
+ self.configuration_bitmap &= ~2**s.state_id
|
|
|
+
|
|
|
+ # combo state changed area
|
|
|
+ self._combo_step.changed_bitmap |= 2**t.lca.state_id
|
|
|
+ self._combo_step.changed_bitmap |= t.lca.descendant_bitmap
|
|
|
+
|
|
|
+ # execute transition action(s)
|
|
|
+ if t.action:
|
|
|
+ t.action(self, t.enabled_event.parameters if t.enabled_event else [])
|
|
|
|
|
|
- def inState(self, state_strings):
|
|
|
+ # enter states...
|
|
|
+ targets = __getEffectiveTargetStates()
|
|
|
+ enter_set = __enterSet(targets)
|
|
|
+ for s in enter_set:
|
|
|
+ print_debug(termcolor.colored(' ENTER %s' % s.name, 'green'))
|
|
|
+ self.eventless_states += s.has_eventless_transitions
|
|
|
+ self.configuration_bitmap |= 2**s.state_id
|
|
|
+ # execute enter action(s)
|
|
|
+ if s.enter:
|
|
|
+ s.enter(self)
|
|
|
+ try:
|
|
|
+ self.configuration = self.config_mem[self.configuration_bitmap]
|
|
|
+ except:
|
|
|
+ self.configuration = self.config_mem[self.configuration_bitmap] = sorted([s for s in list(self.model.states.values()) if 2**s.state_id & self.configuration_bitmap], key=lambda s: s.state_id)
|
|
|
+ t.enabled_event = None
|
|
|
+
|
|
|
+
|
|
|
+ # def getChildren(self, link_name):
|
|
|
+ # traversal_list = self.controller.object_manager.processAssociationReference(link_name)
|
|
|
+ # return [i["instance"] for i in self.controller.object_manager.getInstances(self, traversal_list)]
|
|
|
+
|
|
|
+ # def getSingleChild(self, link_name):
|
|
|
+ # return self.getChildren(link_nameself.controller)[0] # assume this will return a single child...
|
|
|
+
|
|
|
+ # Return whether the current configuration includes ALL the states given.
|
|
|
+ def inState(self, state_strings: List[str]) -> bool:
|
|
|
state_ids = functools.reduce(lambda x,y: x&y, [2**self.model.states[state_string].state_id for state_string in state_strings])
|
|
|
in_state = (self.configuration_bitmap | state_ids) == self.configuration_bitmap
|
|
|
if in_state:
|
|
@@ -444,7 +248,7 @@ class StatechartInstance(Instance):
|
|
|
|
|
|
# generate transition candidates for current small step
|
|
|
# @profile
|
|
|
- def _transition_candidates(self):
|
|
|
+ def _transition_candidates(self) -> List[Transition]:
|
|
|
changed_bitmap = self._combo_step.changed_bitmap
|
|
|
key = (self.configuration_bitmap, changed_bitmap)
|
|
|
try:
|
|
@@ -461,10 +265,21 @@ class StatechartInstance(Instance):
|
|
|
enabled_events += self._big_step.input_events
|
|
|
enabled_transitions = []
|
|
|
for t in transitions:
|
|
|
- if t.isEnabled(self, enabled_events, enabled_transitions):
|
|
|
+ if self._is_transition_enabled(t, enabled_events, enabled_transitions):
|
|
|
+ # if t.isEnabled(self, enabled_events, enabled_transitions):
|
|
|
enabled_transitions.append(t)
|
|
|
return enabled_transitions
|
|
|
|
|
|
+ def _is_transition_enabled(self, t, events, enabled_transitions) -> bool:
|
|
|
+ if t.trigger is None:
|
|
|
+ t.enabled_event = None
|
|
|
+ return (t.guard is None) or (t.guard == ELSE_GUARD and not enabled_transitions) or t.guard(self, [])
|
|
|
+ else:
|
|
|
+ for event in events:
|
|
|
+ if (t.trigger.name == event.name and (not t.trigger.port or t.trigger.port == event.port)) and ((t.guard is None) or (t.guard == ELSE_GUARD and not enabled_transitions) or t.guard(event.parameters)):
|
|
|
+ t.enabled_event = event
|
|
|
+ return True
|
|
|
+
|
|
|
def _raiseInternalEvent(self, event):
|
|
|
if self.model.semantics.internal_event_lifeline == StatechartSemantics.NextSmallStep:
|
|
|
self._small_step.addNextEvent(event)
|
|
@@ -529,11 +344,11 @@ class SmallStepState(object):
|
|
|
|
|
|
class Maximality(Enum):
|
|
|
TAKE_ONE = 0
|
|
|
- TAKE_MANY = 2
|
|
|
+ TAKE_MANY = 1
|
|
|
|
|
|
class Round:
|
|
|
def __init__(self, maximality: Maximality):
|
|
|
- self.changed_bitmap
|
|
|
+ self.changed_bitmap: int
|
|
|
self.current_events: List[Event] = []
|
|
|
self.next_events: List[Event] = []
|
|
|
self.has_stepped: bool = True
|