| 1 |
|
| 2 |
|
| 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 |
|
| 47 |
|
| 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 |
|
| 59 |
|
| 60 |
|
| 61 |
CONTROL = { |
| 62 |
|
| 63 |
"Row::disclosing": 1, "Row::relaxed": 1, "Row::depth": 1, |
| 64 |
"Slot::with_ranked": 1, "Slot::revealed_by": 1, "Choice::plain": 1, |
| 65 |
|
| 66 |
|
| 67 |
|
| 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 |
|
| 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 |
|