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 /rtemsspec/validation.py | |
parent | interface: Fix mapper prefix (diff) | |
download | rtems-central-cdb62c9f8cd591297bff1c3384dc56d5c752a7bd.tar.bz2 |
validation: Split file
Diffstat (limited to 'rtemsspec/validation.py')
-rw-r--r-- | rtemsspec/validation.py | 645 |
1 files changed, 5 insertions, 640 deletions
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: |