Found while reviewing rosetta's unreviewed class_start n/a cells (keyword-rosetta docs/GATING.md "n/a semantics" review).
What happens
yacc.py:72 wires "class_start": None with docs/language_status/yacc.md (§1, §4) documenting this as deliberate: "a grammar file has no object/type concept." That's true for OOP-style classes, but it misses that Bison/Yacc's %union { ... } directive declares a real compound type — the C union spanning every grammar rule's semantic value ($$/$1/$2, already captured by args). This is the exact same shape the engine already wires class_start to elsewhere for non-OOP languages:
- Fortran's
TYPE ... END TYPE derived-type declarations (fortran.py:180-183, comment: "Fortran's struct/class equivalent")
- COBOL's
PROGRAM-ID/CLASS-ID (cobol.py:231-234)
- generic assembly's
struc/STRUCT macros (assembly.py:105-108)
%union isn't embedded C incidentally picked up by the C-action rules either — it's core yacc/bison grammar syntax. yacc.py's own internal_discriminator (line 48) already lists union among the definitive %-directive markers used to identify a file as yacc in the first place, but no signal rule anywhere in the 47-key dict captures it as a structural entity.
Evidence
Both real .y files in the crucible corpus use %union in the standard form:
# language-crucible-local/data/yacc/freebsd/config.y:1-6
%union {
char *str;
int val;
struct file_list *file;
}
(jailparse.y:45 has the same shape.) This is standard, common bison idiom, not an obscure corner case — 2/2 real gathered files use it.
Fix shape
yacc.py: add a class_start rule anchored on the %union directive, e.g. re.compile(r"^[ \t]*%union\b", re.M), following the same line-start-anchor convention already used for %token/%type in this file's structural_boundaries rule. (Optionally capture a name group for bison's less-common named-tag variant %union name { ... } for parity with how class_start elsewhere captures an entity name — presence detection alone already closes the gap, since a grammar has at most one %union block.)
- Re-run
crucible_check.py/golden masters — expected diff is class_start 0→1 on any real .y/.yy/.ypp file using %union (both freebsd corpus files).
- Update
docs/language_status/yacc.md §1/§3/§4 to move class_start out of the "16 explicit None" count and document the %union mapping alongside func_start's existing "closest function-analog" framing.
- Corpus follow-up (keyword-rosetta): once merged, the
yacc/class_start n/a cell becomes plantable — rosetta corpus PR adding a %union probe to data/yacc/*.y and updating expected_signals.json, same cross-repo choreography as jcl-2610/COND=.
Found by the #1096/#2560 rosetta n/a review sweep.
Found while reviewing rosetta's unreviewed
class_startn/a cells (keyword-rosettadocs/GATING.md"n/a semantics" review).What happens
yacc.py:72wires"class_start": Nonewithdocs/language_status/yacc.md(§1, §4) documenting this as deliberate: "a grammar file has no object/type concept." That's true for OOP-style classes, but it misses that Bison/Yacc's%union { ... }directive declares a real compound type — the C union spanning every grammar rule's semantic value ($$/$1/$2, already captured byargs). This is the exact same shape the engine already wiresclass_startto elsewhere for non-OOP languages:TYPE ... END TYPEderived-type declarations (fortran.py:180-183, comment: "Fortran's struct/class equivalent")PROGRAM-ID/CLASS-ID(cobol.py:231-234)struc/STRUCTmacros (assembly.py:105-108)%unionisn't embedded C incidentally picked up by the C-action rules either — it's core yacc/bison grammar syntax.yacc.py's owninternal_discriminator(line 48) already listsunionamong the definitive%-directive markers used to identify a file as yacc in the first place, but no signal rule anywhere in the 47-key dict captures it as a structural entity.Evidence
Both real
.yfiles in the crucible corpus use%unionin the standard form:(
jailparse.y:45has the same shape.) This is standard, common bison idiom, not an obscure corner case — 2/2 real gathered files use it.Fix shape
yacc.py: add aclass_startrule anchored on the%uniondirective, e.g.re.compile(r"^[ \t]*%union\b", re.M), following the same line-start-anchor convention already used for%token/%typein this file'sstructural_boundariesrule. (Optionally capture a name group for bison's less-common named-tag variant%union name { ... }for parity with howclass_startelsewhere captures an entity name — presence detection alone already closes the gap, since a grammar has at most one%unionblock.)crucible_check.py/golden masters — expected diff isclass_start0→1 on any real.y/.yy/.yppfile using%union(both freebsd corpus files).docs/language_status/yacc.md§1/§3/§4 to moveclass_startout of the "16 explicit None" count and document the%unionmapping alongsidefunc_start's existing "closest function-analog" framing.yacc/class_startn/a cell becomes plantable — rosetta corpus PR adding a%unionprobe todata/yacc/*.yand updatingexpected_signals.json, same cross-repo choreography asjcl-2610/COND=.Found by the #1096/#2560 rosetta n/a review sweep.