diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2021-06-16 19:06:38 +0200 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2021-06-16 19:08:46 +0200 |
commit | cdb62c9f8cd591297bff1c3384dc56d5c752a7bd (patch) | |
tree | ec4512df23ea8f4b0414d71a43f7e179ea222e54 | |
parent | interface: Fix mapper prefix (diff) | |
download | rtems-central-cdb62c9f8cd591297bff1c3384dc56d5c752a7bd.tar.bz2 |
validation: Split file
-rw-r--r-- | rtemsspec/transitionmap.py | 670 | ||||
-rw-r--r-- | rtemsspec/validation.py | 645 | ||||
-rwxr-xr-x | specview.py | 2 |
3 files changed, 676 insertions, 641 deletions
diff --git a/rtemsspec/transitionmap.py b/rtemsspec/transitionmap.py new file mode 100644 index 00000000..c7f2193a --- /dev/null +++ b/rtemsspec/transitionmap.py @@ -0,0 +1,670 @@ +# SPDX-License-Identifier: BSD-2-Clause +""" +This module provides the Transition and TransitionMap classes used to work with +action requirements. +""" + +# Copyright (C) 2020, 2021 embedded brains GmbH (http://www.embedded-brains.de) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import itertools +import math +import textwrap +from typing import Any, Dict, Iterator, List, NamedTuple, Optional, Tuple + +from rtemsspec.content import CContent, enabled_by_to_exp, ExpressionMapper +from rtemsspec.items import is_enabled, Item + + +class Transition(NamedTuple): + """ Represents a action requirement transition map entry. """ + desc_idx: int + enabled_by: Any + skip: int + pre_cond_na: Tuple[int, ...] + post_cond: Tuple[Any, ...] + + +def _variant_to_key(variant: Transition) -> str: + return "".join((enabled_by_to_exp(variant.enabled_by, + ExpressionMapper()), str(variant.skip), + str(variant.pre_cond_na), str(variant.post_cond))) + + +class _TransitionEntry: + def __init__(self): + self.key = "" + self.variants = [] # type: List[Transition] + + def __bool__(self): + return bool(self.variants) + + def __getitem__(self, key): + return self.variants[key] + + def __len__(self): + return len(self.variants) + + def add(self, variant: Transition) -> None: + """ Adds the variant to the transitions of the entry. """ + self.key += _variant_to_key(variant) + self.variants.append(variant) + + def replace(self, index: int, variant: Transition) -> None: + """ Replace the variant at transition variant index. """ + self.key = self.key.replace(_variant_to_key(self.variants[index]), + _variant_to_key(variant)) + self.variants[index] = variant + + +_TransitionMap = List[_TransitionEntry] + + +def _to_st_idx(conditions: List[Any]) -> Tuple[Dict[str, int], ...]: + return tuple( + dict((state["name"], st_idx) for st_idx, state in enumerate( + itertools.chain(condition["states"], [{ + "name": "N/A" + }]))) for condition in conditions) + + +def _to_st_name(conditions: List[Any]) -> Tuple[Tuple[str, ...], ...]: + return tuple( + tuple( + itertools.chain((state["name"] + for state in condition["states"]), ["NA"])) + for condition in conditions) + + +class _PostCondContext(NamedTuple): + transition_map: "TransitionMap" + map_idx: int + pre_co_states: Tuple[int, ...] + post_co_states: Tuple[Any, ...] + post_co_idx: int + ops: Any + + +def _post_cond_bool_and(ctx: _PostCondContext, exp: Any) -> bool: + for element in exp: + if not _post_cond_bool_exp(ctx, element): + return False + return True + + +def _post_cond_bool_not(ctx: _PostCondContext, exp: Any) -> bool: + return not _post_cond_bool_exp(ctx, exp) + + +def _post_cond_bool_or(ctx: _PostCondContext, exp: Any) -> bool: + for element in exp: + if _post_cond_bool_exp(ctx, element): + return True + return False + + +def _post_cond_bool_post_cond(ctx: _PostCondContext, exp: Any) -> bool: + for post_co_name, status in exp.items(): + if isinstance(status, str): + status = [status] + post_co_idx = ctx.transition_map.post_co_name_to_co_idx(post_co_name) + st_idx = [ + ctx.transition_map.post_co_idx_st_name_to_st_idx( + post_co_idx, st_name) for st_name in status + ] + if ctx.post_co_states[post_co_idx] not in st_idx: + return False + return True + + +def _post_cond_bool_pre_cond(ctx: _PostCondContext, exp: Any) -> bool: + for pre_co_name, status in exp.items(): + if isinstance(status, str): + status = [status] + pre_co_idx = ctx.transition_map.pre_co_name_to_co_idx(pre_co_name) + st_idx = [ + ctx.transition_map.pre_co_idx_st_name_to_st_idx( + pre_co_idx, st_name) for st_name in status + ] + if ctx.pre_co_states[pre_co_idx] not in st_idx: + return False + return True + + +_POST_COND_BOOL_OPS = { + "and": _post_cond_bool_and, + "not": _post_cond_bool_not, + "or": _post_cond_bool_or, + "post-conditions": _post_cond_bool_post_cond, + "pre-conditions": _post_cond_bool_pre_cond, +} + + +def _post_cond_bool_exp(ctx: _PostCondContext, exp: Any) -> Optional[int]: + if isinstance(exp, list): + return _post_cond_bool_or(ctx, exp) + key = next(iter(exp)) + return _POST_COND_BOOL_OPS[key](ctx, exp[key]) + + +def _post_cond_do_specified_by(ctx: _PostCondContext, pre_co_name: str) -> int: + pre_co_idx = ctx.transition_map.pre_co_name_to_co_idx(pre_co_name) + st_name = ctx.transition_map.pre_co_idx_st_idx_to_st_name( + pre_co_idx, ctx.pre_co_states[pre_co_idx]) + return ctx.transition_map.post_co_idx_st_name_to_st_idx( + ctx.post_co_idx, st_name) + + +def _post_cond_if(ctx: _PostCondContext) -> Optional[int]: + if _post_cond_bool_exp(ctx, ctx.ops["if"]): + if "then-specified-by" in ctx.ops: + return _post_cond_do_specified_by(ctx, + ctx.ops["then-specified-by"]) + return ctx.transition_map.post_co_idx_st_name_to_st_idx( + ctx.post_co_idx, ctx.ops["then"]) + return None + + +def _post_cond_specified_by(ctx: _PostCondContext) -> Optional[int]: + return _post_cond_do_specified_by(ctx, ctx.ops["specified-by"]) + + +def _post_cond_else(ctx: _PostCondContext) -> Optional[int]: + return ctx.transition_map.post_co_idx_st_name_to_st_idx( + ctx.post_co_idx, ctx.ops["else"]) + + +_POST_COND_OP = { + "else": _post_cond_else, + "if": _post_cond_if, + "specified-by": _post_cond_specified_by, +} + +PostCond = Tuple[int, ...] + +PreCondsOfPostCond = List[Tuple[List[int], ...]] + + +def _compact(pre_conds: PreCondsOfPostCond) -> PreCondsOfPostCond: + while True: + last = pre_conds[0] + combined_pre_conds = [last] + combined_count = 0 + for row in pre_conds[1:]: + diff = [ + index for index, states in enumerate(last) + if states != row[index] + ] + if len(diff) == 1: + index = diff[0] + combined_count += 1 + last[index].extend(row[index]) + else: + combined_pre_conds.append(row) + last = row + pre_conds = combined_pre_conds + if combined_count == 0: + break + return pre_conds + + +def _compact_more(pre_conds: PreCondsOfPostCond) -> PreCondsOfPostCond: + while True: + combined_count = 0 + next_pre_conds = [] + while pre_conds: + first = pre_conds.pop(0) + next_pre_conds.append(first) + for row in pre_conds: + diff = [ + index for index, states in enumerate(first) + if states != row[index] + ] + if len(diff) <= 1: + if diff: + index = diff[0] + first[index].extend(row[index]) + combined_count += 1 + pre_conds.remove(row) + pre_conds = next_pre_conds + if combined_count == 0: + break + return pre_conds + + +class TransitionMap: + """ Representation of an action requirement transition map. """ + + # pylint: disable=too-many-instance-attributes + def __init__(self, item: Item): + self._item = item + self._pre_co_count = len(item["pre-conditions"]) + self._post_co_count = len(item["post-conditions"]) + self.pre_co_summary = tuple(0 for _ in range(self._pre_co_count + 1)) + self._pre_co_idx_st_idx_to_st_name = _to_st_name( + item["pre-conditions"]) + self._post_co_idx_st_idx_to_st_name = _to_st_name( + item["post-conditions"]) + self._pre_co_idx_st_name_to_st_idx = _to_st_idx(item["pre-conditions"]) + self._post_co_idx_st_name_to_st_idx = _to_st_idx( + item["post-conditions"]) + self._pre_co_idx_to_cond = dict( + (co_idx, condition) + for co_idx, condition in enumerate(item["pre-conditions"])) + self._pre_co_name_to_co_idx = dict( + (condition["name"], co_idx) + for co_idx, condition in enumerate(item["pre-conditions"])) + self._post_co_name_to_co_idx = dict( + (condition["name"], co_idx) + for co_idx, condition in enumerate(item["post-conditions"])) + self._post_co_idx_to_co_name = dict( + (co_idx, condition["name"]) + for co_idx, condition in enumerate(item["post-conditions"])) + self._skip_idx_to_name = dict( + (skip_idx + 1, key) + for skip_idx, key in enumerate(item["skip-reasons"].keys())) + self._skip_name_to_idx = dict( + (key, skip_idx + 1) + for skip_idx, key in enumerate(item["skip-reasons"].keys())) + self._entries = {} # type: Dict[str, List[Any]] + self._map = self._build_map() + self._post_process() + + def __getitem__(self, key: str): + return self._item[key] + + def __iter__(self): + yield from self._map + + def entries(self) -> Iterator[List[Any]]: + """ Yields the transition map entry variants sorted by frequency. """ + yield from sorted(self._entries.values(), key=lambda x: x[1]) + + def get_variants(self, + enabled: List[str]) -> Iterator[Tuple[int, Transition]]: + """ + Yields the map index and the transition variants enabled by the enabled + list. + """ + for map_idx, transitions in enumerate(self._map): + for variant in transitions[1:]: + if is_enabled(enabled, variant.enabled_by): + break + else: + variant = transitions[0] + yield map_idx, variant + + def get_post_conditions( + self, enabled: List[str] + ) -> Iterator[Tuple[PostCond, PreCondsOfPostCond]]: + """ + Yields tuples of post-condition variants and the corresponding + pre-condition variants which are enabled by the enabled list. + + The first entry of the post-condition variant is the skip index. The + remaining entries are post-condition indices. The pre-condition + variants are a list of tuples. Each tuple entry corresponds to a + pre-condition and provides a list of corresponding pre-condition state + indices. + """ + entries = {} # type: Dict[PostCond, PreCondsOfPostCond] + for map_idx, variant in self.get_variants(enabled): + key = (variant.skip, ) + variant.post_cond + entry = entries.setdefault(key, []) + entry.append( + tuple([state] for state in self.map_idx_to_pre_co_states( + map_idx, variant.pre_cond_na))) + for post_cond, pre_conds in sorted(entries.items(), + key=lambda x: (x[0][0], len(x[1]))): + pre_conds = _compact_more(_compact(pre_conds)) + yield post_cond, pre_conds + + def _post_process(self) -> None: + for map_idx, transitions in enumerate(self): + if not transitions or not isinstance( + transitions[0].enabled_by, + bool) or not transitions[0].enabled_by: + raise ValueError( + f"transition map of {self._item.spec} contains no default " + "entry for pre-condition set " + f"{{{self._map_index_to_pre_conditions(map_idx)}}}") + entry = self._entries.setdefault(transitions.key, + [0, 0, transitions, []]) + entry[0] += 1 + entry[3].append(map_idx) + for index, entry in enumerate( + sorted(self._entries.values(), + key=lambda x: x[0], + reverse=True)): + entry[1] = index + + def _map_index_to_pre_conditions(self, map_idx: int) -> str: + conditions = [] + for condition in reversed(self._item["pre-conditions"]): + states = condition["states"] + count = len(states) + st_idx = int(map_idx % count) + conditions.append(f"{condition['name']}={states[st_idx]['name']}") + map_idx //= count + return ", ".join(reversed(conditions)) + + def map_idx_to_pre_co_states( + self, map_idx: int, pre_cond_na: Tuple[int, + ...]) -> Tuple[int, ...]: + """ + Maps the transition map index and the associated pre-condition state + indices. + """ + co_states = [] + for condition in reversed(self._item["pre-conditions"]): + count = len(condition["states"]) + co_states.append(count if pre_cond_na[self._pre_co_name_to_co_idx[ + condition["name"]]] else int(map_idx % count)) + map_idx //= count + return tuple(reversed(co_states)) + + def pre_co_name_to_co_idx(self, co_name: str) -> int: + """ + Maps the pre-condition name to the associated pre-condition index. + """ + return self._pre_co_name_to_co_idx[co_name] + + def pre_co_idx_to_co_name(self, co_idx: int) -> str: + """ + Maps the pre-condition index to the associated pre-condition name. + """ + return self._pre_co_idx_to_cond[co_idx]["name"] + + def post_co_name_to_co_idx(self, co_name: str) -> int: + """ + Maps the post-condition name to the associated post-condition index. + """ + return self._post_co_name_to_co_idx[co_name] + + def post_co_idx_to_co_name(self, co_idx: int) -> str: + """ + Maps the post-condition index to the associated post-condition name. + """ + return self._post_co_idx_to_co_name[co_idx] + + def pre_co_idx_st_idx_to_st_name(self, co_idx: int, st_idx: int) -> str: + """ + Maps the pre-condition name and state index to the associated state + name. + """ + return self._pre_co_idx_st_idx_to_st_name[co_idx][st_idx] + + def post_co_idx_st_idx_to_st_name(self, co_idx: int, st_idx: int) -> str: + """ + Maps the post-condition name and state index to the associated state + name. + """ + return self._post_co_idx_st_idx_to_st_name[co_idx][st_idx] + + def pre_co_idx_st_name_to_st_idx(self, co_idx: int, st_name: str) -> int: + """ + Maps the pre-condition index and state name to the associated state + index. + """ + return self._pre_co_idx_st_name_to_st_idx[co_idx][st_name] + + def post_co_idx_st_name_to_st_idx(self, co_idx: int, st_name: str) -> int: + """ + Maps the post-condition index and state name to the associated state + index. + """ + return self._post_co_idx_st_name_to_st_idx[co_idx][st_name] + + def skip_idx_to_name(self, skip_idx: int) -> str: + """ + Maps the skip index the associated skip name index. + """ + return self._skip_idx_to_name[skip_idx] + + def _map_post_cond(self, map_idx: int, co_idx: int, + variant: Transition) -> Transition: + if isinstance(variant.post_cond[co_idx], int): + return variant + pre_co_states = self.map_idx_to_pre_co_states(map_idx, + variant.pre_cond_na) + for ops in variant.post_cond[co_idx]: + idx = _POST_COND_OP[next(iter(ops))](_PostCondContext( + self, map_idx, pre_co_states, variant.post_cond, co_idx, ops)) + if idx is not None: + return Transition( + variant.desc_idx, variant.enabled_by, variant.skip, + variant.pre_cond_na, variant.post_cond[0:co_idx] + + (idx, ) + variant.post_cond[co_idx + 1:]) + raise ValueError( + "cannot determine state for post-condition " + f"'{self._post_co_idx_to_co_name[co_idx]}' of transition map " + f"descriptor {variant.desc_idx} of {self._item.spec} for " + "pre-condition set " + f"{{{self._map_index_to_pre_conditions(map_idx)}}}") + + def _make_post_cond(self, map_idx: int, variant: Transition) -> Transition: + for co_idx in range(len(variant.post_cond)): + variant = self._map_post_cond(map_idx, co_idx, variant) + return variant + + def _add_variant(self, transition_map: _TransitionMap, map_idx: int, + variant: Transition) -> None: + if transition_map[map_idx]: + for index, existing in enumerate(transition_map[map_idx].variants): + if existing.enabled_by == variant.enabled_by: + if variant.skip: + # Allow transition map variants with a skip reason to + # overwrite existing variants with the same enabled-by + # attribute. This is important if variants use N/A for + # some pre-conditions. It makes it also easier to skip + # pre-conditon states which are controlled by build + # options. + transition_map[map_idx].replace(index, variant) + return + raise ValueError( + f"transition map descriptor {variant.desc_idx} of " + f"{self._item.spec} duplicates pre-condition set " + f"{{{self._map_index_to_pre_conditions(map_idx)}}}" + " defined by transition map descriptor " + f"{existing.desc_idx}") + default = transition_map[map_idx][0] + if (default.post_cond, default.skip, + default.pre_cond_na) == (variant.post_cond, variant.skip, + variant.pre_cond_na): + return + elif not isinstance(variant.enabled_by, + bool) or not variant.enabled_by: + raise ValueError( + f"transition map descriptor {variant.desc_idx} of " + f"{self._item.spec} is the first variant for " + f"{{{self._map_index_to_pre_conditions(map_idx)}}} " + "and it is not enabled by default") + self.pre_co_summary = tuple( + a + b for a, b in zip(self.pre_co_summary, (variant.skip, ) + + variant.pre_cond_na)) + transition_map[map_idx].add(variant) + + def _add_transitions(self, transition_map: _TransitionMap, + desc: Dict[str, Any], desc_idx: int, + skip_post_cond: Tuple[Any, ...], co_idx: int, + map_idx: int, pre_cond_na: Tuple[int, ...]) -> None: + # pylint: disable=too-many-arguments + # pylint: disable=too-many-locals + if co_idx < self._pre_co_count: + condition = self._pre_co_idx_to_cond[co_idx] + state_count = len(condition["states"]) + map_idx *= state_count + states = desc["pre-conditions"][condition["name"]] + if isinstance(states, str): + assert states in ["all", "N/A"] + for st_idx in range(state_count): + self._add_transitions( + transition_map, desc, desc_idx, skip_post_cond, + co_idx + 1, map_idx + st_idx, + pre_cond_na + (int(states == "N/A"), )) + else: + for st_name in states: + try: + st_idx = self._pre_co_idx_st_name_to_st_idx[co_idx][ + st_name] + except KeyError as err: + msg = (f"transition map descriptor {desc_idx} of " + f"{self._item.spec} refers to non-existent " + f"state {err} of pre-condition " + f"'{condition['name']}'") + raise ValueError(msg) from err + self._add_transitions(transition_map, desc, desc_idx, + skip_post_cond, co_idx + 1, + map_idx + st_idx, + pre_cond_na + (0, )) + else: + variant = self._make_post_cond( + map_idx, + Transition(desc_idx, desc["enabled-by"], skip_post_cond[0], + pre_cond_na, skip_post_cond[1:])) + self._add_variant(transition_map, map_idx, variant) + + def _add_default(self, transition_map: _TransitionMap, desc: Dict[str, + Any], + desc_idx: int, skip_post_cond: Tuple[int, ...]) -> None: + enabled_by = desc["enabled-by"] + for map_idx, transition in enumerate(transition_map): + if not transition: + transition.add( + self._make_post_cond( + map_idx, + Transition(desc_idx, enabled_by, skip_post_cond[0], + (0, ) * self._pre_co_count, + skip_post_cond[1:]))) + + def _get_post_cond(self, desc: Dict[str, Any], co_idx: int) -> Any: + info = desc["post-conditions"][self._post_co_idx_to_co_name[co_idx]] + if isinstance(info, str): + return self._post_co_idx_st_name_to_st_idx[co_idx][info] + return info + + def _build_map(self) -> _TransitionMap: + transition_count = 1 + for condition in self["pre-conditions"]: + state_count = len(condition["states"]) + if state_count == 0: + raise ValueError(f"pre-condition '{condition['name']}' of " + f"{self._item.spec} has no states") + transition_count *= state_count + transition_map = [_TransitionEntry() for _ in range(transition_count)] + for desc_idx, desc in enumerate(self["transition-map"]): + if isinstance(desc["post-conditions"], dict): + try: + skip_post_cond = (0, ) + tuple( + self._get_post_cond(desc, co_idx) + for co_idx in range(self._post_co_count)) + except KeyError as err: + msg = (f"transition map descriptor {desc_idx} of " + f"{self._item.spec} refers to non-existent " + f"post-condition state {err}") + raise ValueError(msg) from err + else: + skip_post_cond = ( + self._skip_name_to_idx[desc["post-conditions"]], ) + tuple( + self._post_co_idx_st_name_to_st_idx[co_idx]["N/A"] + for co_idx in range(self._post_co_count)) + if isinstance(desc["pre-conditions"], dict): + self._add_transitions(transition_map, desc, desc_idx, + skip_post_cond, 0, 0, ()) + else: + assert desc["pre-conditions"] == "default" + self._add_default(transition_map, desc, desc_idx, + skip_post_cond) + return transition_map + + def _get_entry(self, ident: str, variant: Transition) -> str: + text = "{ " + ", ".join( + itertools.chain( + map(str, (int(variant.skip != 0), ) + variant.pre_cond_na), + ((f"{ident}_Post_{self._post_co_idx_to_co_name[co_idx]}" + f"_{self._post_co_idx_st_idx_to_st_name[co_idx][st_idx]}") + for co_idx, st_idx in enumerate(variant.post_cond)))) + wrapper = textwrap.TextWrapper() + wrapper.initial_indent = " " + wrapper.subsequent_indent = " " + wrapper.width = 79 + return "\n".join(wrapper.wrap(text)) + " }," + + def _get_entry_bits(self) -> int: + bits = self._pre_co_count + 1 + for st_idx_to_st_name in self._post_co_idx_st_idx_to_st_name: + bits += math.ceil(math.log2(len(st_idx_to_st_name))) + return 2**max(math.ceil(math.log2(bits)), 3) + + def add_map(self, content: CContent, ident: str) -> None: + """ Adds the transition map definitions to the content. """ + entries = [] + mapper = ExpressionMapper() + for entry in self.entries(): + transitions = entry[2] + if len(transitions) == 1: + entries.append(self._get_entry(ident, transitions[0])) + else: + ifelse = "#if " + enumerators = [] # type: List[str] + for variant in transitions[1:]: + enumerators.append( + ifelse + enabled_by_to_exp(variant.enabled_by, mapper)) + enumerators.append(self._get_entry(ident, variant)) + ifelse = "#elif " + enumerators.append("#else") + enumerators.append(self._get_entry(ident, transitions[0])) + enumerators.append("#endif") + entries.append("\n".join(enumerators)) + bits = self._get_entry_bits() + content.add("typedef struct {") + with content.indent(): + content.append(f"uint{bits}_t Skip : 1;") + for condition in self["pre-conditions"]: + content.append(f"uint{bits}_t Pre_{condition['name']}_NA : 1;") + for condition in self["post-conditions"]: + state_bits = math.ceil(math.log2(len(condition["states"]) + 1)) + content.append( + f"uint{bits}_t Post_{condition['name']} : {state_bits};") + content.add(f"}} {ident}_Entry;") + content.add([f"static const {ident}_Entry", f"{ident}_Entries[] = {{"]) + entries[-1] = entries[-1].replace("},", "}") + content.append(entries) + bits = max(8, math.ceil(math.log2(len(self._entries)) / 8) * 8) + content.append( + ["};", "", f"static const uint{bits}_t", f"{ident}_Map[] = {{"]) + text = ", ".join( + str(self._entries[transitions.key][1]) + for transitions in self._map) + wrapper = textwrap.TextWrapper() + wrapper.initial_indent = " " + wrapper.subsequent_indent = " " + wrapper.width = 79 + content.append(wrapper.wrap(text)) + content.append("};") + + def get_post_entry_member(self, co_idx: int) -> str: + """ + Gets the post-condition entry member name for the post-condition index. + """ + return f"Post_{self._post_co_idx_to_co_name[co_idx]}" diff --git a/rtemsspec/validation.py b/rtemsspec/validation.py index 70ed7380..24efc210 100644 --- a/rtemsspec/validation.py +++ b/rtemsspec/validation.py @@ -24,20 +24,17 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -# pylint: disable=too-many-lines - import itertools -import math import os import re -import textwrap -from typing import Any, Dict, Iterator, List, NamedTuple, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple -from rtemsspec.content import CContent, CInclude, enabled_by_to_exp, \ - ExpressionMapper, GenericContent, get_value_params, get_value_plural, \ +from rtemsspec.content import CContent, CInclude, \ + GenericContent, get_value_params, get_value_plural, \ get_value_doxygen_group, get_value_doxygen_function, to_camel_case -from rtemsspec.items import is_enabled, Item, ItemCache, \ +from rtemsspec.items import Item, ItemCache, \ ItemGetValueContext, ItemMapper +from rtemsspec.transitionmap import TransitionMap ItemMap = Dict[str, Item] @@ -466,639 +463,7 @@ class _TestSuiteItem(_TestItem): content.add("/** @} */") -class Transition(NamedTuple): - """ Represents a action requirement transition map entry. """ - desc_idx: int - enabled_by: Any - skip: int - pre_cond_na: Tuple[int, ...] - post_cond: Tuple[Any, ...] - - -def _variant_to_key(variant: Transition) -> str: - return "".join((enabled_by_to_exp(variant.enabled_by, - ExpressionMapper()), str(variant.skip), - str(variant.pre_cond_na), str(variant.post_cond))) - - -class _TransitionEntry: - def __init__(self): - self.key = "" - self.variants = [] # type: List[Transition] - - def __bool__(self): - return bool(self.variants) - - def __getitem__(self, key): - return self.variants[key] - - def __len__(self): - return len(self.variants) - - def add(self, variant: Transition) -> None: - """ Adds the variant to the transitions of the entry. """ - self.key += _variant_to_key(variant) - self.variants.append(variant) - - def replace(self, index: int, variant: Transition) -> None: - """ Replace the variant at transition variant index. """ - self.key = self.key.replace(_variant_to_key(self.variants[index]), - _variant_to_key(variant)) - self.variants[index] = variant - - _IdxToX = Tuple[Tuple[str, ...], ...] -_TransitionMap = List[_TransitionEntry] - - -def _to_st_idx(conditions: List[Any]) -> Tuple[Dict[str, int], ...]: - return tuple( - dict((state["name"], st_idx) for st_idx, state in enumerate( - itertools.chain(condition["states"], [{ - "name": "N/A" - }]))) for condition in conditions) - - -def _to_st_name(conditions: List[Any]) -> _IdxToX: - return tuple( - tuple( - itertools.chain((state["name"] - for state in condition["states"]), ["NA"])) - for condition in conditions) - - -class _PostCondContext(NamedTuple): - transition_map: "TransitionMap" - map_idx: int - pre_co_states: Tuple[int, ...] - post_co_states: Tuple[Any, ...] - post_co_idx: int - ops: Any - - -def _post_cond_bool_and(ctx: _PostCondContext, exp: Any) -> bool: - for element in exp: - if not _post_cond_bool_exp(ctx, element): - return False - return True - - -def _post_cond_bool_not(ctx: _PostCondContext, exp: Any) -> bool: - return not _post_cond_bool_exp(ctx, exp) - - -def _post_cond_bool_or(ctx: _PostCondContext, exp: Any) -> bool: - for element in exp: - if _post_cond_bool_exp(ctx, element): - return True - return False - - -def _post_cond_bool_post_cond(ctx: _PostCondContext, exp: Any) -> bool: - for post_co_name, status in exp.items(): - if isinstance(status, str): - status = [status] - post_co_idx = ctx.transition_map.post_co_name_to_co_idx(post_co_name) - st_idx = [ - ctx.transition_map.post_co_idx_st_name_to_st_idx( - post_co_idx, st_name) for st_name in status - ] - if ctx.post_co_states[post_co_idx] not in st_idx: - return False - return True - - -def _post_cond_bool_pre_cond(ctx: _PostCondContext, exp: Any) -> bool: - for pre_co_name, status in exp.items(): - if isinstance(status, str): - status = [status] - pre_co_idx = ctx.transition_map.pre_co_name_to_co_idx(pre_co_name) - st_idx = [ - ctx.transition_map.pre_co_idx_st_name_to_st_idx( - pre_co_idx, st_name) for st_name in status - ] - if ctx.pre_co_states[pre_co_idx] not in st_idx: - return False - return True - - -_POST_COND_BOOL_OPS = { - "and": _post_cond_bool_and, - "not": _post_cond_bool_not, - "or": _post_cond_bool_or, - "post-conditions": _post_cond_bool_post_cond, - "pre-conditions": _post_cond_bool_pre_cond, -} - - -def _post_cond_bool_exp(ctx: _PostCondContext, exp: Any) -> Optional[int]: - if isinstance(exp, list): - return _post_cond_bool_or(ctx, exp) - key = next(iter(exp)) - return _POST_COND_BOOL_OPS[key](ctx, exp[key]) - - -def _post_cond_do_specified_by(ctx: _PostCondContext, pre_co_name: str) -> int: - pre_co_idx = ctx.transition_map.pre_co_name_to_co_idx(pre_co_name) - st_name = ctx.transition_map.pre_co_idx_st_idx_to_st_name( - pre_co_idx, ctx.pre_co_states[pre_co_idx]) - return ctx.transition_map.post_co_idx_st_name_to_st_idx( - ctx.post_co_idx, st_name) - - -def _post_cond_if(ctx: _PostCondContext) -> Optional[int]: - if _post_cond_bool_exp(ctx, ctx.ops["if"]): - if "then-specified-by" in ctx.ops: - return _post_cond_do_specified_by(ctx, - ctx.ops["then-specified-by"]) - return ctx.transition_map.post_co_idx_st_name_to_st_idx( - ctx.post_co_idx, ctx.ops["then"]) - return None - - -def _post_cond_specified_by(ctx: _PostCondContext) -> Optional[int]: - return _post_cond_do_specified_by(ctx, ctx.ops["specified-by"]) - - -def _post_cond_else(ctx: _PostCondContext) -> Optional[int]: - return ctx.transition_map.post_co_idx_st_name_to_st_idx( - ctx.post_co_idx, ctx.ops["else"]) - - -_POST_COND_OP = { - "else": _post_cond_else, - "if": _post_cond_if, - "specified-by": _post_cond_specified_by, -} - -PostCond = Tuple[int, ...] - -PreCondsOfPostCond = List[Tuple[List[int], ...]] - - -def _compact(pre_conds: PreCondsOfPostCond) -> PreCondsOfPostCond: - while True: - last = pre_conds[0] - combined_pre_conds = [last] - combined_count = 0 - for row in pre_conds[1:]: - diff = [ - index for index, states in enumerate(last) - if states != row[index] - ] - if len(diff) == 1: - index = diff[0] - combined_count += 1 - last[index].extend(row[index]) - else: - combined_pre_conds.append(row) - last = row - pre_conds = combined_pre_conds - if combined_count == 0: - break - return pre_conds - - -def _compact_more(pre_conds: PreCondsOfPostCond) -> PreCondsOfPostCond: - while True: - combined_count = 0 - next_pre_conds = [] - while pre_conds: - first = pre_conds.pop(0) - next_pre_conds.append(first) - for row in pre_conds: - diff = [ - index for index, states in enumerate(first) - if states != row[index] - ] - if len(diff) <= 1: - if diff: - index = diff[0] - first[index].extend(row[index]) - combined_count += 1 - pre_conds.remove(row) - pre_conds = next_pre_conds - if combined_count == 0: - break - return pre_conds - - -class TransitionMap: - """ Representation of an action requirement transition map. """ - - # pylint: disable=too-many-instance-attributes - def __init__(self, item: Item): - self._item = item - self._pre_co_count = len(item["pre-conditions"]) - self._post_co_count = len(item["post-conditions"]) - self.pre_co_summary = tuple(0 for _ in range(self._pre_co_count + 1)) - self._pre_co_idx_st_idx_to_st_name = _to_st_name( - item["pre-conditions"]) - self._post_co_idx_st_idx_to_st_name = _to_st_name( - item["post-conditions"]) - self._pre_co_idx_st_name_to_st_idx = _to_st_idx(item["pre-conditions"]) - self._post_co_idx_st_name_to_st_idx = _to_st_idx( - item["post-conditions"]) - self._pre_co_idx_to_cond = dict( - (co_idx, condition) - for co_idx, condition in enumerate(item["pre-conditions"])) - self._pre_co_name_to_co_idx = dict( - (condition["name"], co_idx) - for co_idx, condition in enumerate(item["pre-conditions"])) - self._post_co_name_to_co_idx = dict( - (condition["name"], co_idx) - for co_idx, condition in enumerate(item["post-conditions"])) - self._post_co_idx_to_co_name = dict( - (co_idx, condition["name"]) - for co_idx, condition in enumerate(item["post-conditions"])) - self._skip_idx_to_name = dict( - (skip_idx + 1, key) - for skip_idx, key in enumerate(item["skip-reasons"].keys())) - self._skip_name_to_idx = dict( - (key, skip_idx + 1) - for skip_idx, key in enumerate(item["skip-reasons"].keys())) - self._entries = {} # type: Dict[str, List[Any]] - self._map = self._build_map() - self._post_process() - - def __getitem__(self, key: str): - return self._item[key] - - def __iter__(self): - yield from self._map - - def entries(self) -> Iterator[List[Any]]: - """ Yields the transition map entry variants sorted by frequency. """ - yield from sorted(self._entries.values(), key=lambda x: x[1]) - - def get_variants(self, - enabled: List[str]) -> Iterator[Tuple[int, Transition]]: - """ - Yields the map index and the transition variants enabled by the enabled - list. - """ - for map_idx, transitions in enumerate(self._map): - for variant in transitions[1:]: - if is_enabled(enabled, variant.enabled_by): - break - else: - variant = transitions[0] - yield map_idx, variant - - def get_post_conditions( - self, enabled: List[str] - ) -> Iterator[Tuple[PostCond, PreCondsOfPostCond]]: - """ - Yields tuples of post-condition variants and the corresponding - pre-condition variants which are enabled by the enabled list. - - The first entry of the post-condition variant is the skip index. The - remaining entries are post-condition indices. The pre-condition - variants are a list of tuples. Each tuple entry corresponds to a - pre-condition and provides a list of corresponding pre-condition state - indices. - """ - entries = {} # type: Dict[PostCond, PreCondsOfPostCond] - for map_idx, variant in self.get_variants(enabled): - key = (variant.skip, ) + variant.post_cond - entry = entries.setdefault(key, []) - entry.append( - tuple([state] for state in self.map_idx_to_pre_co_states( - map_idx, variant.pre_cond_na))) - for post_cond, pre_conds in sorted(entries.items(), - key=lambda x: (x[0][0], len(x[1]))): - pre_conds = _compact_more(_compact(pre_conds)) - yield post_cond, pre_conds - - def _post_process(self) -> None: - for map_idx, transitions in enumerate(self): - if not transitions or not isinstance( - transitions[0].enabled_by, - bool) or not transitions[0].enabled_by: - raise ValueError( - f"transition map of {self._item.spec} contains no default " - "entry for pre-condition set " - f"{{{self._map_index_to_pre_conditions(map_idx)}}}") - entry = self._entries.setdefault(transitions.key, - [0, 0, transitions, []]) - entry[0] += 1 - entry[3].append(map_idx) - for index, entry in enumerate( - sorted(self._entries.values(), - key=lambda x: x[0], - reverse=True)): - entry[1] = index - - def _map_index_to_pre_conditions(self, map_idx: int) -> str: - conditions = [] - for condition in reversed(self._item["pre-conditions"]): - states = condition["states"] - count = len(states) - st_idx = int(map_idx % count) - conditions.append(f"{condition['name']}={states[st_idx]['name']}") - map_idx //= count - return ", ".join(reversed(conditions)) - - def map_idx_to_pre_co_states( - self, map_idx: int, pre_cond_na: Tuple[int, - ...]) -> Tuple[int, ...]: - """ - Maps the transition map index and the associated pre-condition state - indices. - """ - co_states = [] - for condition in reversed(self._item["pre-conditions"]): - count = len(condition["states"]) - co_states.append(count if pre_cond_na[self._pre_co_name_to_co_idx[ - condition["name"]]] else int(map_idx % count)) - map_idx //= count - return tuple(reversed(co_states)) - - def pre_co_name_to_co_idx(self, co_name: str) -> int: - """ - Maps the pre-condition name to the associated pre-condition index. - """ - return self._pre_co_name_to_co_idx[co_name] - - def pre_co_idx_to_co_name(self, co_idx: int) -> str: - """ - Maps the pre-condition index to the associated pre-condition name. - """ - return self._pre_co_idx_to_cond[co_idx]["name"] - - def post_co_name_to_co_idx(self, co_name: str) -> int: - """ - Maps the post-condition name to the associated post-condition index. - """ - return self._post_co_name_to_co_idx[co_name] - - def post_co_idx_to_co_name(self, co_idx: int) -> str: - """ - Maps the post-condition index to the associated post-condition name. - """ - return self._post_co_idx_to_co_name[co_idx] - - def pre_co_idx_st_idx_to_st_name(self, co_idx: int, st_idx: int) -> str: - """ - Maps the pre-condition name and state index to the associated state - name. - """ - return self._pre_co_idx_st_idx_to_st_name[co_idx][st_idx] - - def post_co_idx_st_idx_to_st_name(self, co_idx: int, st_idx: int) -> str: - """ - Maps the post-condition name and state index to the associated state - name. - """ - return self._post_co_idx_st_idx_to_st_name[co_idx][st_idx] - - def pre_co_idx_st_name_to_st_idx(self, co_idx: int, st_name: str) -> int: - """ - Maps the pre-condition index and state name to the associated state - index. - """ - return self._pre_co_idx_st_name_to_st_idx[co_idx][st_name] - - def post_co_idx_st_name_to_st_idx(self, co_idx: int, st_name: str) -> int: - """ - Maps the post-condition index and state name to the associated state - index. - """ - return self._post_co_idx_st_name_to_st_idx[co_idx][st_name] - - def skip_idx_to_name(self, skip_idx: int) -> str: - """ - Maps the skip index the associated skip name index. - """ - return self._skip_idx_to_name[skip_idx] - - def _map_post_cond(self, map_idx: int, co_idx: int, - variant: Transition) -> Transition: - if isinstance(variant.post_cond[co_idx], int): - return variant - pre_co_states = self.map_idx_to_pre_co_states(map_idx, - variant.pre_cond_na) - for ops in variant.post_cond[co_idx]: - idx = _POST_COND_OP[next(iter(ops))](_PostCondContext( - self, map_idx, pre_co_states, variant.post_cond, co_idx, ops)) - if idx is not None: - return Transition( - variant.desc_idx, variant.enabled_by, variant.skip, - variant.pre_cond_na, variant.post_cond[0:co_idx] + - (idx, ) + variant.post_cond[co_idx + 1:]) - raise ValueError( - "cannot determine state for post-condition " - f"'{self._post_co_idx_to_co_name[co_idx]}' of transition map " - f"descriptor {variant.desc_idx} of {self._item.spec} for " - "pre-condition set " - f"{{{self._map_index_to_pre_conditions(map_idx)}}}") - - def _make_post_cond(self, map_idx: int, variant: Transition) -> Transition: - for co_idx in range(len(variant.post_cond)): - variant = self._map_post_cond(map_idx, co_idx, variant) - return variant - - def _add_variant(self, transition_map: _TransitionMap, map_idx: int, - variant: Transition) -> None: - if transition_map[map_idx]: - for index, existing in enumerate(transition_map[map_idx].variants): - if existing.enabled_by == variant.enabled_by: - if variant.skip: - # Allow transition map variants with a skip reason to - # overwrite existing variants with the same enabled-by - # attribute. This is important if variants use N/A for - # some pre-conditions. It makes it also easier to skip - # pre-conditon states which are controlled by build - # options. - transition_map[map_idx].replace(index, variant) - return - raise ValueError( - f"transition map descriptor {variant.desc_idx} of " - f"{self._item.spec} duplicates pre-condition set " - f"{{{self._map_index_to_pre_conditions(map_idx)}}}" - " defined by transition map descriptor " - f"{existing.desc_idx}") - default = transition_map[map_idx][0] - if (default.post_cond, default.skip, - default.pre_cond_na) == (variant.post_cond, variant.skip, - variant.pre_cond_na): - return - elif not isinstance(variant.enabled_by, - bool) or not variant.enabled_by: - raise ValueError( - f"transition map descriptor {variant.desc_idx} of " - f"{self._item.spec} is the first variant for " - f"{{{self._map_index_to_pre_conditions(map_idx)}}} " - "and it is not enabled by default") - self.pre_co_summary = tuple( - a + b for a, b in zip(self.pre_co_summary, (variant.skip, ) + - variant.pre_cond_na)) - transition_map[map_idx].add(variant) - - def _add_transitions(self, transition_map: _TransitionMap, - desc: Dict[str, Any], desc_idx: int, - skip_post_cond: Tuple[Any, ...], co_idx: int, - map_idx: int, pre_cond_na: Tuple[int, ...]) -> None: - # pylint: disable=too-many-arguments - # pylint: disable=too-many-locals - if co_idx < self._pre_co_count: - condition = self._pre_co_idx_to_cond[co_idx] - state_count = len(condition["states"]) - map_idx *= state_count - states = desc["pre-conditions"][condition["name"]] - if isinstance(states, str): - assert states in ["all", "N/A"] - for st_idx in range(state_count): - self._add_transitions( - transition_map, desc, desc_idx, skip_post_cond, - co_idx + 1, map_idx + st_idx, - pre_cond_na + (int(states == "N/A"), )) - else: - for st_name in states: - try: - st_idx = self._pre_co_idx_st_name_to_st_idx[co_idx][ - st_name] - except KeyError as err: - msg = (f"transition map descriptor {desc_idx} of " - f"{self._item.spec} refers to non-existent " - f"state {err} of pre-condition " - f"'{condition['name']}'") - raise ValueError(msg) from err - self._add_transitions(transition_map, desc, desc_idx, - skip_post_cond, co_idx + 1, - map_idx + st_idx, - pre_cond_na + (0, )) - else: - variant = self._make_post_cond( - map_idx, - Transition(desc_idx, desc["enabled-by"], skip_post_cond[0], - pre_cond_na, skip_post_cond[1:])) - self._add_variant(transition_map, map_idx, variant) - - def _add_default(self, transition_map: _TransitionMap, desc: Dict[str, - Any], - desc_idx: int, skip_post_cond: Tuple[int, ...]) -> None: - enabled_by = desc["enabled-by"] - for map_idx, transition in enumerate(transition_map): - if not transition: - transition.add( - self._make_post_cond( - map_idx, - Transition(desc_idx, enabled_by, skip_post_cond[0], - (0, ) * self._pre_co_count, - skip_post_cond[1:]))) - - def _get_post_cond(self, desc: Dict[str, Any], co_idx: int) -> Any: - info = desc["post-conditions"][self._post_co_idx_to_co_name[co_idx]] - if isinstance(info, str): - return self._post_co_idx_st_name_to_st_idx[co_idx][info] - return info - - def _build_map(self) -> _TransitionMap: - transition_count = 1 - for condition in self["pre-conditions"]: - state_count = len(condition["states"]) - if state_count == 0: - raise ValueError(f"pre-condition '{condition['name']}' of " - f"{self._item.spec} has no states") - transition_count *= state_count - transition_map = [_TransitionEntry() for _ in range(transition_count)] - for desc_idx, desc in enumerate(self["transition-map"]): - if isinstance(desc["post-conditions"], dict): - try: - skip_post_cond = (0, ) + tuple( - self._get_post_cond(desc, co_idx) - for co_idx in range(self._post_co_count)) - except KeyError as err: - msg = (f"transition map descriptor {desc_idx} of " - f"{self._item.spec} refers to non-existent " - f"post-condition state {err}") - raise ValueError(msg) from err - else: - skip_post_cond = ( - self._skip_name_to_idx[desc["post-conditions"]], ) + tuple( - self._post_co_idx_st_name_to_st_idx[co_idx]["N/A"] - for co_idx in range(self._post_co_count)) - if isinstance(desc["pre-conditions"], dict): - self._add_transitions(transition_map, desc, desc_idx, - skip_post_cond, 0, 0, ()) - else: - assert desc["pre-conditions"] == "default" - self._add_default(transition_map, desc, desc_idx, - skip_post_cond) - return transition_map - - def _get_entry(self, ident: str, variant: Transition) -> str: - text = "{ " + ", ".join( - itertools.chain( - map(str, (int(variant.skip != 0), ) + variant.pre_cond_na), - ((f"{ident}_Post_{self._post_co_idx_to_co_name[co_idx]}" - f"_{self._post_co_idx_st_idx_to_st_name[co_idx][st_idx]}") - for co_idx, st_idx in enumerate(variant.post_cond)))) - wrapper = textwrap.TextWrapper() - wrapper.initial_indent = " " - wrapper.subsequent_indent = " " - wrapper.width = 79 - return "\n".join(wrapper.wrap(text)) + " }," - - def _get_entry_bits(self) -> int: - bits = self._pre_co_count + 1 - for st_idx_to_st_name in self._post_co_idx_st_idx_to_st_name: - bits += math.ceil(math.log2(len(st_idx_to_st_name))) - return 2**max(math.ceil(math.log2(bits)), 3) - - def add_map(self, content: CContent, ident: str) -> None: - """ Adds the transition map definitions to the content. """ - entries = [] - mapper = ExpressionMapper() - for entry in self.entries(): - transitions = entry[2] - if len(transitions) == 1: - entries.append(self._get_entry(ident, transitions[0])) - else: - ifelse = "#if " - enumerators = [] # type: List[str] - for variant in transitions[1:]: - enumerators.append( - ifelse + enabled_by_to_exp(variant.enabled_by, mapper)) - enumerators.append(self._get_entry(ident, variant)) - ifelse = "#elif " - enumerators.append("#else") - enumerators.append(self._get_entry(ident, transitions[0])) - enumerators.append("#endif") - entries.append("\n".join(enumerators)) - bits = self._get_entry_bits() - content.add("typedef struct {") - with content.indent(): - content.append(f"uint{bits}_t Skip : 1;") - for condition in self["pre-conditions"]: - content.append(f"uint{bits}_t Pre_{condition['name']}_NA : 1;") - for condition in self["post-conditions"]: - state_bits = math.ceil(math.log2(len(condition["states"]) + 1)) - content.append( - f"uint{bits}_t Post_{condition['name']} : {state_bits};") - content.add(f"}} {ident}_Entry;") - content.add([f"static const {ident}_Entry", f"{ident}_Entries[] = {{"]) - entries[-1] = entries[-1].replace("},", "}") - content.append(entries) - bits = max(8, math.ceil(math.log2(len(self._entries)) / 8) * 8) - content.append( - ["};", "", f"static const uint{bits}_t", f"{ident}_Map[] = {{"]) - text = ", ".join( - str(self._entries[transitions.key][1]) - for transitions in self._map) - wrapper = textwrap.TextWrapper() - wrapper.initial_indent = " " - wrapper.subsequent_indent = " " - wrapper.width = 79 - content.append(wrapper.wrap(text)) - content.append("};") - - def get_post_entry_member(self, co_idx: int) -> str: - """ - Gets the post-condition entry member name for the post-condition index. - """ - return f"Post_{self._post_co_idx_to_co_name[co_idx]}" def _to_enum(prefix: str, conditions: List[Any]) -> _IdxToX: diff --git a/specview.py b/specview.py index d834b3e2..86dc20eb 100755 --- a/specview.py +++ b/specview.py @@ -34,7 +34,7 @@ from rtemsspec.items import EmptyItem, Item, ItemCache, ItemMapper, \ ItemGetValueContext, Link from rtemsspec.sphinxcontent import SphinxContent from rtemsspec.util import load_config -from rtemsspec.validation import Transition, TransitionMap +from rtemsspec.transitionmap import Transition, TransitionMap _CHILD_ROLES = [ "requirement-refinement", "interface-ingroup", "interface-ingroup-hidden", |