2"""Enforce that the documented field catalog matches the compiled one."""
4from __future__
import annotations
8from pathlib
import Path
11REPO_ROOT = Path(__file__).resolve().parents[2]
12EULERIAN_CATALOG = REPO_ROOT /
"src" /
"field_catalog.c"
13PARTICLE_CATALOG = REPO_ROOT /
"src" /
"particle_field_catalog.c"
14LAYOUT_HEADER = REPO_ROOT /
"include" /
"field_catalog.h"
15PAGE = REPO_ROOT /
"docs" /
"pages" /
"56_Field_Identity_and_Layout_Catalog.md"
19 "node-centered":
"FIELD_LAYOUT_NODE_CENTERED",
20 "shifted cell-centered":
"FIELD_LAYOUT_CELL_CENTERED",
21 "component-staggered":
"FIELD_LAYOUT_COMPONENT_STAGGERED",
22 "I-face family":
"FIELD_LAYOUT_I_FACE",
23 "J-face family":
"FIELD_LAYOUT_J_FACE",
24 "K-face family":
"FIELD_LAYOUT_K_FACE",
28 r'FIELD_(?:COORDINATE_)?ENTRY\(\s*FIELD_ID_\w+\s*,\s*"([^"]+)"'
29 r'[^)]*?(FIELD_LAYOUT_\w+)',
32PARTICLE_ENTRY_RE = re.compile(
r'PARTICLE_FIELD_ENTRY\(\s*PARTICLE_FIELD_ID_\w+\s*,\s*"([^"]+)"')
33LAYOUT_ENUM_RE = re.compile(
r"^\s*(FIELD_LAYOUT_\w+)", re.M)
34BACKTICKED_RE = re.compile(
r"`([A-Za-z_][A-Za-z0-9_]*)`")
39 @brief Canonical name to layout for every compiled Eulerian catalog entry.
40 @return Mapping of field name to `FIELD_LAYOUT_*` value.
42 return dict(ENTRY_RE.findall(EULERIAN_CATALOG.read_text(encoding=
"utf-8")))
47 @brief Canonical names of every compiled particle catalog entry.
48 @return Set of particle field names.
50 return set(PARTICLE_ENTRY_RE.findall(PARTICLE_CATALOG.read_text(encoding=
"utf-8")))
55 @brief Layout vocabulary the header defines.
56 @return Set of `FIELD_LAYOUT_*` enumerators.
58 text = LAYOUT_HEADER.read_text(encoding=
"utf-8")
59 body = text[text.index(
"FIELD_LAYOUT_NODE_CENTERED"):]
60 return set(LAYOUT_ENUM_RE.findall(body[: body.index(
"} FieldLayout;")]))
65 @brief One `@section` body from the catalog page.
66 @param[in] heading Section id to extract.
67 @return Section text up to the next section.
69 text = PAGE.read_text(encoding=
"utf-8")
70 start = text.index(heading)
71 following = text.find(
"@section", start + len(heading))
72 return text[start: following
if following != -1
else len(text)]
77 @brief Field-to-layout mapping the inventory section publishes.
78 @param[in] section Inventory section body.
79 @return Tuple of the mapping and any structural problems found.
82 block = re.search(
r"((?:^- .*(?:\n(?!\n)(?!- ).*)*\n)+)", section, re.M)
84 return {}, [
"section 6 no longer contains a layout-grouped list"]
86 for item
in re.findall(
r"^- (.*?)(?=^- |\Z)", block.group(1), re.S | re.M):
87 label, _, body = item.partition(
":")
88 layout = GROUP_LAYOUTS.get(label.strip())
90 problems.append(f
"section 6 lists an unrecognized layout group '{label.strip()}'")
92 for name
in BACKTICKED_RE.findall(body):
93 documented[name] = layout
94 return documented, problems
99 @brief Particle field names the inventory section publishes.
100 @param[in] section Inventory section body.
101 @return Set of documented particle field names.
103 match = re.search(
r"The particle inventory is:(.*?)\.\s", section, re.S)
104 return set(BACKTICKED_RE.findall(match.group(1)))
if match
else set()
109 @brief Layout vocabulary the layout section tabulates.
110 @return Set of documented `FIELD_LAYOUT_*` enumerators.
113 return set(re.findall(
r"\|\s*`(FIELD_LAYOUT_\w+)`\s*\|", section))
118 @brief Fail when the published catalog inventory and the compiled catalog disagree.
120 @details Checks identity sets, layout assignment, and layout vocabulary. It does not
121 check degrees of freedom, DM family, synchronization class, availability,
122 capability flags, or whether the runtime actually allocates a field.
123 @return Process status code.
129 for name
in sorted(set(code) - set(documented)):
130 problems.append(f
"{name}: in the compiled catalog, absent from section 6")
131 for name
in sorted(set(documented) - set(code)):
132 problems.append(f
"{name}: listed in section 6, absent from the compiled catalog")
133 for name
in sorted(set(code) & set(documented)):
134 if code[name] != documented[name]:
136 f
"{name}: compiled as {code[name]}, documented under {documented[name]}"
140 for layout
in sorted(header_layouts - page_layouts):
141 problems.append(f
"{layout}: defined in the header, absent from the section 4 table")
142 for layout
in sorted(page_layouts - header_layouts):
143 problems.append(f
"{layout}: tabulated in section 4, absent from the header")
146 for name
in sorted(particles_code - particles_page):
147 problems.append(f
"{name}: in the compiled particle catalog, absent from section 6")
148 for name
in sorted(particles_page - particles_code):
149 problems.append(f
"{name}: listed as a particle field, absent from the compiled catalog")
152 print(
"Field catalog documentation does not match the compiled catalog:", file=sys.stderr)
153 for problem
in problems:
154 print(f
" {problem}", file=sys.stderr)
156 "\nUpdate docs/pages/56_Field_Identity_and_Layout_Catalog.md section 6 to match\n"
157 "src/field_catalog.c and src/particle_field_catalog.c. The compiled catalog is\n"
158 "authoritative; the page is the published record of it.",
164 f
"Field catalog audit passed: {len(code)} Eulerian and {len(particles_code)} particle "
165 f
"identities, {len(header_layouts)} layouts. Degrees of freedom, DM family, "
166 "synchronization class, availability, capabilities, and runtime allocation are "
172if __name__ ==
"__main__":
173 raise SystemExit(
main())
set documented_particle(str section)
Particle field names the inventory section publishes.
int main()
Fail when the published catalog inventory and the compiled catalog disagree.
set documented_layouts()
Layout vocabulary the layout section tabulates.
set compiled_particle()
Canonical names of every compiled particle catalog entry.
str page_section(str heading)
One @section body from the catalog page.
set compiled_layouts()
Layout vocabulary the header defines.
dict compiled_eulerian()
Canonical name to layout for every compiled Eulerian catalog entry.
tuple documented_eulerian(str section)
Field-to-layout mapping the inventory section publishes.