code wiki / (root) / nx_dangling_else_gate.nx

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}