nx_dangling_else_gate.nx source
↩ module page · 173 lines · 13338 B
1// nx_dangling_else_gate.nx -- LN30 (lang.plan): AN `else` WITH NO `if` IS REFUSED, NEVER RUN. End-to-end over the
2// PROMOTED compiler: three fixtures are assembled at runtime in /tmp/<gate>/ (never a file a source census could
3// mistake for an organ), the compiler is forked on each with stderr captured, and the teeth read the captured bytes.
4// fx_dangling `if c {..} else {..} else {..}` -> MUST be refused, naming the else, with where (caret), why and fix
5// fx_chain `if {..} else if {..} else {..}` -> MUST compile (the else-if production is untouched)
6// fx_plain `if {..} else {..}` then a second `if` -> MUST compile (an ordinary else is untouched)
7// Why this exists (measured 2026-09-02, nx_autofix_intake_gate): a `} else { ... }` hanging after an if/else that had
8// already closed reached the statement fallback, produced nothing, and the block after it parsed as a bare block that
9// RAN UNCONDITIONALLY -- every admission-refused row was counted twice while the gate printed a partition that did not
10// sum. The refusal is the LN30 contract; the two controls keep this gate from passing on a compiler that refuses every
11// else. Byte-identity of default builds is nx_cc_equiv_gate's job, deliberately not duplicated here.
12// nx_dangling_else_gate -- verdict in the exit code (gv_verdict)
13// license_tier: ORIGINAL layer: lang module: nishi-core.lang.dangling_else_gate
14import "nx_syscalls.nx"
15import "nx_gate_verdict.nx"
16
17const DE_DIR: *u8 = "/tmp/nx_dangling_else_gate\x00" as *u8
18const DE_BAD: *u8 = "/tmp/nx_dangling_else_gate/fx_dangling.nx\x00" as *u8
19const DE_CHAIN: *u8 = "/tmp/nx_dangling_else_gate/fx_chain.nx\x00" as *u8
20const DE_PLAIN: *u8 = "/tmp/nx_dangling_else_gate/fx_plain.nx\x00" as *u8
21const DE_LOUD: *u8 = "/tmp/nx_dangling_else_gate/fx_loud.nx\x00" as *u8
22const DE_ERR_L: *u8 = "/tmp/nx_dangling_else_gate/fx_loud.err\x00" as *u8
23const DE_MODVAR: *u8 = "/tmp/nx_dangling_else_gate/fx_modvar.nx\x00" as *u8
24const DE_MODOK: *u8 = "/tmp/nx_dangling_else_gate/fx_modok.nx\x00" as *u8
25const DE_ERR_V: *u8 = "/tmp/nx_dangling_else_gate/fx_modvar.err\x00" as *u8
26const DE_ERR_O: *u8 = "/tmp/nx_dangling_else_gate/fx_modok.err\x00" as *u8
27const DE_BLOCK: *u8 = "/tmp/nx_dangling_else_gate/fx_block.nx\x00" as *u8
28const DE_SCOPE: *u8 = "/tmp/nx_dangling_else_gate/fx_scope.nx\x00" as *u8
29const DE_ERR_K: *u8 = "/tmp/nx_dangling_else_gate/fx_block.err\x00" as *u8
30const DE_ERR_S: *u8 = "/tmp/nx_dangling_else_gate/fx_scope.err\x00" as *u8
31const DE_ERR_B: *u8 = "/tmp/nx_dangling_else_gate/fx_dangling.err\x00" as *u8
32const DE_ERR_C: *u8 = "/tmp/nx_dangling_else_gate/fx_chain.err\x00" as *u8
33const DE_ERR_P: *u8 = "/tmp/nx_dangling_else_gate/fx_plain.err\x00" as *u8
34const DE_DEVNULL: *u8 = "/dev/null\x00" as *u8
35const DE_CC_NAS: *u8 = "buildroot/_offc/nx_cc_sovereign.elf\x00" as *u8 // NAS gate CWD = nishihost root
36const DE_CC_LAP: *u8 = "_offc/nx_cc_sovereign.elf\x00" as *u8 // laptop CWD = nxc2 root
37const DE_CAP: i64 = 65536
38const DE_MODE: i64 = 420
39
40func de_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
41func de_w(s: *u8) -> i64 { sys_write(1, s, de_slen(s)); return 0 }
42func de_find(b: *u8, n: i64, pat: *u8) -> i64 {
43 let pl: i64 = de_slen(pat)
44 if pl <= 0 { return 0 - 1 }
45 var i: i64 = 0
46 while i + pl <= n {
47 var j: i64 = 0; var ok: i64 = 1
48 while j < pl { if b[i + j] != pat[j] { ok = 0; j = pl } else { j = j + 1 } }
49 if ok == 1 { return i }
50 i = i + 1
51 }
52 return 0 - 1
53}
54func de_write_file(path: *u8, body: *u8) -> i64 {
55 let fd: i64 = sys_openat_wr(path, DE_MODE)
56 if fd < 0 { return 0 - 1 }
57 let n: i64 = de_slen(body)
58 sys_write(fd, body, n)
59 sys_close(fd)
60 return n
61}
62// fork the compiler on src with stderr -> errpath; exit code, -1 on fork fail, 127 on exec fail.
63func de_compile(cc: *u8, src: *u8, errpath: *u8) -> i64 {
64 let pid: i64 = sys_fork()
65 if pid < 0 { return 0 - 1 }
66 if pid == 0 {
67 let dn: i64 = sys_openat_wr(DE_DEVNULL, DE_MODE)
68 if dn >= 0 { sys_dup3(dn, 1, 0) }
69 let ef: i64 = sys_openat_wr(errpath, DE_MODE)
70 if ef >= 0 { sys_dup3(ef, 2, 0) }
71 let argv: *i64 = sys_mmap(32) as *i64
72 argv[0] = cc as i64; argv[1] = src as i64; argv[2] = 0
73 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0
74 sys_execve(cc, argv, envp)
75 sys_exit(127)
76 }
77 let st: *i64 = sys_mmap(16) as *i64
78 sys_wait4(pid, st, 0)
79 return (st[0] >> 8) & 0xff
80}
81func de_read(path: *u8, buf: *u8, cap: i64) -> i64 {
82 let fd: i64 = sys_openat_rd(path)
83 if fd < 0 { return 0 - 1 }
84 var tot: i64 = 0; var go: i64 = 1
85 while go == 1 {
86 let n: i64 = sys_read(fd, ((buf as i64) + tot) as *u8, cap - tot)
87 if n <= 0 { go = 0 } else { tot = tot + n; if tot >= cap { go = 0 } }
88 }
89 sys_close(fd)
90 return tot
91}
92func de_pick_cc() -> *u8 {
93 let fd: i64 = sys_openat_rd(DE_CC_NAS)
94 if fd >= 0 { sys_close(fd); return DE_CC_NAS }
95 return DE_CC_LAP
96}
97
98func main() -> i64 {
99 let c: *i64 = gv_ctr()
100 sys_mkdir(DE_DIR, 493)
101 let cc: *u8 = de_pick_cc()
102 de_w(" compiler=" as *u8); de_w(cc); de_w("\n" as *u8)
103 // fixtures assembled at RUNTIME; no imports, so they resolve from any CWD.
104 // fx_dangling is the SILENT class, byte-for-byte the shape that bit nx_autofix_intake_gate: the inner
105 // else-block is closed one line early, so `} else { ... }` hangs as a second else AND the file is one
106 // brace short. The pre-LN30 compiler swallowed the `else {`, let that `{` stand in for the missing brace,
107 // compiled it with exit 0 (measured: 10,644 B of asm) and RAN the block unconditionally. A fixture that
108 // the old compiler refused loudly would prove nothing about the class that shipped.
109 let w1: i64 = de_write_file(DE_BAD, "func main() -> i64 {\n var n: i64 = 0\n let c: i64 = 1\n if c == 1 {\n if c == 2 {\n n = n + 1\n } else {\n if c == 1 {\n n = n + 10\n }\n } else { n = n + 100 }\n return n\n}\n" as *u8)
110 let w4: i64 = de_write_file(DE_LOUD, "func main() -> i64 {\n var n: i64 = 0\n let c: i64 = 1\n if c == 2 {\n n = n + 1\n } else {\n n = n + 10\n } else { n = n + 100 }\n return n\n}\n" as *u8)
111 let w2: i64 = de_write_file(DE_CHAIN, "func main() -> i64 {\n var n: i64 = 0\n let c: i64 = 1\n if c == 3 { n = n + 1000 } else if c == 1 { n = n + 100 } else { n = n + 5 }\n return n\n}\n" as *u8)
112 let w3: i64 = de_write_file(DE_PLAIN, "func main() -> i64 {\n var n: i64 = 0\n let c: i64 = 1\n if c == 2 {\n n = n + 1\n } else {\n n = n + 10\n }\n if c == 1 { n = n + 7 }\n return n\n}\n" as *u8)
113 gv_check("fixtures-written-to-tmp-gate-dir" as *u8, (w1 > 0) & (w2 > 0) & (w3 > 0) & (w4 > 0), c)
114
115 let rc_b: i64 = de_compile(cc, DE_BAD, DE_ERR_B)
116 let rc_l: i64 = de_compile(cc, DE_LOUD, DE_ERR_L)
117 let rc_c: i64 = de_compile(cc, DE_CHAIN, DE_ERR_C)
118 let rc_p: i64 = de_compile(cc, DE_PLAIN, DE_ERR_P)
119 let eb: *u8 = sys_mmap(DE_CAP); let nb: i64 = de_read(DE_ERR_B, eb, DE_CAP - 1)
120 let el: *u8 = sys_mmap(DE_CAP); let nl: i64 = de_read(DE_ERR_L, el, DE_CAP - 1)
121 let ec: *u8 = sys_mmap(DE_CAP); let nc: i64 = de_read(DE_ERR_C, ec, DE_CAP - 1)
122 let ep: *u8 = sys_mmap(DE_CAP); let np: i64 = de_read(DE_ERR_P, ep, DE_CAP - 1)
123 de_w(" silent rc=" as *u8); gv_num(rc_b); de_w(" loud rc=" as *u8); gv_num(rc_l); de_w(" chain rc=" as *u8); gv_num(rc_c); de_w(" plain rc=" as *u8); gv_num(rc_p); de_w("\n" as *u8)
124 if nb > 0 { de_w(" silent stderr head: " as *u8); var k: i64 = 0; while k < nb { if k < 160 { if eb[k] == (10 as u8) { k = nb } else { sys_write(1, ((eb as i64) + k) as *u8, 1) } } k = k + 1 } de_w("\n" as *u8) }
125
126 gv_check("fixture-reached-the-compiler-all-four-ran" as *u8, (rc_b >= 0) & (rc_l >= 0) & (rc_c >= 0) & (rc_p >= 0) & (rc_b != 127) & (rc_l != 127) & (rc_c != 127) & (rc_p != 127), c)
127 gv_check("silent-class-dangling-else-is-refused-nonzero-exit (pre-LN30 compiled it with exit 0)" as *u8, rc_b != 0, c)
128 gv_check("refusal-names-the-else" as *u8, de_find(eb, nb, "this 'else' has no 'if' to belong to" as *u8) >= 0, c)
129 gv_check("loud-class-top-level-dangling-else-is-refused-BY-NAME-not-by-a-module-level-assert" as *u8, de_find(el, nl, "this 'else' has no 'if' to belong to" as *u8) >= 0, c)
130 gv_check("refusal-carries-the-location-anchor" as *u8, de_find(eb, nb, "error at " as *u8) >= 0, c)
131 gv_check("refusal-carries-why-the-build-stopped" as *u8, de_find(eb, nb, "why the build stopped" as *u8) >= 0, c)
132 gv_check("refusal-fix-names-the-brace-one-level-off" as *u8, de_find(eb, nb, "one level off" as *u8) >= 0, c)
133 // the silent fixture is ALSO one brace short, so its unit ends in the end-of-file diagnostic; the
134 // "wrote no program" summary is asserted on the loud fixture, whose only defect is the stray else.
135 gv_check("refusal-writes-no-program" as *u8, de_find(el, nl, "wrote no program" as *u8) >= 0, c)
136 gv_check("silent-class-refusal-is-the-FIRST-error-reported (the cause, not the knock-on brace errors)" as *u8, de_find(eb, nb, "error at " as *u8) == de_find(eb, nb, "error at fx_dangling.nx:11: this 'else'" as *u8), c)
137 gv_check("control-else-if-chain-compiles-exit-0" as *u8, rc_c == 0, c)
138 gv_check("control-else-if-chain-prints-no-refusal" as *u8, de_find(ec, nc, "has no 'if'" as *u8) < 0, c)
139 gv_check("neg-control-plain-if-else-compiles-exit-0" as *u8, rc_p == 0, c)
140 gv_check("neg-control-plain-if-else-prints-no-refusal" as *u8, de_find(ep, np, "has no 'if'" as *u8) < 0, c)
141
142 // LN31 (same day, same site family): a keyword at MODULE level used to die on an assert that blamed a brace.
143 // A module-level `var` is the measured case (it cost a build of the office gate); its `static` twin compiles.
144 let w5: i64 = de_write_file(DE_MODVAR, "var g_count: i64 = 0\nfunc main() -> i64 {\n g_count = g_count + 1\n return g_count\n}\n" as *u8)
145 let w6: i64 = de_write_file(DE_MODOK, "static g_count: i64\nfunc main() -> i64 {\n g_count = g_count + 1\n return g_count\n}\n" as *u8)
146 gv_check("ln31-fixtures-written" as *u8, (w5 > 0) & (w6 > 0), c)
147 let rc_v: i64 = de_compile(cc, DE_MODVAR, DE_ERR_V)
148 let rc_o: i64 = de_compile(cc, DE_MODOK, DE_ERR_O)
149 let ev: *u8 = sys_mmap(DE_CAP); let nv: i64 = de_read(DE_ERR_V, ev, DE_CAP - 1)
150 let eo: *u8 = sys_mmap(DE_CAP); let no: i64 = de_read(DE_ERR_O, eo, DE_CAP - 1)
151 de_w(" modvar rc=" as *u8); gv_num(rc_v); de_w(" static-control rc=" as *u8); gv_num(rc_o); de_w("\n" as *u8)
152 gv_check("ln31-module-level-var-is-refused-nonzero-exit" as *u8, (rc_v != 0) & (rc_v != 127), c)
153 gv_check("ln31-refusal-names-var-and-teaches-static (not a brace count)" as *u8, de_find(ev, nv, "'var' declares state inside a function" as *u8) >= 0, c)
154 gv_check("ln31-refusal-carries-fix-static-name-type" as *u8, de_find(ev, nv, "static name: type" as *u8) >= 0, c)
155 gv_check("ln31-refusal-no-longer-blames-a-brace-first" as *u8, de_find(ev, nv, "ONE BRACE TOO MANY" as *u8) < 0, c)
156 gv_check("neg-control-ln31-static-module-state-compiles-exit-0" as *u8, rc_o == 0, c)
157
158 // LN32 (same census): a BARE BLOCK STATEMENT `{ ... }` is a scope. Before LN32 a `{` at statement start fell
159 // into the expression fallback and its closing brace CLOSED THE FUNCTION EARLY (three estate sources were
160 // unbuildable for that reason). Two sibling blocks may each declare `d`; a block-local name is gone after it.
161 let w7: i64 = de_write_file(DE_BLOCK, "func main() -> i64 {\n var total: i64 = 0\n { let d: i64 = 5; var m: i64 = d * 2; total = total + m }\n { let d: i64 = 7; var m: i64 = d * 3; total = total + m }\n return total\n}\n" as *u8)
162 let w8: i64 = de_write_file(DE_SCOPE, "func main() -> i64 {\n var total: i64 = 0\n { let d: i64 = 5; total = total + d }\n return total + d\n}\n" as *u8)
163 gv_check("ln32-fixtures-written" as *u8, (w7 > 0) & (w8 > 0), c)
164 let rc_k: i64 = de_compile(cc, DE_BLOCK, DE_ERR_K)
165 let rc_s: i64 = de_compile(cc, DE_SCOPE, DE_ERR_S)
166 let ek: *u8 = sys_mmap(DE_CAP); let nk: i64 = de_read(DE_ERR_K, ek, DE_CAP - 1)
167 let es: *u8 = sys_mmap(DE_CAP); let ns: i64 = de_read(DE_ERR_S, es, DE_CAP - 1)
168 de_w(" block rc=" as *u8); gv_num(rc_k); de_w(" scope-leak rc=" as *u8); gv_num(rc_s); de_w("\n" as *u8)
169 gv_check("ln32-bare-block-statements-with-sibling-lets-compile-exit-0 (pre-LN32 closed the function early)" as *u8, rc_k == 0, c)
170 gv_check("ln32-block-prints-no-module-level-or-unknown-name-error" as *u8, (de_find(ek, nk, "outside every function" as *u8) < 0) & (de_find(ek, nk, "I do not know the name" as *u8) < 0), c)
171 gv_check("neg-control-ln32-block-local-name-used-after-the-block-is-refused-by-name" as *u8, (rc_s != 0) & (rc_s != 127) & (de_find(es, ns, "I do not know the name 'd'" as *u8) >= 0), c)
172 return gv_verdict("DANGLING-ELSE-GATE" as *u8, c, "LN30: an else that begins a statement is refused by name with where, why and fix; else-if chains and ordinary if/else still compile" as *u8)
173}