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}