specware 1.3.4__tar.gz → 1.3.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specware
3
- Version: 1.3.4
3
+ Version: 1.3.6
4
4
  Summary: Provides utilities to specify software.
5
5
  Keywords: certification,documentation,markdown,qualification,requirements-management,traceability
6
6
  Author: The specware Authors
@@ -4,7 +4,7 @@
4
4
 
5
5
  [project]
6
6
  name = "specware"
7
- version = "1.3.4"
7
+ version = "1.3.6"
8
8
  description = "Provides utilities to specify software."
9
9
  authors = [
10
10
  {name = "The specware Authors", email = "specthings@embedded-brains.de"}
@@ -30,6 +30,7 @@ from .contentc import * # noqa: F401, F403
30
30
  from .interface import * # noqa: F401, F403
31
31
  from .interfacedoc import * # noqa: F401, F403
32
32
  from .interfacemapper import * # noqa: F401, F403
33
+ from .registerblock import * # noqa: F401, F403
33
34
  from .rtems import * # noqa: F401, F403
34
35
  from .transitionmap import * # noqa: F401, F403
35
36
  from .util import * # noqa: F401, F403
@@ -38,9 +38,9 @@ from specitems import (COL_SPAN, CommonMarkContent, Item, ItemCache,
38
38
  from specware import (exit_on_config_file_error, open_tree,
39
39
  augment_with_test_case_links, augment_with_test_links,
40
40
  gather_api_items, gather_build_files,
41
- get_register_bits_run, get_register_member_name,
42
- recursive_is_enabled, Transition, TransitionMap,
43
- validate)
41
+ get_register_bits_run, get_register_block_layout,
42
+ get_register_member_name, recursive_is_enabled,
43
+ Transition, TransitionMap, validate)
44
44
 
45
45
  _DOC_FORMAT = {
46
46
  "commonmark": CommonMarkContent,
@@ -56,8 +56,8 @@ _CHILD_ROLES = [
56
56
 
57
57
  _PARENT_ROLES = [
58
58
  "function-implementation", "interface-enumerator",
59
- "performance-runtime-limits-provider", "test-timeouts",
60
- "verification-provider"
59
+ "interface-group-member", "performance-runtime-limits-provider",
60
+ "test-timeouts", "verification-provider"
61
61
  ]
62
62
 
63
63
 
@@ -122,7 +122,8 @@ def _visit_item(item: Item, mapper: ItemMapper, level: int,
122
122
 
123
123
  def _view_interface_placment(item: Item, mapper: ItemMapper, level: int,
124
124
  validated_filter: str) -> None:
125
- for link in item.links_to_children("interface-placement"):
125
+ for link in itertools.chain(item.links_to_children("interface-placement"),
126
+ item.links_to_parents("register-block-host")):
126
127
  if _visit_item(link.item, mapper, level, link, validated_filter):
127
128
  _view_interface_placment(link.item, mapper, level + 1,
128
129
  validated_filter)
@@ -507,12 +508,12 @@ def _register_member(place: str, definition: dict[str, Any],
507
508
  print(" ".join(parts))
508
509
 
509
510
 
510
- def _register_placed(item: Item, includes: dict[str, Item],
511
+ def _register_placed(members: list[Any], includes: dict[str, Item],
511
512
  registers: dict[str,
512
513
  Any], seen: set[str]) -> tuple[int, int]:
513
514
  count = 0
514
515
  unclassified = 0
515
- for member in item["definition"]:
516
+ for member in members:
516
517
  for definition in _register_definitions(member):
517
518
  name, alias = get_register_member_name(definition)
518
519
  if name in includes:
@@ -550,13 +551,14 @@ def _register_block(item: Item) -> tuple[int, int]:
550
551
  parts.extend(link.item["title"]
551
552
  for link in item.links_to_parents("reference", _any_link))
552
553
  print(" ".join(parts))
554
+ layout = get_register_block_layout(item)
553
555
  includes = dict(
554
- (link["name"], link.item)
555
- for link in item.links_to_parents("register-block-include", _any_link))
556
+ (part.data["name"], part.data.item) for part in layout.includes)
556
557
  registers = dict(
557
- (register["name"], register) for register in item["registers"])
558
+ (part.data["name"], part.data) for part in layout.registers)
558
559
  seen: set[str] = set()
559
- placed = _register_placed(item, includes, registers, seen)
560
+ placed = _register_placed([part.data for part in layout.definition],
561
+ includes, registers, seen)
560
562
  unplaced = _register_unplaced(registers, seen)
561
563
  return placed[0] + unplaced[0], placed[1] + unplaced[1]
562
564
 
@@ -572,7 +574,12 @@ def _register_blocks(item: Item, blocks: list[Item],
572
574
  visited.add(item.uid)
573
575
  if item.get("interface-type", None) == "register-block":
574
576
  blocks.append(item)
575
- for link in item.links_to_children(is_link_enabled=_any_link):
577
+ return
578
+ for link in itertools.chain(
579
+ item.links_to_children(is_link_enabled=_any_link),
580
+ item.links_to_parents(
581
+ ["interface-group-member", "register-block-host"],
582
+ is_link_enabled=_any_link)):
576
583
  _register_blocks(link.item, blocks, visited)
577
584
 
578
585
 
@@ -589,7 +589,8 @@ def get_value_params(ctx: ItemGetValueContext) -> Any:
589
589
  def forward_declaration(item: Item) -> str:
590
590
  """ Gets the forward declare for the item. """
591
591
  target = item.parent("interface-target")
592
- return f"{target['interface-type']} {target['name']}"
592
+ kind = target["interface-type"].removeprefix("unspecified-")
593
+ return f"{kind} {target['name']}"
593
594
 
594
595
 
595
596
  def get_value_forward_declaration(ctx: ItemGetValueContext) -> Any:
@@ -48,6 +48,11 @@ from .contentc import (CContent, CInclude, DEFAULT_ITEM_MARKER,
48
48
  get_value_forward_declaration, get_value_hash,
49
49
  get_value_header_file, get_value_params,
50
50
  get_value_unspecified_type)
51
+ from .registerblock import (
52
+ RegisterBlockPart, get_register_block_group, get_interface_groups,
53
+ get_register_block_host_of_domain, get_register_block_hosts,
54
+ get_register_block_identifier, get_register_block_layout,
55
+ get_register_block_prefixes)
51
56
  from .rtems import is_export_affected
52
57
  from .util import get_register_bits_run, get_register_member_name
53
58
 
@@ -57,13 +62,19 @@ _GetLines = Callable[["_Node", Item, str, Any], GenericContent]
57
62
 
58
63
  def _get_ingroups(item: Item) -> _ItemMap:
59
64
  ingroups: _ItemMap = {}
60
- for group in item.parents("interface-ingroup"):
65
+ for group in get_interface_groups(item):
61
66
  ingroups[group.uid] = group
62
67
  return ingroups
63
68
 
64
69
 
70
+ def _get_group_identifier(item: Item) -> str:
71
+ if item.type == "interface/register-block":
72
+ return get_register_block_identifier(item)
73
+ return item["identifier"]
74
+
75
+
65
76
  def _get_group_identifiers(groups: _ItemMap) -> list[str]:
66
- return [item["identifier"] for item in groups.values()]
77
+ return [_get_group_identifier(item) for item in groups.values()]
67
78
 
68
79
 
69
80
  def _get_cite(ctx: ItemGetValueContext) -> str:
@@ -269,6 +280,7 @@ class _RegisterMemberContext(NamedTuple):
269
280
  regs: dict[str, Any]
270
281
  reg_counts: dict[str, int]
271
282
  reg_indices: dict[str, int]
283
+ parts: list[RegisterBlockPart]
272
284
 
273
285
 
274
286
  def _add_register_padding(content: CContent, new_offset: int, old_offset: int,
@@ -409,11 +421,10 @@ def _get_register_bits_shift(start: int, stride: int, first: int) -> str:
409
421
  return f"( {start} {'+' if stride > 0 else '-'} {term} )"
410
422
 
411
423
 
412
- def _get_register_bits_prefix(item: Item, reg_name: str) -> str:
424
+ def _get_register_bits_prefix(item: Item, host: Optional[Link],
425
+ reg_name: str) -> str:
413
426
  """ Get the define prefix of the bit fields of a register. """
414
- prefix = item["register-prefix"]
415
- if prefix is None:
416
- prefix = item["name"]
427
+ prefix, _ = get_register_block_prefixes(item, host)
417
428
  prefix = f"{prefix}_{reg_name}_" if prefix else f"{reg_name}_"
418
429
  return prefix.upper()
419
430
 
@@ -507,9 +518,13 @@ class _Node:
507
518
  """ Nodes of a header file. """
508
519
 
509
520
  # pylint: disable=too-many-instance-attributes
510
- def __init__(self, header_file: "_HeaderFile", item: Item):
521
+ def __init__(self,
522
+ header_file: "_HeaderFile",
523
+ item: Item,
524
+ host: Optional[Link] = None):
511
525
  self.header_file = header_file
512
526
  self.item = item
527
+ self.host = host
513
528
  self.ingroups = _get_ingroups(item)
514
529
  self.dependents: set[_Node] = set()
515
530
  self.depends_on: set[_Node] = set()
@@ -580,22 +595,17 @@ class _Node:
580
595
  def generate_enum(self) -> None:
581
596
  """ Generate an enum. """
582
597
  with self._enum_struct_or_union():
583
- enumerators: list[CContent] = []
584
- for parent in self.item.parents("interface-enumerator"):
585
- enumerator = self._get_description(parent, {})
586
- enumerator.append(
598
+ parents = list(self.item.parents("interface-enumerator"))
599
+ for index, parent in enumerate(parents):
600
+ if index > 0:
601
+ self.content.append("")
602
+ self.content.append(self._get_description(parent, {}))
603
+ get_lines = _Node._get_enumerator_definition
604
+ if index < len(parents) - 1:
605
+ get_lines = _Node._get_enumerator_definition_comma
606
+ self.content.append(
587
607
  _add_definition(self, parent, "definition",
588
- parent["definition"],
589
- _Node._get_enumerator_definition))
590
- enumerators.append(enumerator)
591
- for enumerator in enumerators[0:-1]:
592
- enumerator.last += ","
593
- enumerator.append("")
594
- self.content.append(enumerator)
595
- try:
596
- self.content.append(enumerators[-1])
597
- except IndexError:
598
- pass
608
+ parent["definition"], get_lines))
599
609
 
600
610
  def generate_define(self) -> None:
601
611
  """ Generate a define. """
@@ -623,10 +633,16 @@ class _Node:
623
633
  """ Generate a macro. """
624
634
  self._add_generic_definition(_Node._get_macro_definition)
625
635
 
636
+ def _new_register_member_context(self) -> _RegisterMemberContext:
637
+ layout = get_register_block_layout(self.item)
638
+ return _RegisterMemberContext({}, {}, collections.defaultdict(int),
639
+ collections.defaultdict(int),
640
+ layout.definition)
641
+
626
642
  def _add_register_bits(self, group: str) -> _RegisterMemberContext:
627
- ctx = _RegisterMemberContext({}, {}, collections.defaultdict(int),
628
- collections.defaultdict(int))
629
- for index, register in enumerate(self.item["registers"]):
643
+ ctx = self._new_register_member_context()
644
+ for part in get_register_block_layout(self.item).registers:
645
+ register = part.data
630
646
  name = register["name"]
631
647
  group_ident = group + to_camel_case(name)
632
648
  width = register["width"]
@@ -636,17 +652,17 @@ class _Node:
636
652
  "type": f"uint{width}_t",
637
653
  "group": group_ident
638
654
  }
639
- brief = self.substitute_text(register["brief"])
655
+ brief = self.substitute_text(register["brief"], part.item)
640
656
  with self.content.defgroup_block(group_ident, f"{brief} ({name})"):
641
657
  self.content.add_brief_description(
642
658
  "This group contains register bit definitions.")
643
- self.content.wrap(self.substitute_text(
644
- register["description"]))
659
+ self.content.wrap(
660
+ self.substitute_text(register["description"], part.item))
645
661
  self.content.add("@{")
646
662
  for index_2, bits in enumerate(register["bits"]):
647
663
  self.content.add(
648
664
  _add_definition(
649
- self, self.item, f"registers[{index}]/bits[{index_2}]",
665
+ self, part.item, f"{part.prefix}/bits[{index_2}]",
650
666
  bits,
651
667
  functools.partial(_Node._get_register_bits_definition,
652
668
  reg_name=name,
@@ -656,16 +672,17 @@ class _Node:
656
672
 
657
673
  def _add_register_block_includes(self,
658
674
  ctx: _RegisterMemberContext) -> None:
659
- for link in self.item.links_to_parents("register-block-include"):
675
+ for part in get_register_block_layout(self.item).includes:
676
+ link = part.data
660
677
  name = link["name"]
661
678
  ctx.regs[name] = {}
662
679
  ctx.regs[name]["size"] = link.item["register-block-size"]
663
680
  ctx.regs[name]["type"] = link.item["name"]
664
- ctx.regs[name]["group"] = link.item["identifier"]
681
+ ctx.regs[name]["group"] = get_register_block_identifier(link.item)
665
682
 
666
683
  def _get_register_member_info(self, ctx: _RegisterMemberContext) -> None:
667
684
  offset = -1
668
- for index, member in enumerate(self.item["definition"]):
685
+ for index, member in enumerate(part.data for part in ctx.parts):
669
686
  assert member["offset"] > offset
670
687
  offset = member["offset"]
671
688
  default = [member["default"]] if member["default"] else []
@@ -689,13 +706,13 @@ class _Node:
689
706
  self.substitute_text(self.item["brief"]))
690
707
  self.content.wrap(self.substitute_text(self.item["description"]))
691
708
  self.content.add("@{")
692
- for index, member in enumerate(self.item["definition"]):
709
+ for part in ctx.parts:
693
710
  self.content.add(
694
711
  _add_definition(
695
- self, self.item, f"definition[{index}]", member,
712
+ self, part.item, part.prefix, part.data,
696
713
  functools.partial(_Node._get_register_define_definition,
697
714
  ctx=ctx,
698
- offset=member["offset"])))
715
+ offset=part.data["offset"])))
699
716
  self.content.add_close_group()
700
717
 
701
718
  def _add_register_struct(self, ctx: _RegisterMemberContext,
@@ -708,13 +725,13 @@ class _Node:
708
725
  default_padding = min(*ctx.sizes.values(), 8)
709
726
  offset = 0
710
727
  with self.content.indent():
711
- for index, member in enumerate(self.item["definition"]):
712
- member_offset = member["offset"]
728
+ for index, part in enumerate(ctx.parts):
729
+ member_offset = part.data["offset"]
713
730
  _add_register_padding(self.content, member_offset, offset,
714
731
  default_padding)
715
732
  self.content.add(
716
733
  _add_definition(
717
- self, self.item, f"definition[{index}]", member,
734
+ self, part.item, part.prefix, part.data,
718
735
  functools.partial(
719
736
  _Node._get_register_member_definition, ctx=ctx)))
720
737
  offset = member_offset + ctx.sizes[index]
@@ -729,14 +746,18 @@ class _Node:
729
746
  else:
730
747
  self._add_register_struct(ctx, size)
731
748
 
749
+ def _add_register_block_dependencies(self) -> None:
750
+ for part in get_register_block_layout(self.item).includes:
751
+ parent = part.data.item
752
+ self.header_file.add_includes(parent)
753
+ self.header_file.add_dependency(self, parent)
754
+
732
755
  def generate_register_block(self) -> None:
733
756
  """ Generate a register block. """
734
757
  self.header_file.add_includes(self.item.map("/c/if/uint32_t"))
735
- for parent in self.item.parents("register-block-include"):
736
- self.header_file.add_includes(parent)
737
- self.header_file.add_dependency(self, parent)
738
- group = self.item["identifier"]
739
- name = self.item["register-block-group"]
758
+ self._add_register_block_dependencies()
759
+ group = get_register_block_identifier(self.item, self.host)
760
+ name = get_register_block_group(self.item, self.host)
740
761
  with self.content.defgroup_block(group, name):
741
762
  self.content.add_ingroup(_get_group_identifiers(self.ingroups))
742
763
  self.content.add_brief_description(
@@ -811,6 +832,11 @@ class _Node:
811
832
  return f"{name} = {self.substitute_code(definition, prefix)}"
812
833
  return f"{name}"
813
834
 
835
+ def _get_enumerator_definition_comma(self, item: Item, prefix: str,
836
+ definition: Any) -> GenericContent:
837
+ # The comma ends the enumerator line inside each branch of a variant.
838
+ return f"{self._get_enumerator_definition(item, prefix, definition)},"
839
+
814
840
  def _get_define_definition(self, item: Item, prefix: str,
815
841
  definition: Any) -> GenericContent:
816
842
  name = item["name"]
@@ -865,7 +891,7 @@ class _Node:
865
891
  definition: Any, reg_name: str,
866
892
  reg_width: int) -> GenericContent:
867
893
  lines = [] # list[str]
868
- prefix = _get_register_bits_prefix(self.item, reg_name)
894
+ prefix = _get_register_bits_prefix(self.item, self.host, reg_name)
869
895
  for index, bit in enumerate(definition):
870
896
  if index != 0:
871
897
  lines.append("")
@@ -875,7 +901,13 @@ class _Node:
875
901
  bit))
876
902
  return lines
877
903
 
878
- def _get_register_define_definition(self, item: Item, _prefix: str,
904
+ def _get_offset_prefix(self) -> str:
905
+ prefix = self.item["name"]
906
+ if self.host is not None:
907
+ prefix = self.host.data.get("prefix", None) or prefix
908
+ return prefix.upper()
909
+
910
+ def _get_register_define_definition(self, _item: Item, _prefix: str,
879
911
  definition: Any,
880
912
  ctx: _RegisterMemberContext,
881
913
  offset: int) -> GenericContent:
@@ -886,7 +918,7 @@ class _Node:
886
918
  with content.doxygen_block():
887
919
  content.add(f"@brief See @ref {ctx.regs[name]['group']}.")
888
920
  content.append(
889
- f"#define {item['name'].upper()}_{alias.upper()} {offset:#x}")
921
+ f"#define {self._get_offset_prefix()}_{alias.upper()} {offset:#x}")
890
922
  return content
891
923
 
892
924
  def _get_register_member_definition(
@@ -1004,7 +1036,7 @@ class _ZephyrNode(_Node):
1004
1036
  definition: Any, reg_name: str,
1005
1037
  reg_width: int) -> GenericContent:
1006
1038
  lines = [] # list[str]
1007
- prefix = _get_register_bits_prefix(self.item, reg_name)
1039
+ prefix = _get_register_bits_prefix(self.item, self.host, reg_name)
1008
1040
  for bit in sorted(definition, key=lambda x: x["start"]):
1009
1041
  lines.extend(
1010
1042
  _get_zephyr_register_bits_lines(
@@ -1013,9 +1045,9 @@ class _ZephyrNode(_Node):
1013
1045
  return lines
1014
1046
 
1015
1047
  def _add_register_bits(self, group: str) -> _RegisterMemberContext:
1016
- ctx = _RegisterMemberContext({}, {}, collections.defaultdict(int),
1017
- collections.defaultdict(int))
1018
- for index, register in enumerate(self.item["registers"]):
1048
+ ctx = self._new_register_member_context()
1049
+ for part in get_register_block_layout(self.item).registers:
1050
+ register = part.data
1019
1051
  name = register["name"]
1020
1052
  width = register["width"]
1021
1053
  assert width in [8, 16, 32, 64]
@@ -1024,7 +1056,7 @@ class _ZephyrNode(_Node):
1024
1056
  for index_2, bits in enumerate(register["bits"]):
1025
1057
  self.content.append(
1026
1058
  _add_definition(
1027
- self, self.item, f"registers[{index}]/bits[{index_2}]",
1059
+ self, part.item, f"{part.prefix}/bits[{index_2}]",
1028
1060
  bits,
1029
1061
  functools.partial(
1030
1062
  _ZephyrNode._get_register_bits_definition,
@@ -1032,34 +1064,36 @@ class _ZephyrNode(_Node):
1032
1064
  reg_width=width)))
1033
1065
  return ctx
1034
1066
 
1035
- def _get_register_define_definition(self, item: Item, _prefix: str,
1067
+ def _get_offset_prefix(self) -> str:
1068
+ _, prefix = get_register_block_prefixes(self.item, self.host)
1069
+ return prefix.upper()
1070
+
1071
+ def _get_register_define_definition(self, _item: Item, _prefix: str,
1036
1072
  definition: Any,
1037
1073
  ctx: _RegisterMemberContext,
1038
1074
  offset: int) -> GenericContent:
1039
1075
  name, alias = get_register_member_name(definition)
1040
- prefix = item.get("offset-prefix", None)
1041
- if prefix is None:
1042
- prefix = item["name"]
1043
- define = f"#define {prefix.upper()}_{alias.upper()}"
1076
+ define = f"#define {self._get_offset_prefix()}_{alias.upper()}"
1044
1077
  count = definition["count"]
1045
1078
  if count == 1:
1046
1079
  return f"{define} {offset:#x}U"
1047
1080
  return f"{define}(i) ({offset:#x}U + {ctx.regs[name]['size']}U * (i))"
1048
1081
 
1049
1082
  def _add_register_defines(self, ctx: _RegisterMemberContext) -> None:
1050
- for index, member in enumerate(self.item["definition"]):
1083
+ for part in ctx.parts:
1051
1084
  self.content.append(
1052
1085
  _add_definition(
1053
- self, self.item, f"definition[{index}]", member,
1086
+ self, part.item, part.prefix, part.data,
1054
1087
  functools.partial(
1055
1088
  _ZephyrNode._get_register_define_definition,
1056
1089
  ctx=ctx,
1057
- offset=member["offset"])))
1090
+ offset=part.data["offset"])))
1058
1091
 
1059
1092
  def _add_register_block_includes(self,
1060
1093
  ctx: _RegisterMemberContext) -> None:
1061
1094
  super()._add_register_block_includes(ctx)
1062
- for link in self.item.links_to_parents("register-block-include"):
1095
+ for part in get_register_block_layout(self.item).includes:
1096
+ link = part.data
1063
1097
  # Register block structures are not typedefed in this style
1064
1098
  ctx.regs[link["name"]]["type"] = f"struct {link.item['name']}"
1065
1099
 
@@ -1089,13 +1123,13 @@ class _ZephyrNode(_Node):
1089
1123
  default_padding = min(*ctx.sizes.values(), 8)
1090
1124
  offset = 0
1091
1125
  with self.content.indent():
1092
- for index, member in enumerate(self.item["definition"]):
1093
- member_offset = member["offset"]
1126
+ for index, part in enumerate(ctx.parts):
1127
+ member_offset = part.data["offset"]
1094
1128
  self._append_register_padding(member_offset, offset,
1095
1129
  default_padding)
1096
1130
  self.content.append(
1097
1131
  _add_definition(
1098
- self, self.item, f"definition[{index}]", member,
1132
+ self, part.item, part.prefix, part.data,
1099
1133
  functools.partial(
1100
1134
  _ZephyrNode._get_register_member_definition,
1101
1135
  ctx=ctx)))
@@ -1106,19 +1140,17 @@ class _ZephyrNode(_Node):
1106
1140
 
1107
1141
  def generate_register_block(self) -> None:
1108
1142
  domain, size = _get_register_domain_and_size(self.item)
1109
- self.header_file.add_includes(self.item.map("/zephyr/if/genmask"))
1110
- for parent in self.item.parents("register-block-include"):
1111
- self.header_file.add_includes(parent)
1112
- self.header_file.add_dependency(self, parent)
1113
- group = self.item["identifier"]
1114
- name = self.item["register-block-group"]
1115
- ctx = self._add_register_bits(group)
1143
+ self.header_file.add_includes(
1144
+ self.item.map(self.header_file.register_bit_macros))
1145
+ self._add_register_block_dependencies()
1146
+ ctx = self._add_register_bits("")
1116
1147
  self._add_register_block_includes(ctx)
1117
1148
  self._get_register_member_info(ctx)
1118
1149
  if domain == "memory":
1119
1150
  self.header_file.add_includes(self.item.map("/c/if/uint32_t"))
1120
1151
  self._add_register_struct(ctx, size)
1121
1152
  else:
1153
+ name = get_register_block_group(self.item, self.host)
1122
1154
  self.content.add(f"/* {name} address offsets */")
1123
1155
  self._add_register_defines(ctx)
1124
1156
 
@@ -1173,6 +1205,9 @@ def _merge_enabled_by(options: dict[str, Item], link: Link) -> Any:
1173
1205
  return {"and": [enabled_by, enabled_by_2]}
1174
1206
 
1175
1207
 
1208
+ DEFAULT_REGISTER_BIT_MACROS = "/zephyr/kernel/sys-util/if/genmask"
1209
+
1210
+
1176
1211
  class _HeaderFile:
1177
1212
  """ A header file. """
1178
1213
 
@@ -1184,7 +1219,8 @@ class _HeaderFile:
1184
1219
  context: ContentContext,
1185
1220
  option_expressions: OptionExpressions,
1186
1221
  formatter: Optional[ClangFormatter] = None,
1187
- item_marker: str = DEFAULT_ITEM_MARKER):
1222
+ item_marker: str = DEFAULT_ITEM_MARKER,
1223
+ register_bit_macros: str = DEFAULT_REGISTER_BIT_MACROS):
1188
1224
  # pylint: disable=too-many-arguments
1189
1225
  # pylint: disable=too-many-positional-arguments
1190
1226
  self._item = item
@@ -1197,6 +1233,7 @@ class _HeaderFile:
1197
1233
  self.options = options
1198
1234
  self.enabled = enabled
1199
1235
  self.item_marker = item_marker
1236
+ self.register_bit_macros = register_bit_macros
1200
1237
  self._option_expressions = option_expressions
1201
1238
 
1202
1239
  def option_expression(self, option: str) -> str:
@@ -1204,18 +1241,42 @@ class _HeaderFile:
1204
1241
  return self._option_expressions.get_expression(self._item, option)
1205
1242
 
1206
1243
  def add_includes(self, item: Item) -> None:
1207
- """ Add the includes of the item to the header file includes. """
1244
+ """
1245
+ Add the includes of the item to the header file includes.
1246
+
1247
+ A register block which header files host adds the host of the domain
1248
+ of this header file.
1249
+ """
1208
1250
  for parent in item.parents("interface-placement"):
1209
1251
  if parent.type in [
1210
1252
  "interface/header-file",
1211
1253
  "interface/unspecified-header-file"
1212
1254
  ]:
1213
1255
  self._includes.append(parent)
1256
+ if get_register_block_hosts(item):
1257
+ self._includes.append(
1258
+ get_register_block_host_of_domain(
1259
+ item, self._item.parent("interface-placement")).item)
1214
1260
 
1215
1261
  def add_node(self, item: Item) -> None:
1216
1262
  """ Add a node for the item. """
1217
1263
  self._nodes[item.uid] = _Node(self, item)
1218
1264
 
1265
+ def add_hosted_node(self, link: Link) -> None:
1266
+ """ Add a node for the register block of the host link. """
1267
+ item = link.item
1268
+ if item.type != "interface/register-block":
1269
+ raise ValueError(f"header file '{self._item.uid}' hosts "
1270
+ f"'{item.uid}' which is no register block")
1271
+ if any(True for _ in item.parents("interface-placement")):
1272
+ raise ValueError(f"register block '{item.uid}' has an interface "
1273
+ "placement and header file "
1274
+ f"'{self._item.uid}' hosts it")
1275
+ get_register_block_host_of_domain(
1276
+ item, self._item.parent("interface-placement"))
1277
+ node_type = _ZephyrNode if link["style"] == "zephyr" else _Node
1278
+ self._nodes[item.uid] = node_type(self, item, link)
1279
+
1219
1280
  def add_dependency(self, node: _Node, item: Item) -> None:
1220
1281
  """
1221
1282
  Add a dependency from a node to another node identified by an item if
@@ -1231,6 +1292,9 @@ class _HeaderFile:
1231
1292
  for child in self._item.children("interface-placement"):
1232
1293
  self.add_node(child)
1233
1294
  self.content.register_license_and_copyrights_of_item(child)
1295
+ for link in self._item.links_to_parents("register-block-host"):
1296
+ self.add_hosted_node(link)
1297
+ self.content.register_license_and_copyrights_of_item(link.item)
1234
1298
  for node in self._nodes.values():
1235
1299
  node.generate()
1236
1300
 
@@ -1362,7 +1426,8 @@ def _generate_header_file(item: Item, config: dict, options: dict[str, Item],
1362
1426
  header_file = _HEADER_FILE[config.get("style", "default")](
1363
1427
  item, options, config["enabled"], context,
1364
1428
  OptionExpressions(config["option-expressions"]), formatter,
1365
- config.get("item-marker", DEFAULT_ITEM_MARKER))
1429
+ config.get("item-marker", DEFAULT_ITEM_MARKER),
1430
+ config.get("register-bit-macros", DEFAULT_REGISTER_BIT_MACROS))
1366
1431
  header_file.generate_nodes()
1367
1432
  header_file.finalize()
1368
1433
  header_file.write(domain_path, file_path)
@@ -0,0 +1,300 @@
1
+ # SPDX-License-Identifier: BSD-2-Clause
2
+ """ Provides the placement and the layout of register blocks. """
3
+
4
+ # Copyright (C) 2026 embedded brains GmbH & Co. KG
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions
8
+ # are met:
9
+ # 1. Redistributions of source code must retain the above copyright
10
+ # notice, this list of conditions and the following disclaimer.
11
+ # 2. Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
19
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25
+ # POSSIBILITY OF SUCH DAMAGE.
26
+
27
+ from typing import Any, NamedTuple, Optional
28
+
29
+ from specitems import Item, Link
30
+
31
+ __all__ = [
32
+ "RegisterBlockLayout", "RegisterBlockPart", "get_group_members",
33
+ "get_interface_container", "get_interface_groups", "get_interface_members",
34
+ "get_register_block_base", "get_register_block_group",
35
+ "get_register_block_host_of_domain", "get_register_block_hosts",
36
+ "get_register_block_identifier", "get_register_block_layout",
37
+ "get_register_block_prefixes"
38
+ ]
39
+
40
+
41
+ def get_register_block_hosts(item: Item) -> list[Link]:
42
+ """
43
+ Get the links of the header files which host the register block.
44
+
45
+ Each link is a link from the header file to the register block, so
46
+ ``link.item`` is the header file. The links are sorted by the UID of the
47
+ header file.
48
+ """
49
+ return sorted(item.links_to_children("register-block-host"),
50
+ key=lambda link: link.item.uid)
51
+
52
+
53
+ def get_register_block_host_of_domain(item: Item, domain: Item) -> Link:
54
+ """
55
+ Get the link of the header file of the domain which hosts the register
56
+ block.
57
+
58
+ Raises:
59
+ ValueError: No header file or more than one header file of the domain
60
+ hosts the register block.
61
+ """
62
+ hosts = [
63
+ link for link in get_register_block_hosts(item)
64
+ if link.item.parent("interface-placement") == domain
65
+ ]
66
+ if not hosts:
67
+ raise ValueError(f"no header file of domain '{domain.uid}' hosts "
68
+ f"register block '{item.uid}'")
69
+ if len(hosts) > 1:
70
+ raise ValueError(f"more than one header file of domain "
71
+ f"'{domain.uid}' hosts register block '{item.uid}': "
72
+ f"{', '.join(link.item.uid for link in hosts)}")
73
+ return hosts[0]
74
+
75
+
76
+ def get_interface_members(item: Item) -> list[Item]:
77
+ """
78
+ Get the interfaces which the interface container places.
79
+
80
+ These are the children of the ``interface-placement`` links and the
81
+ register blocks of the ``register-block-host`` links of the container.
82
+ """
83
+ return list(item.children("interface-placement")) + list(
84
+ item.parents("register-block-host"))
85
+
86
+
87
+ def get_interface_groups(item: Item) -> list[Item]:
88
+ """
89
+ Get the interface groups of the interface.
90
+
91
+ These are the parents of the ``interface-ingroup`` links of the interface
92
+ and the groups which link to the interface through the
93
+ ``interface-group-member`` role.
94
+
95
+ Raises:
96
+ ValueError: A group links to an interface which is no register block.
97
+ """
98
+ members = list(item.children("interface-group-member"))
99
+ if members and item.type != "interface/register-block":
100
+ raise ValueError(f"group '{members[0].uid}' has the member "
101
+ f"'{item.uid}' which is no register block")
102
+ groups = dict(
103
+ (group.uid, group) for group in item.parents("interface-ingroup"))
104
+ groups.update((group.uid, group) for group in members)
105
+ return list(groups.values())
106
+
107
+
108
+ def get_group_members(item: Item) -> list[Item]:
109
+ """
110
+ Get the members of the interface group.
111
+
112
+ These are the children of the ``interface-ingroup`` links of the group
113
+ and the parents of its ``interface-group-member`` links.
114
+ """
115
+ return list(item.children("interface-ingroup")) + list(
116
+ item.parents("interface-group-member"))
117
+
118
+
119
+ def get_interface_container(item: Item) -> Item:
120
+ """
121
+ Get the interface container of the interface.
122
+
123
+ The container is the parent of the ``interface-placement`` link. A
124
+ register block without such a link resides in the first header file which
125
+ hosts it.
126
+
127
+ Raises:
128
+ IndexError: The interface resides in no container.
129
+ """
130
+ try:
131
+ return item.parent("interface-placement")
132
+ except IndexError:
133
+ hosts = get_register_block_hosts(item)
134
+ if not hosts:
135
+ raise
136
+ return hosts[0].item
137
+
138
+
139
+ def _get_block_value(item: Item, host: Optional[Link], key: str) -> Any:
140
+ if host is not None:
141
+ value = host.data.get(key, None)
142
+ if value is not None:
143
+ return value
144
+ else:
145
+ values = set(link.data[key] for link in get_register_block_hosts(item)
146
+ if link.data.get(key, None) is not None)
147
+ if len(values) > 1:
148
+ raise ValueError(f"the header files which host register block "
149
+ f"'{item.uid}' state different values for "
150
+ f"'{key}': {', '.join(sorted(values))}")
151
+ if values:
152
+ return values.pop()
153
+ value = item.get(key, None)
154
+ if value is None:
155
+ raise ValueError(f"register block '{item.uid}' has no '{key}' and no "
156
+ "host link states one")
157
+ return value
158
+
159
+
160
+ def get_register_block_identifier(item: Item,
161
+ host: Optional[Link] = None) -> str:
162
+ """
163
+ Get the interface group identifier of the register block.
164
+
165
+ The value of the host link takes precedence over the value of the block.
166
+ Without a host link, the value of the host links of the block takes
167
+ precedence.
168
+ """
169
+ return _get_block_value(item, host, "identifier")
170
+
171
+
172
+ def get_register_block_group(item: Item, host: Optional[Link] = None) -> str:
173
+ """
174
+ Get the interface group name of the register block.
175
+
176
+ The value of the host link takes precedence over the value of the block.
177
+ Without a host link, the value of the host links of the block takes
178
+ precedence.
179
+ """
180
+ return _get_block_value(item, host, "register-block-group")
181
+
182
+
183
+ def get_register_block_prefixes(item: Item,
184
+ host: Optional[Link] = None
185
+ ) -> tuple[str, str]:
186
+ """
187
+ Get the register prefix and the offset prefix of the register block.
188
+
189
+ The prefix of the host link replaces both prefixes of the block. A
190
+ missing prefix of the block is the name of the block.
191
+ """
192
+ if host is not None:
193
+ prefix = host.data.get("prefix", None)
194
+ if prefix is not None:
195
+ return prefix, prefix
196
+ register_prefix = item["register-prefix"]
197
+ if register_prefix is None:
198
+ register_prefix = item["name"]
199
+ offset_prefix = item.get("offset-prefix", None)
200
+ if offset_prefix is None:
201
+ offset_prefix = item["name"]
202
+ return register_prefix, offset_prefix
203
+
204
+
205
+ def get_register_block_base(item: Item) -> Optional[Item]:
206
+ """
207
+ Get the base of the register block.
208
+
209
+ Raises:
210
+ ValueError: The block has more than one base.
211
+ """
212
+ bases = list(item.parents("register-block-base"))
213
+ if len(bases) > 1:
214
+ raise ValueError(f"register block '{item.uid}' has more than one "
215
+ "base")
216
+ return bases[0] if bases else None
217
+
218
+
219
+ class RegisterBlockPart(NamedTuple):
220
+ """
221
+ Is a register, a member or an include link of a register block layout.
222
+ """
223
+ item: Item
224
+ prefix: str
225
+ data: Any
226
+
227
+
228
+ class RegisterBlockLayout(NamedTuple):
229
+ """
230
+ Is the layout of a register block which includes the parts of its bases.
231
+ """
232
+ registers: list[RegisterBlockPart]
233
+ definition: list[RegisterBlockPart]
234
+ includes: list[RegisterBlockPart]
235
+
236
+
237
+ def _own_layout(item: Item) -> RegisterBlockLayout:
238
+ return RegisterBlockLayout([
239
+ RegisterBlockPart(item, f"registers[{index}]", register)
240
+ for index, register in enumerate(item["registers"])
241
+ ], [
242
+ RegisterBlockPart(item, f"definition[{index}]", member)
243
+ for index, member in enumerate(item["definition"])
244
+ ], [
245
+ RegisterBlockPart(item, "", link)
246
+ for link in item.links_to_parents("register-block-include")
247
+ ])
248
+
249
+
250
+ def _derive_layout(item: Item, base: RegisterBlockLayout,
251
+ own: RegisterBlockLayout) -> RegisterBlockLayout:
252
+ include_names = set(part.data["name"] for part in base.includes)
253
+ for part in own.includes:
254
+ name = part.data["name"]
255
+ if name in include_names or any(part_2.data["name"] == name
256
+ for part_2 in base.registers):
257
+ raise ValueError(f"register block '{item.uid}' includes a "
258
+ f"register block named '{name}' which its base "
259
+ "defines")
260
+ registers = list(base.registers)
261
+ for part in own.registers:
262
+ name = part.data["name"]
263
+ if name in include_names:
264
+ raise ValueError(f"register '{name}' of register block "
265
+ f"'{item.uid}' has the name of a register block "
266
+ "which its base includes")
267
+ for index, part_2 in enumerate(registers):
268
+ if part_2.data["name"] == name:
269
+ registers[index] = part
270
+ break
271
+ else:
272
+ registers.append(part)
273
+ members = dict((part.data["offset"], part) for part in base.definition)
274
+ members.update((part.data["offset"], part) for part in own.definition)
275
+ return RegisterBlockLayout(registers,
276
+ [members[offset] for offset in sorted(members)],
277
+ base.includes + own.includes)
278
+
279
+
280
+ def _get_layout(item: Item, visited: set[str]) -> RegisterBlockLayout:
281
+ if item.uid in visited:
282
+ raise ValueError(f"register block '{item.uid}' is its own base")
283
+ visited.add(item.uid)
284
+ own = _own_layout(item)
285
+ base = get_register_block_base(item)
286
+ if base is None:
287
+ return own
288
+ return _derive_layout(item, _get_layout(base, visited), own)
289
+
290
+
291
+ def get_register_block_layout(item: Item) -> RegisterBlockLayout:
292
+ """
293
+ Get the layout of the register block.
294
+
295
+ A derivative starts with the layout of its base. A register of the name
296
+ of a base register replaces the base register. A member at the offset of
297
+ a base member replaces the base member. The derivative adds its other
298
+ registers, members and includes.
299
+ """
300
+ return _get_layout(item, set())
@@ -31,6 +31,8 @@ from typing import Any, Callable, Iterable
31
31
  from specitems import (EnabledSet, Item, ItemCache, create_unique_link,
32
32
  link_is_enabled, to_iterable)
33
33
 
34
+ from .registerblock import get_interface_members
35
+
34
36
  _NOT_PRE_QUALIFIED = frozenset((
35
37
  "/acfg/constraint/option-not-pre-qualified",
36
38
  "/constraint/constant-not-pre-qualified",
@@ -49,6 +51,10 @@ _ENABLEMENT_ROLES = ("interface-function", "interface-ingroup",
49
51
  "interface-ingroup-hidden", "requirement-refinement",
50
52
  "validation")
51
53
 
54
+ # An item is enabled through the items which link to it by these roles.
55
+ _ENABLEMENT_CHILD_ROLES = ("interface-group-member", "register-block-base",
56
+ "register-block-host", "register-block-include")
57
+
52
58
 
53
59
  def recursive_is_enabled(enabled_set: EnabledSet, item: Item) -> bool:
54
60
  """
@@ -58,8 +64,10 @@ def recursive_is_enabled(enabled_set: EnabledSet, item: Item) -> bool:
58
64
  if not item.is_enabled(enabled_set):
59
65
  return False
60
66
  result = True
61
- for parent in item.parents(_ENABLEMENT_ROLES,
62
- is_link_enabled=link_is_enabled):
67
+ for parent in itertools.chain(
68
+ item.parents(_ENABLEMENT_ROLES, is_link_enabled=link_is_enabled),
69
+ item.children(_ENABLEMENT_CHILD_ROLES,
70
+ is_link_enabled=link_is_enabled)):
63
71
  if recursive_is_enabled(enabled_set, parent):
64
72
  return True
65
73
  result = False
@@ -123,21 +131,25 @@ _CHILD_ROLES = ("requirement-refinement", "interface-ingroup",
123
131
  _PARENT_ROLES = ("function-implementation", "interface-enumerator",
124
132
  "performance-runtime-limits")
125
133
 
126
- # WARNING: This role set works only with _visit_tree() which stops the
127
- # recursion once it sees an item the second time. It is there to support older
128
- # versions of the RTEMS specification where not every interface was assigned to
129
- # an interface group or other group membership roles were used.
130
- _BACKWARD_COMPATIBLE_CHILD_ROLES = _CHILD_ROLES + ("appl-config-group-member",
131
- "interface-placement")
134
+ # Through this role, a group is the child of each of its members.
135
+ _VALIDATION_PARENT_ROLES = _PARENT_ROLES + ("interface-group-member", )
136
+
137
+ # WARNING: These role sets work only with _visit_tree() which stops the
138
+ # recursion once it sees an item the second time. The appl-config-group-member
139
+ # role supports older versions of the RTEMS specification which used this group
140
+ # membership role.
141
+ _VISIT_CHILD_ROLES = _CHILD_ROLES + ("appl-config-group-member",
142
+ "interface-placement")
143
+
144
+ _VISIT_PARENT_ROLES = _VALIDATION_PARENT_ROLES + ("register-block-host", )
132
145
 
133
146
 
134
147
  def _visit_tree(item: Item, related_items: set[Item]) -> None:
135
148
  if item in related_items:
136
149
  return
137
150
  related_items.add(item)
138
- for item_2 in itertools.chain(
139
- item.children(_BACKWARD_COMPATIBLE_CHILD_ROLES),
140
- item.parents(_PARENT_ROLES)):
151
+ for item_2 in itertools.chain(item.children(_VISIT_CHILD_ROLES),
152
+ item.parents(_VISIT_PARENT_ROLES)):
141
153
  _visit_tree(item_2, related_items)
142
154
 
143
155
 
@@ -158,14 +170,15 @@ _EXPORT_CHILD_ROLES = ("interface-function", "interface-placement",
158
170
  "requirement-disposition", "requirement-refinement",
159
171
  "test-case", "validation")
160
172
 
161
- _EXPORT_PARENT_ROLES = _PARENT_ROLES + ("constraint", "errno",
162
- "register-block-include")
173
+ _EXPORT_PARENT_ROLES = _PARENT_ROLES + (
174
+ "constraint", "errno", "register-block-base", "register-block-host",
175
+ "register-block-include")
163
176
 
164
177
  # Items reached through the shallow roles contribute to the generated content
165
178
  # of the visiting item, however, the items related to them do not. Expanding
166
179
  # them would relate a header file to every item of each included header file
167
180
  # and to every member of each interface group.
168
- _EXPORT_SHALLOW_CHILD_ROLES = ("placement-order", )
181
+ _EXPORT_SHALLOW_CHILD_ROLES = ("interface-group-member", "placement-order")
169
182
 
170
183
  _EXPORT_SHALLOW_PARENT_ROLES = ("interface-include", "interface-ingroup",
171
184
  "interface-ingroup-hidden", "interface-target")
@@ -420,8 +433,9 @@ def _validate_tree(item: Item, validator: Callable[[Item, bool], bool],
420
433
  validation_dependencies: list[tuple[str, str]] = []
421
434
  for index, link in enumerate(
422
435
  sorted(
423
- itertools.chain(item.links_to_children(_CHILD_ROLES),
424
- item.links_to_parents(_PARENT_ROLES)))):
436
+ itertools.chain(
437
+ item.links_to_children(_CHILD_ROLES),
438
+ item.links_to_parents(_VALIDATION_PARENT_ROLES)))):
425
439
  item_2 = link.item
426
440
  validated = _validate_tree(item_2, validator, order[:-1] +
427
441
  (order[-1] + index + 1, 0),
@@ -430,7 +444,8 @@ def _validate_tree(item: Item, validator: Callable[[Item, bool], bool],
430
444
  role = _VALIDATION_METHOD[item_2.type]
431
445
  elif link.role == "requirement-refinement":
432
446
  role = "refinement"
433
- elif link.role.startswith("interface-ingroup"):
447
+ elif link.role.startswith("interface-ingroup") or (
448
+ link.role == "interface-group-member"):
434
449
  role = "group member"
435
450
  else:
436
451
  role = link.role.replace("-", " ")
@@ -445,7 +460,7 @@ def _validate_tree(item: Item, validator: Callable[[Item, bool], bool],
445
460
  if type_name in _CONTAINER_TYPE:
446
461
  validation_dependencies.extend(
447
462
  (item_2.uid, "interface placement")
448
- for item_2 in item.children("interface-placement"))
463
+ for item_2 in get_interface_members(item))
449
464
  validated = validator(item, validated)
450
465
  item.view["validated"] = validated
451
466
  item.view["validation-dependencies"] = sorted(validation_dependencies)
@@ -457,7 +472,7 @@ def _validate_containers(item: Item) -> bool:
457
472
  if item.type in _CONTAINER_TYPE:
458
473
  # If at least one not validated child exists, then the container is not
459
474
  # validated
460
- for item_2 in item.children("interface-placement"):
475
+ for item_2 in get_interface_members(item):
461
476
  try:
462
477
  if not item_2.view["validated"]:
463
478
  validated = False
@@ -468,19 +483,20 @@ def _validate_containers(item: Item) -> bool:
468
483
  f"{item.uid} container member "
469
484
  f"{item_2.uid} has no validated status") from err
470
485
  for item_2 in itertools.chain(item.children(_CHILD_ROLES),
471
- item.parents(_PARENT_ROLES)):
486
+ item.parents(_VALIDATION_PARENT_ROLES)):
472
487
  validated = _validate_containers(item_2) and validated
473
488
  return validated
474
489
 
475
490
 
476
- def _fixup_pre_qualified(item: Item, types: list[str],
477
- roles: str | list[str]) -> None:
491
+ def _fixup_pre_qualified(
492
+ item: Item, types: list[str],
493
+ get_members: Callable[[Item], Iterable[Item]]) -> None:
478
494
  for type_name in types:
479
495
  for item_2 in item.cache.items_by_type.get(type_name, []):
480
496
  # Count of not pre-qualified (index 0) and pre-qualified (index 1)
481
497
  # children
482
498
  count = [0, 0]
483
- for item_3 in item_2.children(roles):
499
+ for item_3 in get_members(item_2):
484
500
  count[int(item_3.view["pre-qualified"])] += 1
485
501
  # If at least one not pre-qualified child exists and no
486
502
  # pre-qualified child exists, then the item is not pre-qualified.
@@ -497,11 +513,13 @@ def validate(root: Item, validator: Callable[[Item, bool], bool]) -> set[Item]:
497
513
  related_items: set[Item] = set()
498
514
  _validate_tree(root, validator, (0, ), related_items)
499
515
  _validate_containers(root)
500
- _fixup_pre_qualified(root,
501
- ["interface/appl-config-group", "interface/group"],
502
- ["interface-ingroup", "interface-ingroup-hidden"])
516
+ _fixup_pre_qualified(
517
+ root, ["interface/appl-config-group", "interface/group"],
518
+ lambda item_2: itertools.chain(
519
+ item_2.children(["interface-ingroup", "interface-ingroup-hidden"]),
520
+ item_2.parents("interface-group-member")))
503
521
  _fixup_pre_qualified(root, ["interface/header-file"],
504
- "interface-placement")
522
+ get_interface_members)
505
523
  return related_items
506
524
 
507
525
 
index 0fbcbe6..51e706e 100644
Binary file
File without changes
File without changes
File without changes
File without changes