| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- import termcolor
- from typing import *
- import itertools
- from sccd.statechart.static.action import *
- from sccd.statechart.static.state_ref import StateRef
- from sccd.util.bitmap import *
- from sccd.util import timer
- from sccd.util.visit_tree import *
- from sccd.util.freezable import *
- from abc import *
- @dataclass(eq=False)
- class AbstractState:
- short_name: str # value of 'id' attribute in XML
- parent: Optional['AbstractState'] # only None if root state
- children: List['AbstractState'] = field(default_factory=list)
- ####### Calculated values below ########
- state_id: int = -1
- state_id_bitmap: Bitmap = Bitmap() # bitmap with only state_id-bit set
- full_name: str = ""
- ancestors: Bitmap = Bitmap()
- descendants: Bitmap = Bitmap()
- effective_targets: Bitmap = Bitmap()
- def __post_init__(self):
- if self.parent is not None:
- self.parent.children.append(self)
- def __str__(self):
- return "AbstractState(%s)" % self.short_name
- __repr__ = __str__
- @dataclass(eq=False)
- class State(AbstractState, Visitable):
- type: 'StateType' = None
- real_children: List['State'] = field(default_factory=list) # children, but not including pseudo-states such as history
- # whether this is a stable stabe. this field is ignored if maximality semantics is not set to SYNTACTIC
- stable: bool = False
- # Outgoing transitions
- transitions: List['Transition'] = field(default_factory=list)
- enter: List[Action] = field(default_factory=list)
- exit: List[Action] = field(default_factory=list)
- ####### Calculated values below ########
- depth: int = -1 # Root is 0, root's children are 1, and so on
- # If a direct child of this state is a deep history state, then "deep history" needs to be recorded when exiting this state. This value contains a tuple, with the (history-id, history_mask, history state) of that child state.
- deep_history: Optional[Tuple[int, Bitmap, 'DeepHistoryState']] = None
- # If a direct child of this state is a shallow history state, then "shallow history" needs to be recorded when exiting this state. This value is the history-id of that child state
- shallow_history: Optional[Tuple[int, 'ShallowHistoryState']] = None
- # Triggers of outgoing transitions that are AfterTrigger.
- after_triggers: List['AfterTrigger'] = field(default_factory=list)
- def __post_init__(self):
- super().__post_init__()
- if self.parent is not None:
- self.parent.real_children.append(self)
- def __str__(self):
- if isinstance(self.type, AndState):
- return "AndState(%s)" % self.short_name
- elif isinstance(self.type, OrState):
- return "OrState(%s)" % self.short_name
- else:
- return "State?(%s)" % self.short_name
- __repr__ = __str__
- @dataclass(eq=False)
- class HistoryState(AbstractState):
- history_id: int = -1
- def __post_init__(self):
- super().__post_init__()
- self.type = HistoryStateType(self)
- @dataclass(eq=False)
- class ShallowHistoryState(HistoryState):
- def __str__(self):
- return "ShallowHistoryState(%s)" % self.short_name
- __repr__ = __str__
- @dataclass(eq=False)
- class DeepHistoryState(HistoryState):
- def __str__(self):
- return "DeepHistoryState(%s)" % self.short_name
- __repr__ = __str__
- @dataclass(eq=False)
- class StateType(ABC):
- state: State
- @abstractmethod
- def effective_targets(self):
- pass
- @abstractmethod
- def additional_effective_targets(self, exclude: 'State'):
- pass
- @dataclass(eq=False)
- class AndState(StateType):
- def effective_targets(self):
- return self.additional_effective_targets(None)
- def additional_effective_targets(self, exclude: 'State'):
- return [self.state] + list(itertools.chain(*(c.type.effective_targets() for c in self.state.children if not isinstance(c, HistoryState) and c is not exclude)))
- @dataclass(eq=False)
- class OrState(StateType):
- default_state: AbstractState
- def effective_targets(self):
- return [self.state] + self.default_state.type.effective_targets()
- def additional_effective_targets(self, exclude: 'State'):
- return [self.state]
- @dataclass(eq=False)
- class HistoryStateType(StateType):
- def effective_targets(self):
- return []
- def additional_effective_targets(self, exclude: 'State'):
- assert False # history state cannot have children and therefore should never occur in a "enter path"
- @dataclass
- class EventDecl:
- __slots__ = ["name", "params_decl"]
- name: str
- params_decl: List[ParamDecl]
- def render(self) -> str:
- if self.params_decl:
- return self.name + '(' + ', '.join(p.render() for p in self.params_decl) + ')'
- else:
- return self.name
- @dataclass
- class Trigger:
- __slots__ = ["enabling", "enabling_bitmap"]
- enabling: List[EventDecl]
- def check(self, events: List[InternalEvent]) -> bool:
- for e in self.enabling:
- if e.name not in (e.name for e in events):
- return False
- return True
- def render(self) -> str:
- return ' ∧ '.join(e.render() for e in self.enabling)
- @dataclass
- class NegatedTrigger(Trigger):
- __slots__ = ["disabling", "disabling_bitmap"]
- disabling: List[EventDecl]
- def check(self, events: List[InternalEvent]) -> bool:
- if not Trigger.check(self, events):
- return False
- for e in self.disabling:
- if e.name in (e.name for e in events):
- return False
- return True
- def render(self) -> str:
- return ' ∧ '.join(itertools.chain((e.render() for e in self.enabling), ('¬'+e.render() for e in self.disabling)))
- class AfterTrigger(Trigger):
- def __init__(self, name: str, after_id: int, delay: Expression):
- enabling = [EventDecl(name=name, params_decl=[])]
- super().__init__(enabling)
- # self.id = id
- self.name = name
- self.after_id = after_id # unique ID for AfterTrigger
- self.delay = delay
- def render(self) -> str:
- return "after("+self.delay.render()+")"
- EMPTY_TRIGGER = Trigger(enabling=[])
- @dataclass(eq=False)
- class Transition(StateRef):
- guard: Optional[FunctionDeclaration] = None
- actions: List[Action] = field(default_factory=list)
- trigger: Trigger = EMPTY_TRIGGER
- ####### CALCULATED VALUES ########
- id: int = None # just a unique number, >= 0
- exit_mask: State = None
- arena: State = None
- arena_bitmap: Bitmap = None
- # The "enter set" can be computed partially statically, or entirely statically if there are no history states in it.
- enter_states_static: Bitmap = None
- target_history_id: Optional[int] = None # History ID if target of transition is a history state, otherwise None.
- target_stable: bool = None # Whether target state is a stable state
- raised_events: Bitmap = None # (internal) event IDs raised by this transition
- def __str__(self):
- if self.target is None:
- return "Transition(%s -> %s)" % (self.source.short_name, self.target_string)
- else:
- return termcolor.colored("%s -> %s" % (self.source.full_name, self.target.full_name), 'green')
- __repr__ = __str__
- class StateTree:
- def __init__(self, root: State):
- super().__init__()
- self.root: State = root
- self.state_dict = {}
- self.state_list = []
- self.transition_list = []
- self.timer_count = 0 # number of after-transitions in the statechart
- self.history_states: List[HistoryState] = []
- self.initial_history_values: List[Bitmap] = []
- with timer.Context("optimize tree"):
- def assign_state_id():
- next_id = 0
- def f(state: State, _=None):
- nonlocal next_id
- state.state_id = next_id
- state.state_id_bitmap = bit(next_id)
- next_id += 1
- return f
- def assign_full_name(state: State, parent_full_name: str = ""):
- if state is root:
- full_name = '/'
- elif state.parent is root:
- full_name = '/' + state.short_name
- else:
- full_name = parent_full_name + '/' + state.short_name
- state.full_name = full_name
- return full_name
- def assign_depth(state: State, parent_depth: int = 0):
- state.depth = parent_depth + 1
- return parent_depth + 1
- def add_to_list(state: State ,_=None):
- self.state_dict[state.full_name] = state
- self.state_list.append(state)
- def visit_transitions(state: State, _=None):
- for t in state.transitions:
- self.transition_list.append(t)
- if t.trigger and isinstance(t.trigger, AfterTrigger):
- state.after_triggers.append(t.trigger)
- self.timer_count += 1
- def set_ancestors(state: State, ancestors=Bitmap()):
- state.ancestors = ancestors
- return ancestors | state.state_id_bitmap
- def set_descendants(state: State, children_descendants):
- descendants = bm_union(children_descendants)
- state.descendants = descendants
- return state.state_id_bitmap | descendants
- def calculate_effective_targets(state: State, _=None):
- # implementation of "effective_targets"-method is recursive (slow)
- # store the result, it is always the same:
- state.effective_targets = states_to_bitmap(state.type.effective_targets())
- def deal_with_history(state: State, children_history):
- for h in children_history:
- if isinstance(h, DeepHistoryState):
- state.deep_history = (h.history_id, h.parent.descendants, h)
- elif isinstance(h, ShallowHistoryState):
- state.shallow_history = (h.history_id, h)
- if isinstance(state, HistoryState):
- state.history_id = len(self.initial_history_values) # generate history ID
- self.history_states.append(state)
- self.initial_history_values.append(state.parent.effective_targets)
- return state
- visit_tree(root, lambda s: s.children,
- parent_first=[
- assign_state_id(),
- assign_full_name,
- add_to_list,
- set_ancestors,
- ],
- child_first=[
- set_descendants,
- calculate_effective_targets,
- ])
- visit_tree(root, lambda s: s.real_children,
- parent_first=[
- assign_depth,
- visit_transitions,
- ],
- child_first=[])
- self.initial_states = root.effective_targets
- visit_tree(root, lambda s: s.children,
- child_first=[
- deal_with_history,
- ])
- # print()
- # def pretty_print(s, indent=""):
- # print(indent, s)
- # return indent + " "
- # visit_tree(root, lambda s: s.children,
- # parent_first=[
- # pretty_print,
- # ])
- for t_id, t in enumerate(self.transition_list):
- # Arena can be computed statically. First compute Lowest-common ancestor:
- arena = self.lca(t.source, t.target)
- # Arena must be an Or-state:
- while not isinstance(arena.type, OrState):
- arena = arena.parent
- # Exit states can be efficiently computed at runtime based on the set of current states.
- # Enter states are more complex but luckily, can be computed *partially* statically:
- # As a start, we calculate the enter path:
- # The enter path is the path from arena to the target state (including the target state, but not including the arena).
- # Enter path is the intersection between:
- # 1) the transition's target and its ancestors, and
- # 2) the arena's descendants
- enter_path = (t.target.state_id_bitmap | t.target.ancestors) & arena.descendants
- # All states on the enter path will be entered, but on the enter path, there may also be AND-states whose children are not on the enter path, but should also be entered.
- enter_path_iter = self.bitmap_to_states(enter_path)
- entered_state = next(enter_path_iter, None)
- enter_states_static = Bitmap()
- while entered_state is not None:
- next_entered_state = next(enter_path_iter, None)
- if next_entered_state:
- # an intermediate state on the path from arena to target
- enter_states_static |= states_to_bitmap(entered_state.type.additional_effective_targets(next_entered_state))
- else:
- # the actual target of the transition
- enter_states_static |= entered_state.effective_targets
- entered_state = next_entered_state
- target_history_id = None
- if isinstance(t.target, HistoryState):
- target_history_id = t.target.history_id
- target_stable = False
- if isinstance(t.target, State):
- target_stable = t.target.stable
- raised_events = []
- for a in t.actions:
- if isinstance(a, RaiseInternalEvent):
- raised_events.append(a.name)
- t.id = t_id
- t.exit_mask = arena.descendants
- t.arena = arena
- t.arena_bitmap = arena.descendants | arena.state_id_bitmap
- t.enter_states_static = enter_states_static
- t.target_history_id = target_history_id
- t.target_stable = target_stable
- t.raised_events = raised_events
- def bitmap_to_states(self, bitmap: Bitmap) -> Iterator[State]:
- return (self.state_list[id] for id in bm_items(bitmap))
- def bitmap_to_states_reverse(self, bitmap: Bitmap) -> Iterator[State]:
- return (self.state_list[id] for id in bm_reverse_items(bitmap))
- def lca(self, s1: State, s2: State) -> State:
- # Intersection between source & target ancestors, last member in depth-first sorted state list.
- return self.state_list[bm_highest_bit((s1.ancestors) & (s2.ancestors))]
- def states_to_bitmap(states: Iterable[State]) -> Bitmap:
- return bm_from_list(s.state_id for s in states)
- # Is parent ancestor of child? Also returns true when parent IS child.
- # If this function returns True, and child is a current state, then parent will be too.
- def is_ancestor(parent: State, child: State) -> bool:
- return bm_has(child.ancestors | child.state_id_bitmap, parent.state_id)
|