code wiki / (root) / nx_langspec.nx

nx_langspec.nx source

↩ module page · 767 lines · 33803 B

1// nx_langspec.nx -- THE CONFORMANCE RUNNER for the written NishiLang specification. 2// lang.plan rung LR5, watch symbol spec_conformance_run, board /compare/lang. 3// 4// THE FERROCENE LESSON, TAKEN EARLY. Rust reached safety-critical qualification only by retrofitting 5// a written specification years after 1.0, and the qualification artifact was never the prose -- it 6// was REQUIREMENTS-TO-TESTS TRACEABILITY. So the load-bearing half of this rung is this runner, not 7// the document: every clause in knowledge/specs/nishilang.spec carries an ID, every case in 8// knowledge/specs/nishilang.suite cites the clause IDs it exercises, and this organ resolves those 9// citations MECHANICALLY, drives the SHIPPED compiler over every case, and reports which clauses no 10// case exercises. That unexercised list is the real deliverable: a spec nothing checks is a comment, 11// and a clause nothing exercises is UNVERIFIED however confidently it is written. 12// 13// WHAT IT IS NOT. It is not a second compiler-gate driver: it COMPOSES nx_ccgate_lib, the one organ 14// in the estate that spawns the compiler, and nx_lineconf_lib for its thresholds. It does not 15// replace nx_langdiag_gate -- that gate proved 11 hand-written rows refuse for the RIGHT reason and 16// is where this suite's discipline comes from. What it adds is the three things a hard-coded row 17// list structurally cannot have: the cases are DATA (adding one is a data row, not a rebuild), each 18// case is TRACED to a normative clause, and the run reports COVERAGE over the whole clause set. 19// 20// THREE STATES, NEVER TWO. A case is PASS, FAIL or UNOBSERVABLE. UNOBSERVABLE is a case the runner 21// could not judge through no fault of the language -- its source is missing, the assembler failed, 22// the artifact could not be renamed. It is NOT folded into pass and NOT folded into fail: an axis 23// that cannot see must abstain, never acquit. Every count is printed beside its denominator and the 24// partition is reconciled in the output, because an abstention nobody reads is a lie nobody told. 25// 26// NO SILENT CAPS. Budgets come from the conf. A spec or suite that exceeds one is not truncated in 27// silence: the surplus is counted, named OVER-BUDGET, and coverage_complete is forced to 0. 28// 29// EXITS: 0 CONFORMANT - 1 DEVIATION - 2 usage - 3 UNOBSERVABLE - 4 EMPTY-SUITE. 30// EMPTY-SUITE is its own exit on purpose: a conformance runner that certifies the empty set is the 31// vacuous-test defect wearing a certification costume, and 0 must be unreachable that way. 32// 33// DECLARED LIMITATION, MEASURED NOT ASSUMED: an INLINE case is materialised into a scratch directory 34// outside the tree and the compiler resolves an import relative to the importing file, so an INLINE 35// case CANNOT import. A case that needs the standard library is written as a fixture in the tree and 36// cited by path. Found by this organ's own first run, which reported the offending case FAIL in the 37// compiler's own words (expand_imports failed, exit 12) rather than passing it for the wrong reason. 38// 39// Usage: nx_langspec [run|coverage|clauses|cases] [spec] [suite] [compiler] [conf] 40// The conf argument exists so a gate can supply its OWN thresholds against its OWN fixture spec 41// without touching the production ratchet -- a gate that had to lower the live coverage floor to run 42// its fixtures would be editing the detector to fit the test. 43// license_tier: ORIGINAL Read-only against the tree. No hw writes (Rule 26). 44import "nx_syscalls.nx" 45import "nx_gate_verdict.nx" 46import "nx_ccgate_lib.nx" 47import "nx_lineconf_lib.nx" 48 49const LS_CONF: *u8 = "knowledge/specs/nishilang_conformance.conf\x00" 50const LS_SPEC_DEF: *u8 = "knowledge/specs/nishilang.spec\x00" 51const LS_SUITE_DEF: *u8 = "knowledge/specs/nishilang.suite\x00" 52const LS_SCRATCH: *u8 = "/tmp/nx_langspec\x00" 53 54const LS_EXIT_CONFORMANT: i64 = 0 55const LS_EXIT_DEVIATION: i64 = 1 56const LS_EXIT_USAGE: i64 = 2 57const LS_EXIT_UNOBSERVABLE: i64 = 3 58const LS_EXIT_EMPTY: i64 = 4 59 60const LS_KIND_ACCEPT: i64 = 1 61const LS_KIND_REFUSE: i64 = 2 62const LS_KIND_RUN: i64 = 3 63 64const LS_V_PASS: i64 = 1 65const LS_V_FAIL: i64 = 2 66const LS_V_UNOBS: i64 = 3 67 68const LS_R_NONE: i64 = 0 69const LS_R_SRC_ABSENT: i64 = 1 70const LS_R_REFUSED_LEGAL: i64 = 2 71const LS_R_ASM_MARKER: i64 = 3 72const LS_R_ACCEPTED_ILLEGAL: i64 = 4 73const LS_R_WRONG_REASON: i64 = 5 74const LS_R_WRONG_CC_EXIT: i64 = 6 75const LS_R_ASSEMBLER: i64 = 7 76const LS_R_RENAME: i64 = 8 77const LS_R_WRONG_RUN_EXIT: i64 = 9 78const LS_R_INLINE_WRITE: i64 = 10 79const LS_R_BAD_KIND: i64 = 11 80 81// clause record stride and fields 82const LS_LF: i64 = 9 83const LS_L_IDOFF: i64 = 0 84const LS_L_IDLEN: i64 = 1 85const LS_L_SECOFF: i64 = 2 86const LS_L_SECLEN: i64 = 3 87const LS_L_TTLOFF: i64 = 4 88const LS_L_TTLLEN: i64 = 5 89const LS_L_TXTOFF: i64 = 6 90const LS_L_TXTLEN: i64 = 7 91const LS_L_EX: i64 = 8 92 93// case record stride and fields 94const LS_CF: i64 = 18 95const LS_C_IDOFF: i64 = 0 96const LS_C_IDLEN: i64 = 1 97const LS_C_CLOFF: i64 = 2 98const LS_C_CLLEN: i64 = 3 99const LS_C_KIND: i64 = 4 100const LS_C_FLGOFF: i64 = 5 101const LS_C_FLGLEN: i64 = 6 102const LS_C_EXPOFF: i64 = 7 103const LS_C_EXPLEN: i64 = 8 104const LS_C_SRCOFF: i64 = 9 105const LS_C_SRCLEN: i64 = 10 106const LS_C_INLINE: i64 = 11 107const LS_C_VERD: i64 = 12 108const LS_C_WANT: i64 = 13 109const LS_C_GOT: i64 = 14 110const LS_C_PAROFF: i64 = 15 111const LS_C_PARLEN: i64 = 16 112const LS_C_REASON: i64 = 17 113 114const LS_PIPE: i64 = 124 115const LS_SEMI: i64 = 59 116const LS_SPEC_FIELDS: i64 = 5 117const LS_CASE_FIELDS: i64 = 8 118const LS_PERMIL: i64 = 1000 119const LS_MODE_RW: i64 = 0x1a4 120const LS_SCRATCH_MODE: i64 = 0x1ed 121const LS_FBUF: i64 = 32 122 123// --------------------------------------------------------------------------------------------- 124// primitives -- explicit go-flags everywhere; the loop cursor is NEVER the exit sentinel. 125// --------------------------------------------------------------------------------------------- 126func ls_p(s: *u8) -> i64 { gv_puts(s); return 0 } 127func ls_n(v: i64) -> i64 { gv_num(v); return 0 } 128func ls_kv(k: *u8, v: i64) -> i64 { gv_puts(k); gv_puts("=" as *u8); gv_num(v); gv_puts("\n" as *u8); return 0 } 129 130func ls_wr(b: *u8, off: i64, len: i64) -> i64 { 131 if len > 0 { sys_write(1, ((b as i64) + off) as *u8, len) } 132 return 0 133} 134 135// index of the newline that ends the line starting at i, or n 136func ls_eol(b: *u8, n: i64, i: i64) -> i64 { 137 var k: i64 = i 138 var go: i64 = 1 139 while go == 1 { 140 if k >= n { go = 0 } else { 141 if b[k] == (LCF_NL as u8) { go = 0 } else { k = k + 1 } 142 } 143 } 144 return k 145} 146 147func ls_starts(b: *u8, off: i64, end: i64, lit: *u8) -> i64 { 148 var j: i64 = 0 149 var ok: i64 = 1 150 var go: i64 = 1 151 while go == 1 { 152 if lit[j] == (0 as u8) { go = 0 } else { 153 if off + j >= end { ok = 0; go = 0 } else { 154 if b[off + j] != lit[j] { ok = 0; go = 0 } else { j = j + 1 } 155 } 156 } 157 } 158 return ok 159} 160 161func ls_litlen(lit: *u8) -> i64 { var j: i64 = 0; while lit[j] != (0 as u8) { j = j + 1 } return j } 162 163func ls_slice_is(b: *u8, off: i64, len: i64, lit: *u8) -> i64 { 164 if ls_litlen(lit) != len { return 0 } 165 return ls_starts(b, off, off + len, lit) 166} 167 168func ls_streq(a: *u8, b: *u8) -> i64 { 169 var i: i64 = 0 170 var same: i64 = 1 171 var go: i64 = 1 172 while go == 1 { 173 if a[i] == (0 as u8) { go = 0 } else { 174 if a[i] != b[i] { same = 0; go = 0 } else { i = i + 1 } 175 } 176 } 177 if same == 1 { if b[i] != (0 as u8) { same = 0 } } 178 return same 179} 180 181// number of pipe-delimited fields on [off,end) 182func ls_nfields(b: *u8, off: i64, end: i64) -> i64 { 183 var c: i64 = 1 184 var i: i64 = off 185 while i < end { if b[i] == (LS_PIPE as u8) { c = c + 1 } i = i + 1 } 186 return c 187} 188 189// k-th (0-based) pipe field of [off,end) into out[0]=off out[1]=len; 1 when present 190func ls_field(b: *u8, off: i64, end: i64, k: i64, out: *i64) -> i64 { 191 var idx: i64 = 0 192 var s: i64 = off 193 var i: i64 = off 194 var found: i64 = 0 195 var go: i64 = 1 196 while go == 1 { 197 var brk: i64 = 0 198 if i >= end { brk = 1 } else { if b[i] == (LS_PIPE as u8) { brk = 1 } } 199 if brk == 1 { 200 if idx == k { out[0] = s; out[1] = i - s; found = 1; go = 0 } else { 201 if i >= end { go = 0 } else { idx = idx + 1; s = i + 1; i = i + 1 } 202 } 203 } else { i = i + 1 } 204 } 205 return found 206} 207 208func ls_dup(b: *u8, off: i64, len: i64) -> *u8 { 209 let d: *u8 = sys_mmap(len + 2) 210 var i: i64 = 0 211 while i < len { d[i] = b[off + i]; i = i + 1 } 212 d[len] = 0 as u8 213 return d 214} 215 216// the integer that follows the first equals sign inside the slice, or -1 217func ls_int_after_eq(b: *u8, off: i64, len: i64) -> i64 { 218 var p: i64 = 0 - 1 219 var i: i64 = 0 220 while i < len { 221 if b[off + i] == (LCF_EQ as u8) { if p < 0 { p = i + 1 } } 222 i = i + 1 223 } 224 if p < 0 { return 0 - 1 } 225 var v: i64 = 0 226 var seen: i64 = 0 227 var j: i64 = p 228 var go: i64 = 1 229 while go == 1 { 230 if j >= len { go = 0 } else { 231 let c: i64 = b[off + j] as i64 232 var isd: i64 = 0 233 if c >= LCF_ZERO { if c <= LCF_NINE { isd = 1 } } 234 if isd == 1 { v = v * LCF_DECIMAL + (c - LCF_ZERO); seen = 1; j = j + 1 } else { go = 0 } 235 } 236 } 237 if seen == 0 { return 0 - 1 } 238 return v 239} 240 241func ls_readable(p: *u8) -> i64 { 242 let fd: i64 = sys_openat_rd(p) 243 if fd < 0 { return 0 } 244 sys_close(fd) 245 return 1 246} 247 248func ls_write_slice(path: *u8, b: *u8, off: i64, len: i64) -> i64 { 249 let fd: i64 = sys_openat_wr(path, LS_MODE_RW) 250 if fd < 0 { return 0 - 1 } 251 if len > 0 { sys_write(fd, ((b as i64) + off) as *u8, len) } 252 sys_close(fd) 253 return 0 254} 255 256// print up to cap bytes of a capture, ANNOUNCING the real size so a shown prefix can never be 257// mistaken for the whole thing. 258func ls_head(path: *u8, cap: i64) -> i64 { 259 let lp: *i64 = sys_mmap(LCF_WORD * 2) as *i64 260 let b: *u8 = sys_read_file(path, lp) 261 if (b as i64) == 0 { ls_p(" capture-head bytes=0 shown=0 (unreadable)\n" as *u8); return 0 } 262 var n: i64 = lp[0] 263 var shown: i64 = n 264 if shown > cap { shown = cap } 265 ls_p(" capture-head bytes=" as *u8); ls_n(n) 266 ls_p(" shown=" as *u8); ls_n(shown) 267 ls_p("\n" as *u8) 268 if shown > 0 { sys_write(1, b, shown) } 269 ls_p("\n" as *u8) 270 return 0 271} 272 273func ls_verd_name(v: i64) -> *u8 { 274 if v == LS_V_PASS { return "PASS" as *u8 } 275 if v == LS_V_FAIL { return "FAIL" as *u8 } 276 if v == LS_V_UNOBS { return "UNOBSERVABLE" as *u8 } 277 return "NOT-RUN" as *u8 278} 279 280func ls_reason_name(r: i64) -> *u8 { 281 if r == LS_R_SRC_ABSENT { return "case-source-absent" as *u8 } 282 if r == LS_R_REFUSED_LEGAL { return "compiler-refused-a-program-the-spec-says-is-legal" as *u8 } 283 if r == LS_R_ASM_MARKER { return "accepted-but-the-required-assembly-marker-is-absent" as *u8 } 284 if r == LS_R_ACCEPTED_ILLEGAL { return "compiler-accepted-a-program-the-spec-says-is-refused" as *u8 } 285 if r == LS_R_WRONG_REASON { return "refused-but-not-for-the-clause-under-test" as *u8 } 286 if r == LS_R_WRONG_CC_EXIT { return "refused-with-the-wrong-compiler-exit-status" as *u8 } 287 if r == LS_R_ASSEMBLER { return "assembler-failed-so-the-language-was-not-judged" as *u8 } 288 if r == LS_R_RENAME { return "artifact-rename-failed-so-the-language-was-not-judged" as *u8 } 289 if r == LS_R_WRONG_RUN_EXIT { return "program-ran-with-the-wrong-exit-status" as *u8 } 290 if r == LS_R_INLINE_WRITE { return "inline-case-source-could-not-be-materialised" as *u8 } 291 if r == LS_R_BAD_KIND { return "unknown-case-kind" as *u8 } 292 return "-" as *u8 293} 294 295func ls_kind_name(k: i64) -> *u8 { 296 if k == LS_KIND_ACCEPT { return "ACCEPT" as *u8 } 297 if k == LS_KIND_REFUSE { return "REFUSE" as *u8 } 298 if k == LS_KIND_RUN { return "RUN" as *u8 } 299 return "UNKNOWN" as *u8 300} 301 302// index of the clause whose id equals the cited slice, or -1 303func ls_find_clause(L: *i64, nl: i64, sb: *u8, ub: *u8, off: i64, len: i64) -> i64 { 304 var k: i64 = 0 305 var hit: i64 = 0 - 1 306 while k < nl { 307 if hit < 0 { 308 let o: i64 = L[k * LS_LF + LS_L_IDOFF] 309 let n: i64 = L[k * LS_LF + LS_L_IDLEN] 310 if n == len { 311 var j: i64 = 0 312 var same: i64 = 1 313 var go: i64 = 1 314 while go == 1 { 315 if j >= len { go = 0 } else { 316 if sb[o + j] != ub[off + j] { same = 0; go = 0 } else { j = j + 1 } 317 } 318 } 319 if same == 1 { hit = k } 320 } 321 } 322 k = k + 1 323 } 324 return hit 325} 326 327// --------------------------------------------------------------------------------------------- 328// spec_conformance_run -- THE WATCH SYMBOL. Loads the spec and the suite, resolves every clause 329// citation, optionally drives the shipped compiler over every case, and prints the coverage report 330// and the verdict. Returns the process exit code. 331// --------------------------------------------------------------------------------------------- 332func spec_conformance_run(specp: *u8, suitep: *u8, cc: *u8, do_run: i64, confp: *u8) -> i64 { 333 let max_clauses: i64 = lcf_int_of(confp, "max_clauses\x00" as *u8) 334 let max_cases: i64 = lcf_int_of(confp, "max_cases\x00" as *u8) 335 let headcap: i64 = lcf_int_of(confp, "fail_capture_head_bytes\x00" as *u8) 336 let floor_permil: i64 = lcf_int_of(confp, "min_clause_coverage_permil\x00" as *u8) 337 var confbad: i64 = 0 338 if max_clauses == LCF_MISS { ls_p("CONF-MISSING key=max_clauses\n" as *u8); confbad = 1 } 339 if max_cases == LCF_MISS { ls_p("CONF-MISSING key=max_cases\n" as *u8); confbad = 1 } 340 if headcap == LCF_MISS { ls_p("CONF-MISSING key=fail_capture_head_bytes\n" as *u8); confbad = 1 } 341 if floor_permil == LCF_MISS { ls_p("CONF-MISSING key=min_clause_coverage_permil\n" as *u8); confbad = 1 } 342 if confbad == 1 { 343 ls_p("conf=" as *u8); ls_p(confp); ls_p("\n" as *u8) 344 ls_p("NX-LANGSPEC verdict=UNOBSERVABLE exit=3\n" as *u8) 345 return LS_EXIT_UNOBSERVABLE 346 } 347 348 let slp: *i64 = sys_mmap(LCF_WORD * 2) as *i64 349 let sb: *u8 = sys_read_file(specp, slp) 350 if (sb as i64) == 0 { 351 ls_p("SPEC-UNREADABLE path=" as *u8); ls_p(specp); ls_p("\n" as *u8) 352 ls_p("NX-LANGSPEC verdict=UNOBSERVABLE exit=3\n" as *u8) 353 return LS_EXIT_UNOBSERVABLE 354 } 355 let sn: i64 = slp[0] 356 let ulp: *i64 = sys_mmap(LCF_WORD * 2) as *i64 357 let ub: *u8 = sys_read_file(suitep, ulp) 358 if (ub as i64) == 0 { 359 ls_p("SUITE-UNREADABLE path=" as *u8); ls_p(suitep); ls_p("\n" as *u8) 360 ls_p("NX-LANGSPEC verdict=UNOBSERVABLE exit=3\n" as *u8) 361 return LS_EXIT_UNOBSERVABLE 362 } 363 let un: i64 = ulp[0] 364 365 ls_p("NX-LANGSPEC conformance run\n" as *u8) 366 ls_p("spec=" as *u8); ls_p(specp); ls_p(" bytes=" as *u8); ls_n(sn); ls_p("\n" as *u8) 367 ls_p("suite=" as *u8); ls_p(suitep); ls_p(" bytes=" as *u8); ls_n(un); ls_p("\n" as *u8) 368 ls_p("conf=" as *u8); ls_p(confp); ls_p("\n" as *u8) 369 ls_p("compiler=" as *u8); ls_p(cc); ls_p("\n" as *u8) 370 ls_kv("compiler_present" as *u8, ls_readable(cc)) 371 ls_kv("drives_compiler" as *u8, do_run) 372 373 // ---- parse the spec ---- 374 let L: *i64 = sys_mmap(LCF_WORD * max_clauses * LS_LF) as *i64 375 let f: *i64 = sys_mmap(LCF_WORD * LS_FBUF) as *i64 376 var nl: i64 = 0 377 var clause_over: i64 = 0 378 var spec_malformed: i64 = 0 379 var i: i64 = 0 380 var go: i64 = 1 381 while go == 1 { 382 if i >= sn { go = 0 } else { 383 let e: i64 = ls_eol(sb, sn, i) 384 if ls_starts(sb, i, e, "clause|\x00" as *u8) == 1 { 385 if ls_nfields(sb, i, e) != LS_SPEC_FIELDS { 386 spec_malformed = spec_malformed + 1 387 ls_p("SPEC-MALFORMED fields=" as *u8); ls_n(ls_nfields(sb, i, e)) 388 ls_p(" want=" as *u8); ls_n(LS_SPEC_FIELDS); ls_p(" row=" as *u8) 389 ls_wr(sb, i, e - i); ls_p("\n" as *u8) 390 } else { 391 if nl >= max_clauses { clause_over = clause_over + 1 } else { 392 let base: i64 = nl * LS_LF 393 ls_field(sb, i, e, 1, f); L[base + LS_L_IDOFF] = f[0]; L[base + LS_L_IDLEN] = f[1] 394 ls_field(sb, i, e, 2, f); L[base + LS_L_SECOFF] = f[0]; L[base + LS_L_SECLEN] = f[1] 395 ls_field(sb, i, e, 3, f); L[base + LS_L_TTLOFF] = f[0]; L[base + LS_L_TTLLEN] = f[1] 396 ls_field(sb, i, e, 4, f); L[base + LS_L_TXTOFF] = f[0]; L[base + LS_L_TXTLEN] = f[1] 397 L[base + LS_L_EX] = 0 398 nl = nl + 1 399 } 400 } 401 } 402 i = e + 1 403 } 404 } 405 406 // ---- parse the suite ---- 407 let C: *i64 = sys_mmap(LCF_WORD * max_cases * LS_CF) as *i64 408 var nc: i64 = 0 409 var case_over: i64 = 0 410 var suite_malformed: i64 = 0 411 var unterminated: i64 = 0 412 var incase: i64 = 0 413 var cur: i64 = 0 - 1 414 var srcstart: i64 = 0 415 i = 0 416 go = 1 417 while go == 1 { 418 if i >= un { go = 0 } else { 419 let e: i64 = ls_eol(ub, un, i) 420 if incase == 1 { 421 if ls_slice_is(ub, i, e - i, "endcase\x00" as *u8) == 1 { 422 if cur >= 0 { 423 C[cur * LS_CF + LS_C_SRCOFF] = srcstart 424 C[cur * LS_CF + LS_C_SRCLEN] = i - srcstart 425 } 426 incase = 0 427 cur = 0 - 1 428 } 429 } else { 430 if ls_starts(ub, i, e, "case|\x00" as *u8) == 1 { 431 if ls_nfields(ub, i, e) != LS_CASE_FIELDS { 432 suite_malformed = suite_malformed + 1 433 ls_p("SUITE-MALFORMED fields=" as *u8); ls_n(ls_nfields(ub, i, e)) 434 ls_p(" want=" as *u8); ls_n(LS_CASE_FIELDS); ls_p(" row=" as *u8) 435 ls_wr(ub, i, e - i); ls_p("\n" as *u8) 436 } else { 437 var slot: i64 = 0 - 1 438 if nc >= max_cases { case_over = case_over + 1 } else { slot = nc; nc = nc + 1 } 439 var kind: i64 = 0 440 var srcoff: i64 = 0 441 var srclen: i64 = 0 442 ls_field(ub, i, e, 7, f); srcoff = f[0]; srclen = f[1] 443 if slot >= 0 { 444 let b2: i64 = slot * LS_CF 445 ls_field(ub, i, e, 1, f); C[b2 + LS_C_IDOFF] = f[0]; C[b2 + LS_C_IDLEN] = f[1] 446 ls_field(ub, i, e, 2, f); C[b2 + LS_C_CLOFF] = f[0]; C[b2 + LS_C_CLLEN] = f[1] 447 ls_field(ub, i, e, 3, f) 448 if ls_slice_is(ub, f[0], f[1], "ACCEPT\x00" as *u8) == 1 { kind = LS_KIND_ACCEPT } 449 if ls_slice_is(ub, f[0], f[1], "REFUSE\x00" as *u8) == 1 { kind = LS_KIND_REFUSE } 450 if ls_slice_is(ub, f[0], f[1], "RUN\x00" as *u8) == 1 { kind = LS_KIND_RUN } 451 C[b2 + LS_C_KIND] = kind 452 ls_field(ub, i, e, 4, f); C[b2 + LS_C_FLGOFF] = f[0]; C[b2 + LS_C_FLGLEN] = f[1] 453 ls_field(ub, i, e, 5, f); C[b2 + LS_C_EXPOFF] = f[0]; C[b2 + LS_C_EXPLEN] = f[1] 454 var want: i64 = 0 - 1 455 if kind == LS_KIND_RUN { want = ls_int_after_eq(ub, f[0], f[1]) } 456 ls_field(ub, i, e, 6, f); C[b2 + LS_C_PAROFF] = f[0]; C[b2 + LS_C_PARLEN] = f[1] 457 if kind == LS_KIND_REFUSE { want = ls_int_after_eq(ub, f[0], f[1]) } 458 C[b2 + LS_C_WANT] = want 459 C[b2 + LS_C_GOT] = 0 - 1 460 C[b2 + LS_C_VERD] = 0 461 C[b2 + LS_C_REASON] = LS_R_NONE 462 C[b2 + LS_C_SRCOFF] = srcoff 463 C[b2 + LS_C_SRCLEN] = srclen 464 C[b2 + LS_C_INLINE] = 0 465 } 466 if ls_slice_is(ub, srcoff, srclen, "INLINE\x00" as *u8) == 1 { 467 incase = 1 468 cur = slot 469 srcstart = e + 1 470 if slot >= 0 { C[slot * LS_CF + LS_C_INLINE] = 1 } 471 } 472 } 473 } 474 } 475 i = e + 1 476 } 477 } 478 if incase == 1 { 479 unterminated = 1 480 if cur >= 0 { 481 C[cur * LS_CF + LS_C_SRCOFF] = srcstart 482 C[cur * LS_CF + LS_C_SRCLEN] = un - srcstart 483 } 484 ls_p("SUITE-UNTERMINATED an INLINE case reached end of file with no endcase line\n" as *u8) 485 } 486 487 // ---- traceability: resolve every clause citation ---- 488 var unresolved: i64 = 0 489 var marks: i64 = 0 490 var ci: i64 = 0 491 while ci < nc { 492 let coff: i64 = C[ci * LS_CF + LS_C_CLOFF] 493 let clen: i64 = C[ci * LS_CF + LS_C_CLLEN] 494 var s: i64 = coff 495 var p: i64 = coff 496 var g2: i64 = 1 497 while g2 == 1 { 498 var brk: i64 = 0 499 if p >= coff + clen { brk = 1 } else { if ub[p] == (LS_SEMI as u8) { brk = 1 } } 500 if brk == 1 { 501 let idl: i64 = p - s 502 if idl > 0 { 503 marks = marks + 1 504 let li: i64 = ls_find_clause(L, nl, sb, ub, s, idl) 505 if li >= 0 { L[li * LS_LF + LS_L_EX] = L[li * LS_LF + LS_L_EX] + 1 } else { 506 unresolved = unresolved + 1 507 ls_p("UNRESOLVED-MARK case=" as *u8) 508 ls_wr(ub, C[ci * LS_CF + LS_C_IDOFF], C[ci * LS_CF + LS_C_IDLEN]) 509 ls_p(" clause=" as *u8); ls_wr(ub, s, idl); ls_p("\n" as *u8) 510 } 511 } 512 if p >= coff + clen { g2 = 0 } else { s = p + 1 } 513 } 514 p = p + 1 515 } 516 ci = ci + 1 517 } 518 519 // ---- drive the shipped compiler ---- 520 var npass: i64 = 0 521 var nfail: i64 = 0 522 var nunobs: i64 = 0 523 if do_run == 1 { 524 if ls_readable(cc) == 0 { 525 ls_p("COMPILER-ABSENT path=" as *u8); ls_p(cc) 526 ls_p(" -- nothing was measured. This is NOT a language deviation.\n" as *u8) 527 ls_p("NX-LANGSPEC verdict=UNOBSERVABLE exit=3\n" as *u8) 528 return LS_EXIT_UNOBSERVABLE 529 } 530 sys_mkdir(LS_SCRATCH, LS_SCRATCH_MODE) 531 let pid: i64 = ccg_pid() 532 let p_s: *u8 = ccg_path("/tmp/nx_langspec/c_\x00" as *u8, pid, ".s\x00" as *u8) 533 let p_err: *u8 = ccg_path("/tmp/nx_langspec/c_\x00" as *u8, pid, ".err\x00" as *u8) 534 let p_src: *u8 = ccg_path("/tmp/nx_langspec/c_\x00" as *u8, pid, ".nx\x00" as *u8) 535 let p_elf: *u8 = ccg_path("_build/nxlangspec_\x00" as *u8, pid, ".elf\x00" as *u8) 536 let p_asm: *u8 = ccg_path("_build/nxlangspec_asm_\x00" as *u8, pid, ".elf\x00" as *u8) 537 ci = 0 538 while ci < nc { 539 let b3: i64 = ci * LS_CF 540 let kind: i64 = C[b3 + LS_C_KIND] 541 var verd: i64 = LS_V_UNOBS 542 var reason: i64 = LS_R_NONE 543 var srcp: *u8 = 0 as *u8 544 if C[b3 + LS_C_INLINE] == 1 { 545 if ls_write_slice(p_src, ub, C[b3 + LS_C_SRCOFF], C[b3 + LS_C_SRCLEN]) == 0 { srcp = p_src } 546 else { reason = LS_R_INLINE_WRITE } 547 } else { 548 srcp = ls_dup(ub, C[b3 + LS_C_SRCOFF], C[b3 + LS_C_SRCLEN]) 549 } 550 if (srcp as i64) != 0 { 551 if ls_readable(srcp) == 0 { reason = LS_R_SRC_ABSENT; srcp = 0 as *u8 } 552 } 553 if (srcp as i64) != 0 { 554 var flag: *u8 = 0 as *u8 555 if ls_slice_is(ub, C[b3 + LS_C_FLGOFF], C[b3 + LS_C_FLGLEN], "-\x00" as *u8) == 0 { 556 flag = ls_dup(ub, C[b3 + LS_C_FLGOFF], C[b3 + LS_C_FLGLEN]) 557 } 558 let expp: *u8 = ls_dup(ub, C[b3 + LS_C_EXPOFF], C[b3 + LS_C_EXPLEN]) 559 if kind == LS_KIND_RUN { 560 let rc: i64 = ccg_build(cc, flag, srcp, p_s, p_elf, p_err, p_asm) 561 C[b3 + LS_C_GOT] = rc 562 if rc == 1 { verd = LS_V_FAIL; reason = LS_R_REFUSED_LEGAL } 563 if rc == 2 { verd = LS_V_UNOBS; reason = LS_R_ASSEMBLER } 564 if rc == 3 { verd = LS_V_UNOBS; reason = LS_R_RENAME } 565 if rc == 0 { 566 var ph: *u8 = 0 as *u8 567 if ls_slice_is(ub, C[b3 + LS_C_PAROFF], C[b3 + LS_C_PARLEN], "-\x00" as *u8) == 0 { 568 ph = ls_dup(ub, C[b3 + LS_C_PAROFF], C[b3 + LS_C_PARLEN]) 569 } 570 let ex: i64 = ccg_phase_to(p_elf, ph, p_s, p_err) 571 C[b3 + LS_C_GOT] = ex 572 if ex == C[b3 + LS_C_WANT] { verd = LS_V_PASS } else { verd = LS_V_FAIL; reason = LS_R_WRONG_RUN_EXIT } 573 } 574 } else { 575 if kind == LS_KIND_ACCEPT { 576 let st: i64 = ccg_compile(cc, flag, srcp, p_s, p_err) 577 C[b3 + LS_C_GOT] = wait_status_rc(st) 578 if st != 0 { verd = LS_V_FAIL; reason = LS_R_REFUSED_LEGAL } else { 579 if ccg_file_has(p_s, expp) == 1 { verd = LS_V_PASS } else { verd = LS_V_FAIL; reason = LS_R_ASM_MARKER } 580 } 581 } else { 582 if kind == LS_KIND_REFUSE { 583 let st: i64 = ccg_compile(cc, flag, srcp, p_s, p_err) 584 let ex: i64 = wait_status_rc(st) 585 C[b3 + LS_C_GOT] = ex 586 if st == 0 { verd = LS_V_FAIL; reason = LS_R_ACCEPTED_ILLEGAL } else { 587 if ccg_file_has(p_err, expp) == 0 { verd = LS_V_FAIL; reason = LS_R_WRONG_REASON } else { 588 verd = LS_V_PASS 589 if C[b3 + LS_C_WANT] >= 0 { 590 if ex != C[b3 + LS_C_WANT] { verd = LS_V_FAIL; reason = LS_R_WRONG_CC_EXIT } 591 } 592 } 593 } 594 } else { verd = LS_V_UNOBS; reason = LS_R_BAD_KIND } 595 } 596 } 597 } 598 C[b3 + LS_C_VERD] = verd 599 C[b3 + LS_C_REASON] = reason 600 if verd == LS_V_PASS { npass = npass + 1 } 601 if verd == LS_V_FAIL { nfail = nfail + 1 } 602 if verd == LS_V_UNOBS { nunobs = nunobs + 1 } 603 ls_p("CASE id=" as *u8); ls_wr(ub, C[b3 + LS_C_IDOFF], C[b3 + LS_C_IDLEN]) 604 ls_p(" kind=" as *u8); ls_p(ls_kind_name(kind)) 605 ls_p(" verdict=" as *u8); ls_p(ls_verd_name(verd)) 606 ls_p(" want=" as *u8); ls_n(C[b3 + LS_C_WANT]) 607 ls_p(" got=" as *u8); ls_n(C[b3 + LS_C_GOT]) 608 ls_p(" reason=" as *u8); ls_p(ls_reason_name(reason)) 609 ls_p(" clauses=" as *u8); ls_wr(ub, C[b3 + LS_C_CLOFF], C[b3 + LS_C_CLLEN]) 610 ls_p("\n" as *u8) 611 if verd == LS_V_FAIL { 612 if kind == LS_KIND_ACCEPT { ls_head(p_s, headcap) } else { ls_head(p_err, headcap) } 613 } 614 ci = ci + 1 615 } 616 } 617 618 // ---- coverage: the deliverable ---- 619 var exercised: i64 = 0 620 var k: i64 = 0 621 while k < nl { if L[k * LS_LF + LS_L_EX] > 0 { exercised = exercised + 1 } k = k + 1 } 622 let unexercised: i64 = nl - exercised 623 k = 0 624 while k < nl { 625 if L[k * LS_LF + LS_L_EX] == 0 { 626 ls_p("UNEXERCISED clause=" as *u8); ls_wr(sb, L[k * LS_LF + LS_L_IDOFF], L[k * LS_LF + LS_L_IDLEN]) 627 ls_p(" section=" as *u8); ls_wr(sb, L[k * LS_LF + LS_L_SECOFF], L[k * LS_LF + LS_L_SECLEN]) 628 ls_p(" title=" as *u8); ls_wr(sb, L[k * LS_LF + LS_L_TTLOFF], L[k * LS_LF + LS_L_TTLLEN]) 629 ls_p("\n" as *u8) 630 } 631 k = k + 1 632 } 633 634 var permil: i64 = 0 635 if nl > 0 { permil = exercised * LS_PERMIL / nl } 636 637 ls_kv("clauses_total" as *u8, nl) 638 ls_kv("clauses_over_budget" as *u8, clause_over) 639 ls_kv("clauses_exercised" as *u8, exercised) 640 ls_kv("clauses_unexercised" as *u8, unexercised) 641 ls_kv("clause_partition_sum" as *u8, exercised + unexercised) 642 ls_kv("clause_coverage_permil" as *u8, permil) 643 ls_kv("clause_coverage_floor_permil" as *u8, floor_permil) 644 ls_kv("citation_marks" as *u8, marks) 645 ls_kv("unresolved_marks" as *u8, unresolved) 646 ls_kv("cases_admitted" as *u8, nc) 647 ls_kv("cases_over_budget" as *u8, case_over) 648 ls_kv("cases_malformed" as *u8, suite_malformed) 649 ls_kv("spec_rows_malformed" as *u8, spec_malformed) 650 ls_kv("cases_unterminated" as *u8, unterminated) 651 ls_kv("cases_pass" as *u8, npass) 652 ls_kv("cases_fail" as *u8, nfail) 653 ls_kv("cases_unobservable" as *u8, nunobs) 654 ls_kv("case_partition_sum" as *u8, npass + nfail + nunobs) 655 656 var partition_ok: i64 = 0 657 if do_run == 1 { if npass + nfail + nunobs == nc { partition_ok = 1 } } 658 if do_run == 0 { if npass + nfail + nunobs == 0 { partition_ok = 1 } } 659 ls_kv("case_partition_reconciles" as *u8, partition_ok) 660 661 var coverage_complete: i64 = 1 662 if do_run == 0 { coverage_complete = 0 } 663 if clause_over > 0 { coverage_complete = 0 } 664 if case_over > 0 { coverage_complete = 0 } 665 if nunobs > 0 { coverage_complete = 0 } 666 if partition_ok == 0 { coverage_complete = 0 } 667 ls_kv("coverage_complete" as *u8, coverage_complete) 668 669 var deviation: i64 = 0 670 if nfail > 0 { deviation = 1 } 671 if unresolved > 0 { deviation = 1 } 672 if spec_malformed > 0 { deviation = 1 } 673 if suite_malformed > 0 { deviation = 1 } 674 if unterminated > 0 { deviation = 1 } 675 if permil < floor_permil { 676 deviation = 1 677 ls_p("COVERAGE-BELOW-FLOOR permil=" as *u8); ls_n(permil) 678 ls_p(" floor=" as *u8); ls_n(floor_permil) 679 ls_p(" -- write a case for each UNEXERCISED clause above, or retire the clause. Do NOT lower the floor.\n" as *u8) 680 } 681 682 if nl == 0 { 683 ls_p("NX-LANGSPEC verdict=UNOBSERVABLE exit=3 (the specification declares no clauses)\n" as *u8) 684 return LS_EXIT_UNOBSERVABLE 685 } 686 if nc == 0 { 687 ls_p("NX-LANGSPEC verdict=EMPTY-SUITE exit=4 (zero cases admitted -- a conformance run over the empty set certifies nothing)\n" as *u8) 688 return LS_EXIT_EMPTY 689 } 690 if deviation == 1 { 691 ls_p("NX-LANGSPEC verdict=DEVIATION exit=1\n" as *u8) 692 return LS_EXIT_DEVIATION 693 } 694 if coverage_complete == 0 { 695 ls_p("NX-LANGSPEC verdict=UNOBSERVABLE exit=3\n" as *u8) 696 return LS_EXIT_UNOBSERVABLE 697 } 698 ls_p("NX-LANGSPEC verdict=CONFORMANT exit=0\n" as *u8) 699 return LS_EXIT_CONFORMANT 700} 701 702func ls_usage() -> i64 { 703 ls_p("usage: nx_langspec [run|coverage|clauses|cases] [spec] [suite] [compiler] [conf]\n" as *u8) 704 ls_p(" run drive the shipped compiler over every case and report coverage (default)\n" as *u8) 705 ls_p(" coverage resolve clause citations only, no compiles -- always UNOBSERVABLE, never CONFORMANT\n" as *u8) 706 ls_p(" clauses list every clause id, section and title\n" as *u8) 707 ls_p(" cases list every case id, kind and cited clauses\n" as *u8) 708 ls_p("exit: 0 CONFORMANT 1 DEVIATION 2 usage 3 UNOBSERVABLE 4 EMPTY-SUITE\n" as *u8) 709 return LS_EXIT_USAGE 710} 711 712// listing verbs share the parse but not the run; kept tiny so the report path has ONE owner. 713func ls_list(specp: *u8, suitep: *u8, want_cases: i64) -> i64 { 714 let lp: *i64 = sys_mmap(LCF_WORD * 2) as *i64 715 var path: *u8 = specp 716 if want_cases == 1 { path = suitep } 717 let b: *u8 = sys_read_file(path, lp) 718 if (b as i64) == 0 { ls_p("UNREADABLE path=" as *u8); ls_p(path); ls_p("\n" as *u8); return LS_EXIT_UNOBSERVABLE } 719 let n: i64 = lp[0] 720 var tag: *u8 = "clause|\x00" as *u8 721 if want_cases == 1 { tag = "case|\x00" as *u8 } 722 var rows: i64 = 0 723 var i: i64 = 0 724 var go: i64 = 1 725 while go == 1 { 726 if i >= n { go = 0 } else { 727 let e: i64 = ls_eol(b, n, i) 728 if ls_starts(b, i, e, tag) == 1 { rows = rows + 1; ls_wr(b, i, e - i); ls_p("\n" as *u8) } 729 i = e + 1 730 } 731 } 732 ls_kv("rows" as *u8, rows) 733 if rows == 0 { return LS_EXIT_EMPTY } 734 return LS_EXIT_CONFORMANT 735} 736 737func main(argc: i64, argv: *i64) -> i64 { 738 ccg_anchor_root() 739 var verb: *u8 = "run\x00" as *u8 740 if argc >= 2 { verb = argv[1] as *u8 } 741 var specp: *u8 = LS_SPEC_DEF 742 var suitep: *u8 = LS_SUITE_DEF 743 if argc >= 3 { specp = argv[2] as *u8 } 744 if argc >= 4 { suitep = argv[3] as *u8 } 745 var confp: *u8 = LS_CONF 746 if argc >= 6 { confp = argv[5] as *u8 } 747 let ccbuf: *u8 = sys_mmap(LCF_WORD * LS_FBUF) 748 var cc: *u8 = 0 as *u8 749 if lcf_str_of(confp, "compiler_elf\x00" as *u8, ccbuf, LCF_WORD * LS_FBUF) > 0 { cc = ccbuf } 750 if argc >= 5 { cc = argv[4] as *u8 } 751 if (cc as i64) == 0 { 752 ls_p("CONF-MISSING key=compiler_elf -- the subject is undeclared, so nothing can be measured.\n" as *u8) 753 ls_p("NX-LANGSPEC verdict=UNOBSERVABLE exit=3\n" as *u8) 754 sys_exit(LS_EXIT_UNOBSERVABLE) 755 return LS_EXIT_UNOBSERVABLE 756 } 757 var rc: i64 = LS_EXIT_USAGE 758 if ls_streq(verb, "run\x00" as *u8) == 1 { rc = spec_conformance_run(specp, suitep, cc, 1, confp) } else { 759 if ls_streq(verb, "coverage\x00" as *u8) == 1 { rc = spec_conformance_run(specp, suitep, cc, 0, confp) } else { 760 if ls_streq(verb, "clauses\x00" as *u8) == 1 { rc = ls_list(specp, suitep, 0) } else { 761 if ls_streq(verb, "cases\x00" as *u8) == 1 { rc = ls_list(specp, suitep, 1) } else { rc = ls_usage() } 762 } 763 } 764 } 765 sys_exit(rc) 766 return rc 767}