Skip to main content

max / quasi

6.7 KB · 164 lines History Blame Raw
1 #!/usr/bin/env python3
2 """Re-run the zero-site census behind section 3's deletion candidates.
3
4 Section 8 of wiki `quasi-declare-form` names "a dead-list member with a live
5 construction site" as one of four things that reopen the closure claim, and
6 warns that a fifth one means the census needs re-running with a different
7 predicate rather than patching. Four have already been found: `Row::meter`,
8 the `menu` field on Row and Cells, `Field::multiple` and `Field::keeps_value`.
9
10 WHY THE ORIGINAL CENSUS MISSED THEM. It looked for `Type::method(`. Three of the
11 four are reached another way, and the vocabulary is built by chaining, so that
12 predicate cannot see most of its own subject:
13
14 Row::meter(..) an associated call <- the only shape it saw
15 row.meter(..) a chained builder
16 row.meter = Some(..) a field assignment
17
18 `.current` is the documented case: 10 live sites, every one a field assignment.
19 So this script counts all three reach-paths, per name, over the three shape
20 directories, with comments, strings, raw strings and char literals blanked
21 first, tests and `parity.rs` excluded, by reusing `population.py`.
22
23 A hit is a CANDIDATE, not a verdict. `.value(` matches any type's `value`
24 builder, so a name that comes back live still has to be read at its sites to
25 say whether the receiver is the vocabulary type. The script prints the sites so
26 that reading is cheap. What it is for is the other direction: a name that comes
27 back at zero under all three paths is safely dead, and that is a claim the old
28 predicate could not make.
29
30 python3 scripts/dead-list-census.py # the census
31 python3 scripts/dead-list-census.py --sites # every hit with its line
32 """
33
34 from __future__ import annotations
35
36 import argparse
37 import importlib.util
38 import re
39 from pathlib import Path
40
41 ROOT = Path.home() / "Code"
42 spec = importlib.util.spec_from_file_location("population", Path(__file__).resolve().parent / "population.py")
43 pop = importlib.util.module_from_spec(spec)
44 spec.loader.exec_module(pop)
45
46 # Section 3's deletion candidates, by owning type. `Node`'s two variants and the
47 # two enums are matched as paths rather than as members.
48 CANDIDATES = {
49 "Row": ["open", "chosen", "value", "selected", "toggle", "worth", "role",
50 "primary", "identified", "choosing"],
51 "Cells": ["values"],
52 "Field": ["curve", "as_asked"],
53 "Screen": ["saying", "holds", "chooses", "anchors", "feeds", "is_live", "clocks"],
54 "Node": ["clock", "and_more", "meter", "holds", "names", "about"],
55 }
56 VARIANTS = ["Node::Age", "Node::Prose", "Loading::", "Clock::"]
57
58 # The six near-dead members the record KEEPS, each with one consumer. Counted
59 # as a control: if the script cannot see these, its predicate is broken and its
60 # zeroes mean nothing.
61 CONTROL = {
62 # The six near-dead members the record keeps, one consumer each.
63 "Row::disclosing": 1, "Row::relaxed": 1, "Row::depth": 1,
64 "Slot::with_ranked": 1, "Slot::revealed_by": 1, "Choice::plain": 1,
65 # The four the old census condemned wrongly. These are the real control:
66 # each is reached by a chained builder or a field assignment, which is the
67 # exact defect, and a predicate that cannot see them is the old one.
68 "Row::meter": 5, "Row::menu": 2, "Cells::menu": 1,
69 "Field::many": 1, "Field::keeping_value": 1,
70 }
71
72
73 def reach_paths(ty: str, name: str) -> dict[str, re.Pattern]:
74 """The three ways a member is reached, as named patterns."""
75 return {
76 "assoc": re.compile(rf"\b{ty}::{name}\s*\("),
77 "chained": re.compile(rf"\.{name}\s*\("),
78 "assigned": re.compile(rf"\.{name}\s*=(?!=)"),
79 }
80
81
82 def scan():
83 files = []
84 for rel in pop.SHAPE_DIRS:
85 d = ROOT / rel
86 for f in sorted(d.rglob("*.rs")):
87 parts = f.relative_to(ROOT).parts
88 if f.name in ("tests.rs", "parity.rs") or "tests" in parts:
89 continue
90 raw = f.read_text(encoding="utf-8", errors="replace")
91 files.append((str(f.relative_to(ROOT)), pop.strip_cfg_test(pop.blank_noncode(raw)), raw))
92 return files
93
94
95 def hits_for(files, pattern) -> list[str]:
96 out = []
97 for rel, blanked, raw in files:
98 for m in pattern.finditer(blanked):
99 line = blanked.count("\n", 0, m.start()) + 1
100 lines = raw.splitlines()
101 text = lines[line - 1].strip() if 0 <= line - 1 < len(lines) else ""
102 out.append(f"{rel}:{line} {text[:88]}")
103 return out
104
105
106 def main() -> int:
107 ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
108 ap.add_argument("--sites", action="store_true", help="print every hit with its line")
109 args = ap.parse_args()
110
111 files = scan()
112
113 print("CONTROL -- the six near-dead members the record keeps at one site each.")
114 print("If any reads 0 here, this predicate is broken and its zeroes mean nothing.\n")
115 broken = False
116 for full, expected in CONTROL.items():
117 ty, name = full.split("::")
118 total = sum(len(hits_for(files, p)) for p in reach_paths(ty, name).values())
119 flag = "" if total >= expected else " <-- PREDICATE BROKEN"
120 if total < expected:
121 broken = True
122 print(f" {full:24} {total:3} (record says {expected}){flag}")
123
124 print("\nDELETION CANDIDATES, all three reach-paths.\n")
125 live = []
126 for ty, names in CANDIDATES.items():
127 for name in names:
128 paths = reach_paths(ty, name)
129 found = {k: hits_for(files, p) for k, p in paths.items()}
130 total = sum(len(v) for v in found.values())
131 mark = " LIVE" if total else ""
132 detail = " ".join(f"{k}={len(v)}" for k, v in found.items() if v)
133 print(f" {ty + '::' + name:24} {total:3} {detail}{mark}")
134 if total:
135 live.append((f"{ty}::{name}", found))
136
137 print("\nVARIANTS AND ENUMS\n")
138 for v in VARIANTS:
139 p = re.compile(re.escape(v))
140 h = hits_for(files, p)
141 print(f" {v:24} {len(h):3}{' LIVE' if h else ''}")
142 if h:
143 live.append((v, {"path": h}))
144
145 print(f"\n{len(live)} of {sum(len(v) for v in CANDIDATES.values()) + len(VARIANTS)} "
146 f"deletion candidates have at least one candidate site.")
147 print("A candidate site is not a live construction: read it before acting. "
148 "A name at zero under all three paths is safely dead.")
149 if broken:
150 print("\nTHE CONTROL FAILED. Do not quote any zero above.")
151
152 if args.sites:
153 print("\n--- SITES ---")
154 for name, found in live:
155 print(f"\n{name}")
156 for kind, hs in found.items():
157 for h in hs:
158 print(f" [{kind}] {h}")
159 return 0
160
161
162 if __name__ == "__main__":
163 raise SystemExit(main())
164