code wiki / _hdl_build / nx_canoncat.nx

nx_canoncat.nx source

↩ module page · 249 lines · 12307 B

1// nx_canoncat -- concatenate N canon files into one, renumbering feature part references. 2// 3// ★THE ENTIRE "PERFECT ONE UNIT, THEN AGGREGATE" MECHANISM IS THIS ORGAN (plan rung S4). A skull, a 4// hand, a torso are each authored and gated as their OWN canon; composition into a body is a cat. 5// That is only true because of one discipline enforced here: 6// 7// P and R rows pass through BYTE-VERBATIM. This organ does not parse them beyond the leading 8// letter, so when the canon grows fields (P grew mat, then rotZ, then rotY) concatenation keeps 9// working without an edit here -- an organ that re-emitted parsed fields would silently STRIP any 10// field added after it was written, which is exactly how format evolution corrupts a pipeline. 11// 12// F rows are the one exception, because they carry the ONLY cross-row reference in the format: a 13// part INDEX, where parts are numbered by order of P appearance. File k's F rows are shifted by 14// the P count of files 0..k-1. Everything after the index is passed through verbatim for the same 15// reason as above. 16// 17// Fail-closed on purpose: 18// - an F row referencing a part its OWN file never declared is a corrupt canon -> refuse (exit 5), 19// because shifting a dangling index would turn one file's corruption into another file's feature. 20// - an unknown row type -> refuse (exit 6). A row this organ cannot classify is a row whose 21// renumbering rule is UNKNOWN; passing it through silently would be a guess with a file format. 22// 23// usage: nx_canoncat <out.canon> <in1.canon> [in2.canon ...] 24// nx_canoncat selftest 25import "nx_gate_verdict.nx" 26 27const CC_MAXIN: i64 = 8388608 // 8MB per input canon -- far above any real canon, refuses beyond 28const CC_MAXOUT: i64 = 67108864 29 30func cc_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 31func cc_pn(v: i64) -> i64 { 32 let b: *u8 = sys_mmap(32); var x: i64=v; var ng: i64=0 33 if x<0 { ng=1; x=0-x } 34 var i: i64=31 35 if x==0 { b[i]=48 as u8; i=i-1 } 36 while x>0 { b[i]=(48+x%10) as u8; x=x/10; i=i-1 } 37 if ng==1 { b[i]=45 as u8; i=i-1 } 38 sys_write(1,(b as i64 + i + 1) as *u8, 31-i); return 0 39} 40func cc_streq(a: *u8, b: *u8) -> i64 { 41 var i: i64=0; var go: i64=1; var eq: i64=1 42 while go==1 { if a[i]!=b[i] { eq=0; go=0 } else { if a[i]==(0 as u8) { go=0 } else { i=i+1 } } } 43 return eq 44} 45// append v as decimal digits into out at pos 46func cc_wnum(out: *u8, pos: *i64, v: i64) -> i64 { 47 var x: i64 = v 48 if x < 0 { out[pos[0]] = 45 as u8; pos[0] = pos[0]+1; x = 0-x } 49 let t: *u8 = sys_mmap(32); var k: i64 = 0 50 if x == 0 { t[0] = 48 as u8; k = 1 } 51 while x > 0 { t[k] = (48+x%10) as u8; x = x/10; k = k+1 } 52 var q: i64 = k-1 53 while q >= 0 { out[pos[0]] = t[q]; pos[0] = pos[0]+1; q = q-1 } 54 return 0 55} 56// concatenate one input buffer into out. pbase = part offset for this file. Returns this file's own 57// P count, or negative on refusal: -5 dangling F reference, -6 unknown row type. 58func cc_one(inb: *u8, n: i64, out: *u8, opos: *i64, pbase: i64) -> i64 { 59 var pcount: i64 = 0 60 var i: i64 = 0 61 while i < n { 62 // find the line end (le = newline index, or n for an unterminated last line, which is 63 // normalised to newline-terminated on output) 64 var le: i64 = i 65 var scan: i64 = 1 66 while scan == 1 { 67 if le >= n { scan = 0 } else { 68 if inb[le] == (10 as u8) { scan = 0 } else { le = le + 1 } 69 } 70 } 71 let c: i64 = inb[i] as i64 72 if c == 80 { pcount = pcount + 1 } // 'P' 73 var handled: i64 = 0 74 if c == 70 { // 'F' -- rewrite the part index, field 2 75 handled = 1 76 var k: i64 = i + 1 77 var sk: i64 = 1 78 while sk == 1 { 79 if k >= le { sk = 0 } else { if inb[k] == (32 as u8) { k = k + 1 } else { sk = 0 } } 80 } 81 var v: i64 = 0 82 var got: i64 = 0 83 var neg: i64 = 0 84 if k < le { if inb[k] == (45 as u8) { neg = 1; k = k + 1 } } 85 var dg: i64 = 1 86 while dg == 1 { 87 if k >= le { dg = 0 } else { 88 let d: i64 = inb[k] as i64 89 if d >= 48 { if d <= 57 { v = v*10 + (d-48); got = 1; k = k + 1 } else { dg = 0 } } 90 else { dg = 0 } 91 } 92 } 93 if got == 0 { return 0-6 } 94 if neg == 1 { v = 0 - v } 95 // ★the dangling check uses THIS FILE's own part count seen SO FAR -- a feature must sit on 96 // a part already declared above it, the same implicit contract by which R rows attach to 97 // the most recent P. 98 if v < 0 { return 0-5 } 99 if v >= pcount { return 0-5 } 100 out[opos[0]] = 70 as u8; opos[0] = opos[0] + 1 101 out[opos[0]] = 32 as u8; opos[0] = opos[0] + 1 102 cc_wnum(out, opos, v + pbase) 103 var m: i64 = k 104 while m < le { out[opos[0]] = inb[m]; opos[0] = opos[0] + 1; m = m + 1 } 105 out[opos[0]] = 10 as u8; opos[0] = opos[0] + 1 106 } 107 if handled == 0 { 108 var ok: i64 = 0 109 if c == 80 { ok = 1 } // P 110 if c == 82 { ok = 1 } // R 111 if c == 10 { ok = 1 } // blank line 112 if c == 35 { ok = 1 } // '#' comment, tolerated verbatim 113 if ok == 0 { return 0-6 } 114 var m2: i64 = i 115 while m2 < le { out[opos[0]] = inb[m2]; opos[0] = opos[0] + 1; m2 = m2 + 1 } 116 out[opos[0]] = 10 as u8; opos[0] = opos[0] + 1 117 } 118 i = le + 1 119 } 120 return pcount 121} 122func cc_cat(outpath: *u8, files: *i64, nf: i64) -> i64 { 123 let out: *u8 = sys_mmap(CC_MAXOUT) 124 let opos: *i64 = sys_mmap(16) as *i64 125 opos[0] = 0 126 var pbase: i64 = 0 127 var f: i64 = 0 128 while f < nf { 129 let szp: *i64 = sys_mmap(16) as *i64 130 let inb: *u8 = sys_read_file(files[f] as *u8, szp) 131 if (inb as i64) == 0 { return 0-3 } 132 if szp[0] > CC_MAXIN { return 0-4 } 133 let pc: i64 = cc_one(inb, szp[0], out, opos, pbase) 134 if pc < 0 { return pc } 135 pbase = pbase + pc 136 f = f + 1 137 } 138 let fd: i64 = sys_openat_wr(outpath, 420) 139 if fd < 0 { return 0-7 } 140 sys_write(fd, out, opos[0]) 141 sys_close(fd) 142 return pbase 143} 144func cc_gate() -> i64 { 145 let ctr: *i64 = gv_ctr() 146 gv_head("nx_canoncat selftest -- composition is a cat, and it refuses to guess" as *u8) 147 // fixture A: 2 parts, one F on part 1, P rows carrying DIFFERENT field counts (format evolution) 148 let fa: *u8 = "P 0 0 0 0 0 0\nR 470 0 0 35 45\nP 1 90 48 40 -41 0 15 30\nR 500 50 -6 38 51\nF 1 706 62 62 34 14\n" as *u8 149 // fixture B: 1 part, one F on part 0 150 let fb: *u8 = "P 1 0 0 0 0 0\nR 100 0 0 20 20\nF 0 200 30 60 20 10\n" as *u8 151 let pa: *u8 = "/tmp/cc_a.canon" as *u8 152 let pb: *u8 = "/tmp/cc_b.canon" as *u8 153 let po: *u8 = "/tmp/cc_o.canon" as *u8 154 var la: i64 = 0 155 while fa[la] != (0 as u8) { la = la + 1 } 156 var lb: i64 = 0 157 while fb[lb] != (0 as u8) { lb = lb + 1 } 158 let fd1: i64 = sys_openat_wr(pa, 420) 159 sys_write(fd1, fa, la); sys_close(fd1) 160 let fd2: i64 = sys_openat_wr(pb, 420) 161 sys_write(fd2, fb, lb); sys_close(fd2) 162 // ★★T1 IDENTITY IS BYTE-IDENTITY. cat of ONE file must reproduce it exactly -- this is the tooth 163 // that proves P/R passthrough is verbatim, and it holds for every field the format will ever grow. 164 let fl: *i64 = sys_mmap(8*8) as *i64 165 fl[0] = pa as i64 166 var t1: i64 = 0 167 if cc_cat(po, fl, 1) == 2 { 168 let szp: *i64 = sys_mmap(16) as *i64 169 let ob: *u8 = sys_read_file(po, szp) 170 if szp[0] == la { 171 var same: i64 = 1 172 var i: i64 = 0 173 while i < la { if ob[i] != fa[i] { same = 0; i = la } else { i = i + 1 } } 174 t1 = same 175 } 176 } 177 gv_check("T1 IDENTITY: cat of one file is BYTE-IDENTICAL, so passthrough survives every future field the format grows" as *u8, t1, ctr) 178 // ★★T2 THE SHIFT. A+B: B's F row must reference part 2 (0 + A's 2 parts); A's F row stays 1. 179 fl[0] = pa as i64 180 fl[1] = pb as i64 181 var t2: i64 = 0 182 if cc_cat(po, fl, 2) == 3 { 183 let szp2: *i64 = sys_mmap(16) as *i64 184 let ob2: *u8 = sys_read_file(po, szp2) 185 // count: "F 1 " present once, "F 2 " present once, "F 0 " absent 186 var f1: i64 = 0 187 var f2: i64 = 0 188 var f0: i64 = 0 189 var i2: i64 = 0 190 while i2 < szp2[0]-3 { 191 if ob2[i2] == (70 as u8) { if ob2[i2+1] == (32 as u8) { 192 if ob2[i2+2] == (49 as u8) { if ob2[i2+3] == (32 as u8) { f1 = f1 + 1 } } 193 if ob2[i2+2] == (50 as u8) { if ob2[i2+3] == (32 as u8) { f2 = f2 + 1 } } 194 if ob2[i2+2] == (48 as u8) { if ob2[i2+3] == (32 as u8) { f0 = f0 + 1 } } 195 } } 196 i2 = i2 + 1 197 } 198 if f1 == 1 { if f2 == 1 { if f0 == 0 { t2 = 1 } } } 199 } 200 gv_check("T2 THE SHIFT: the second file's feature lands on part 2, not part 0 -- the one cross-row reference is renumbered and nothing else is touched" as *u8, t2, ctr) 201 // ★T3 REFUSES a dangling feature: an F referencing a part its file never declared 202 let fc: *u8 = "P 0 0 0 0 0 0\nF 3 100 10 10 10 10\n" as *u8 203 var lc: i64 = 0 204 while fc[lc] != (0 as u8) { lc = lc + 1 } 205 let fd3: i64 = sys_openat_wr("/tmp/cc_c.canon" as *u8, 420) 206 sys_write(fd3, fc, lc); sys_close(fd3) 207 fl[0] = "/tmp/cc_c.canon" as *u8 as i64 208 var t3: i64 = 0 209 if cc_cat(po, fl, 1) == 0-5 { t3 = 1 } 210 gv_check("T3 REFUSES A DANGLING FEATURE: shifting a corrupt index would turn one file's corruption into another file's feature, so it refuses instead" as *u8, t3, ctr) 211 // ★T4 REFUSES an unknown row type -- a row with no renumbering rule is never passed through on a guess 212 let fdt: *u8 = "P 0 0 0 0 0 0\nQ 1 2 3\n" as *u8 213 var ld: i64 = 0 214 while fdt[ld] != (0 as u8) { ld = ld + 1 } 215 let fd4: i64 = sys_openat_wr("/tmp/cc_d.canon" as *u8, 420) 216 sys_write(fd4, fdt, ld); sys_close(fd4) 217 fl[0] = "/tmp/cc_d.canon" as *u8 as i64 218 var t4: i64 = 0 219 if cc_cat(po, fl, 1) == 0-6 { t4 = 1 } 220 gv_check("T4 REFUSES AN UNKNOWN ROW: a row this organ cannot classify has an UNKNOWN renumbering rule, and a silent passthrough would be a guess with a file format" as *u8, t4, ctr) 221 // ★T5 REFUSES a missing input -- composition never invents an empty contribution 222 fl[0] = "/tmp/cc_missing.canon" as *u8 as i64 223 var t5: i64 = 0 224 if cc_cat(po, fl, 1) == 0-3 { t5 = 1 } 225 gv_check("T5 REFUSES A MISSING INPUT rather than composing an empty contribution" as *u8, t5, ctr) 226 return gv_verdict("CANONCAT-GATE" as *u8, ctr, "compose canons by cat; verbatim passthrough; the one reference renumbered; refuses to guess" as *u8) 227} 228func main(argc: i64, argv: *i64) -> i64 { 229 if argc >= 2 { 230 if cc_streq(argv[1] as *u8, "selftest" as *u8) == 1 { return cc_gate() } 231 if argc >= 3 { 232 let fl: *i64 = sys_mmap(64*8) as *i64 233 var nf: i64 = 0 234 var i: i64 = 2 235 while i < argc { fl[nf] = argv[i]; nf = nf + 1; i = i + 1 } 236 let r: i64 = cc_cat(argv[1] as *u8, fl, nf) 237 if r < 0 { 238 cc_puts("{\x22organ\x22:\x22nx_canoncat\x22,\x22verdict\x22:\x22REFUSED\x22,\x22rc\x22:" as *u8); cc_pn(r) 239 cc_puts(",\x22why\x22:\x22-3 unreadable input, -5 dangling F part reference, -6 unknown row type, -4 oversized input, -7 cannot write output\x22}\n" as *u8) 240 return 0 - r 241 } 242 cc_puts("{\x22organ\x22:\x22nx_canoncat\x22,\x22parts_total\x22:" as *u8); cc_pn(r) 243 cc_puts(",\x22reads\x22:\x22one canon from many. P and R rows pass through byte-verbatim so the organ survives format growth; F part indices are shifted by the preceding files' part counts -- the format's only cross-row reference, renumbered, nothing else touched.\x22}\n" as *u8) 244 return 0 245 } 246 } 247 cc_puts("{\x22organ\x22:\x22nx_canoncat\x22,\x22usage\x22:\x22nx_canoncat <out.canon> <in1> [in2 ...] | nx_canoncat selftest\x22}\n" as *u8) 248 return 0 249}