code wiki / _hdl_build / nx_uiq_atree_lib.nx
nx_uiq_atree_lib.nx source
↩ module page · 533 lines · 23440 B
1// nx_uiq_atree_lib.nx -- F106b ARIA-tree snapshot + Zhang-Shasha tree edit distance (experiential).
2// SOTA axis (banked 07-21): ARIA-tree snapshots + ZSS TED = the deterministic STRUCTURAL regression
3// signal (Playwright aria-snapshot analog), computed sovereignly. A page becomes a role tree
4// (landmarks/headings/links/buttons/inputs/imgs/lists/tables/forms + explicit role= overrides,
5// names from aria-label > alt > inner text), rooted at a virtual document node; TED between two
6// trees = insert/delete/rename cost 1 (classic Zhang-Shasha keyroot DP). TED 0 = structurally
7// identical BY CONSTRUCTION; a removed nav link, a demoted heading, an inserted wrapper each = 1.
8// HONEST ENVELOPE: static source scan; script bodies masked; generic div/span skipped unless role=;
9// node cap 512 + depth cap 64 DECLARED (capped tree -> TED refused, never silently truncated).
10// license_tier: ORIGINAL genealogy: F106b; family nx_uiq_color/contrast/perf/console
11import "nx_uiq_console_lib.nx"
12
13const AT_CAP_NODES: i64 = 512
14const AT_CAP_DEPTH: i64 = 64
15const AT_S_ROLE: i64 = 16
16const AT_S_AUX: i64 = 528
17const AT_S_NOFF: i64 = 1040
18const AT_S_NLEN: i64 = 1552
19const AT_S_PAR: i64 = 2064
20const AT_SLOTS: i64 = 2576
21const AT_FIX_A: *u8 = "<header><h1>Site</h1></header><nav><a href=\"/x\">X</a><a href=\"/y\">Y</a></nav><main><section><h2>S</h2><img src=\"i.png\" alt=\"pic\"></section></main><footer></footer>" as *u8
22const AT_FIX_C: *u8 = "<header><h1>Site</h1></header><nav><a href=\"/x\">X</a></nav><main><section><h2>S</h2><img src=\"i.png\" alt=\"pic\"></section></main><footer></footer>" as *u8
23const AT_FIX_D: *u8 = "<header><h2>Site</h2></header><nav><a href=\"/x\">X</a><a href=\"/y\">Y</a></nav><main><section><h2>S</h2><img src=\"i.png\" alt=\"pic\"></section></main><footer></footer>" as *u8
24const AT_FIX_E: *u8 = "<header><h1>Site</h1></header><nav><a href=\"/x\">X</a><a href=\"/y\">Y</a></nav><main><article><section><h2>S</h2><img src=\"i.png\" alt=\"pic\"></section></article></main><footer></footer>" as *u8
25const AT_FIX_MASK2: *u8 = "<nav><a href=\"/x\">X</a></nav><script>let s='<nav><a href=\"/z\">Z</a></nav>'</script>" as *u8
26
27func at_arr(t: *i64, slot: i64) -> *i64 {
28 let b: i64 = t as i64
29 let o: i64 = slot*8
30 return (b+o) as *i64
31}
32
33func at_min3(a: i64, b: i64, c: i64) -> i64 {
34 var m: i64 = a
35 if b<m { m=b }
36 if c<m { m=c }
37 return m
38}
39
40// find attr value ` name="value"` inside [s,g); out[0]=off out[1]=len; returns 1/0
41func at_attr_val(mk: *u8, s: i64, g: i64, pat: *u8, out: *i64) -> i64 {
42 let o: i64 = cs_find_from(mk, g, s, pat)
43 if o<0 { return 0 }
44 let pl: i64 = cs_len(pat)
45 let vs: i64 = o+pl
46 var e: i64 = vs
47 var d: i64 = 0
48 while d==0 { if e>=g { d=1 } else { let c: i64=mk[e] as i64; if c==34 { d=1 } else { e=e+1 } } }
49 out[0]=vs
50 out[1]=e-vs
51 return 1
52}
53
54// exact tag compare: uiq_word_eq is PREFIX-based (lit exhausted = match) -- a bare "a" would
55// swallow article/aside and "li" would swallow <link>; length must match exactly.
56func at_tag_eq(mk: *u8, off: i64, len: i64, lit: *u8) -> i64 {
57 let ll: i64 = cs_len(lit)
58 if ll!=len { return 0 }
59 return uiq_word_eq(mk, off, len, lit)
60}
61
62// tag -> role id: 1 banner 2 nav 3 main 4 contentinfo 5 aside 6 region 7 article 8 heading(aux=lvl)
63// 9 link 10 button 11 input 12 img 13 table 14 list 15 listitem 16 form 17 dialog 18 combobox
64// 19 textarea 20 explicit-role. out[0]=role out[1]=aux out[2]=container(1/0)
65func at_tagrole(mk: *u8, toff: i64, tlen: i64, out: *i64) -> i64 {
66 out[0]=0
67 out[1]=0
68 out[2]=1
69 if at_tag_eq(mk,toff,tlen,"header" as *u8)==1 { out[0]=1; return 1 }
70 if at_tag_eq(mk,toff,tlen,"nav" as *u8)==1 { out[0]=2; return 1 }
71 if at_tag_eq(mk,toff,tlen,"main" as *u8)==1 { out[0]=3; return 1 }
72 if at_tag_eq(mk,toff,tlen,"footer" as *u8)==1 { out[0]=4; return 1 }
73 if at_tag_eq(mk,toff,tlen,"aside" as *u8)==1 { out[0]=5; return 1 }
74 if at_tag_eq(mk,toff,tlen,"section" as *u8)==1 { out[0]=6; return 1 }
75 if at_tag_eq(mk,toff,tlen,"article" as *u8)==1 { out[0]=7; return 1 }
76 if tlen==2 {
77 let c0: i64 = mk[toff] as i64
78 let c1: i64 = mk[toff+1] as i64
79 if c0==104 { if c1>=49 { if c1<=54 { out[0]=8; out[1]=c1-48; return 1 } } }
80 }
81 if at_tag_eq(mk,toff,tlen,"button" as *u8)==1 { out[0]=10; return 1 }
82 if at_tag_eq(mk,toff,tlen,"input" as *u8)==1 { out[0]=11; out[2]=0; return 1 }
83 if at_tag_eq(mk,toff,tlen,"img" as *u8)==1 { out[0]=12; out[2]=0; return 1 }
84 if at_tag_eq(mk,toff,tlen,"table" as *u8)==1 { out[0]=13; return 1 }
85 if at_tag_eq(mk,toff,tlen,"ul" as *u8)==1 { out[0]=14; return 1 }
86 if at_tag_eq(mk,toff,tlen,"ol" as *u8)==1 { out[0]=14; return 1 }
87 if at_tag_eq(mk,toff,tlen,"li" as *u8)==1 { out[0]=15; return 1 }
88 if at_tag_eq(mk,toff,tlen,"form" as *u8)==1 { out[0]=16; return 1 }
89 if at_tag_eq(mk,toff,tlen,"dialog" as *u8)==1 { out[0]=17; return 1 }
90 if at_tag_eq(mk,toff,tlen,"select" as *u8)==1 { out[0]=18; return 1 }
91 if at_tag_eq(mk,toff,tlen,"textarea" as *u8)==1 { out[0]=19; return 1 }
92 return 0
93}
94
95// build role tree from html buf into tree object t (mmap AT_SLOTS*8). returns node count.
96func at_build(buf: *u8, n: i64, t: *i64) -> i64 {
97 let mk: *u8 = sys_mmap(n+16)
98 cs_mask_scripts(buf, n, mk)
99 let role: *i64 = at_arr(t, AT_S_ROLE)
100 let aux: *i64 = at_arr(t, AT_S_AUX)
101 let noff: *i64 = at_arr(t, AT_S_NOFF)
102 let nlen: *i64 = at_arr(t, AT_S_NLEN)
103 let par: *i64 = at_arr(t, AT_S_PAR)
104 t[1]=0
105 t[2]=buf as i64
106 role[0]=0
107 aux[0]=0
108 noff[0]=0
109 nlen[0]=0
110 par[0]=0-1
111 var nn: i64 = 1
112 let stn: *i64 = sys_mmap(AT_CAP_DEPTH*8) as *i64
113 let sto: *i64 = sys_mmap(AT_CAP_DEPTH*8) as *i64
114 let stl: *i64 = sys_mmap(AT_CAP_DEPTH*8) as *i64
115 stn[0]=0
116 sto[0]=0
117 stl[0]=0
118 var sp: i64 = 1
119 let av: *i64 = sys_mmap(32) as *i64
120 let ro: *i64 = sys_mmap(32) as *i64
121 var i: i64 = 0
122 while i<n {
123 let c: i64 = mk[i] as i64
124 if c!=60 { i=i+1 } else {
125 if i+1>=n { i=i+1 } else {
126 let c1: i64 = mk[i+1] as i64
127 if c1==33 {
128 var isc: i64 = 0
129 if i+3<n { let c2: i64=mk[i+2] as i64; let c3: i64=mk[i+3] as i64; if c2==45 { if c3==45 { isc=1 } } }
130 if isc==1 {
131 let ce: i64 = cs_find_from(mk, n, i+4, "-->" as *u8)
132 if ce<0 { i=n } else { i=ce+3 }
133 } else {
134 var j: i64=i+2
135 var d: i64=0
136 while d==0 { if j>=n { d=1 } else { let cj: i64=mk[j] as i64; if cj==62 { d=1 } else { j=j+1 } } }
137 i=j+1
138 }
139 } else {
140 if c1==47 {
141 let ts: i64 = i+2
142 var e: i64 = ts
143 var d2: i64 = 0
144 while d2==0 { if e>=n { d2=1 } else { let ce2: i64=mk[e] as i64; let isn: i64=cs_namec(ce2); if isn==1 { e=e+1 } else { d2=1 } } }
145 let tl: i64 = e-ts
146 var j2: i64 = e
147 var d3: i64 = 0
148 while d3==0 { if j2>=n { d3=1 } else { let cj2: i64=mk[j2] as i64; if cj2==62 { d3=1 } else { j2=j2+1 } } }
149 var k: i64 = sp-1
150 var fnd: i64 = 0-1
151 while k>=1 { let sm: i64 = cs_span_eq(mk, sto[k], stl[k], ts, tl); if sm==1 { fnd=k; k=0 } else { k=k-1 } }
152 if fnd>=1 { sp=fnd }
153 i=j2+1
154 } else {
155 let isn0: i64 = cs_namec(c1)
156 if isn0==0 { i=i+1 } else {
157 let ts: i64 = i+1
158 var e: i64 = ts
159 var d2: i64 = 0
160 while d2==0 { if e>=n { d2=1 } else { let ce2: i64=mk[e] as i64; let isn: i64=cs_namec(ce2); if isn==1 { e=e+1 } else { d2=1 } } }
161 let tl: i64 = e-ts
162 var g: i64 = e
163 var d3: i64 = 0
164 while d3==0 { if g>=n { d3=1 } else { let cg: i64=mk[g] as i64; if cg==62 { d3=1 } else { g=g+1 } } }
165 ro[0]=0
166 ro[1]=0
167 ro[2]=1
168 let hasrole: i64 = at_attr_val(mk, e, g, " role=\"" as *u8, av)
169 if hasrole==1 { ro[0]=20; ro[1]=0; ro[2]=1 } else { let tr: i64 = at_tagrole(mk, ts, tl, ro); if tr==0 { ro[0]=0 } }
170 var rv: i64 = ro[0]
171 var isa: i64 = 0
172 if at_tag_eq(mk,ts,tl,"a" as *u8)==1 { isa=1 }
173 if isa==1 {
174 if hasrole==0 {
175 let hh: i64 = cs_find_from(mk, g, e, " href" as *u8)
176 if hh>=0 { rv=9; ro[1]=0; ro[2]=1 } else { rv=0 }
177 }
178 }
179 // CHROME-AX-CALIBRATED 2026-07-23 (seq632): a bare <section>/<form> is
180 // GENERIC in the real a11y tree -- region/form require an accessible NAME.
181 // And <header>/<footer> are banner/contentinfo ONLY at document scope;
182 // inside nav/main/aside/section/article the browser demotes them.
183 if hasrole==0 {
184 if rv==6 || rv==16 {
185 var named: i64 = 0
186 let l1: i64 = cs_find_from(mk, g, e, " aria-label=\"" as *u8)
187 if l1>=0 { named=1 }
188 let l2: i64 = cs_find_from(mk, g, e, " aria-labelledby=\"" as *u8)
189 if l2>=0 { named=1 }
190 let l3: i64 = cs_find_from(mk, g, e, " title=\"" as *u8)
191 if l3>=0 { named=1 }
192 if named==0 { rv=0 }
193 }
194 if rv==1 || rv==4 {
195 var scoped: i64 = 0
196 var k2: i64 = sp-1
197 while k2>=1 {
198 let an: i64 = stn[k2]
199 let ar: i64 = role[an]
200 if ar==2 || ar==3 || ar==5 || ar==6 || ar==7 { scoped=1; k2=0 } else { k2=k2-1 }
201 }
202 if scoped==1 { rv=0 }
203 }
204 }
205 if rv>0 {
206 var nmo: i64 = 0
207 var nml: i64 = 0
208 if hasrole==1 { nmo=av[0]; nml=av[1] } else {
209 let hal: i64 = at_attr_val(mk, e, g, " aria-label=\"" as *u8, av)
210 if hal==1 { nmo=av[0]; nml=av[1] } else {
211 if rv==12 {
212 let haa: i64 = at_attr_val(mk, e, g, " alt=\"" as *u8, av)
213 if haa==1 { nmo=av[0]; nml=av[1] }
214 }
215 if rv==8 || rv==9 || rv==10 {
216 var xs: i64 = g+1
217 var d4: i64 = 0
218 while d4==0 { if xs>=n { d4=1 } else { let cx: i64=mk[xs] as i64; if cx==32 || cx==10 || cx==13 || cx==9 { xs=xs+1 } else { d4=1 } } }
219 var xe: i64 = xs
220 var d5: i64 = 0
221 while d5==0 { if xe>=n { d5=1 } else { let cy: i64=mk[xe] as i64; if cy==60 { d5=1 } else { if xe-xs>=40 { d5=1 } else { xe=xe+1 } } } }
222 nmo=xs
223 nml=xe-xs
224 }
225 }
226 }
227 if nn<AT_CAP_NODES {
228 role[nn]=rv
229 aux[nn]=ro[1]
230 noff[nn]=nmo
231 nlen[nn]=nml
232 par[nn]=stn[sp-1]
233 if ro[2]==1 { if sp<AT_CAP_DEPTH { stn[sp]=nn; sto[sp]=ts; stl[sp]=tl; sp=sp+1 } }
234 nn=nn+1
235 } else { t[1]=1 }
236 }
237 i=g+1
238 }
239 }
240 }
241 }
242 }
243 }
244 t[0]=nn
245 return nn
246}
247
248func at_rolename(r: i64, a: i64) -> i64 {
249 if r==0 { uiq_w("document" as *u8); return 0 }
250 if r==1 { uiq_w("banner" as *u8); return 0 }
251 if r==2 { uiq_w("navigation" as *u8); return 0 }
252 if r==3 { uiq_w("main" as *u8); return 0 }
253 if r==4 { uiq_w("contentinfo" as *u8); return 0 }
254 if r==5 { uiq_w("complementary" as *u8); return 0 }
255 if r==6 { uiq_w("region" as *u8); return 0 }
256 if r==7 { uiq_w("article" as *u8); return 0 }
257 if r==8 { uiq_w("heading" as *u8); uiq_pn(a); return 0 }
258 if r==9 { uiq_w("link" as *u8); return 0 }
259 if r==10 { uiq_w("button" as *u8); return 0 }
260 if r==11 { uiq_w("input" as *u8); return 0 }
261 if r==12 { uiq_w("img" as *u8); return 0 }
262 if r==13 { uiq_w("table" as *u8); return 0 }
263 if r==14 { uiq_w("list" as *u8); return 0 }
264 if r==15 { uiq_w("listitem" as *u8); return 0 }
265 if r==16 { uiq_w("form" as *u8); return 0 }
266 if r==17 { uiq_w("dialog" as *u8); return 0 }
267 if r==18 { uiq_w("combobox" as *u8); return 0 }
268 if r==19 { uiq_w("textarea" as *u8); return 0 }
269 uiq_w("role" as *u8)
270 return 0
271}
272
273// print indented snapshot (skips the virtual root)
274func at_snap(t: *i64) -> i64 {
275 let nn: i64 = t[0]
276 let role: *i64 = at_arr(t, AT_S_ROLE)
277 let aux: *i64 = at_arr(t, AT_S_AUX)
278 let noff: *i64 = at_arr(t, AT_S_NOFF)
279 let nlen: *i64 = at_arr(t, AT_S_NLEN)
280 let par: *i64 = at_arr(t, AT_S_PAR)
281 let pb: *u8 = t[2] as *u8
282 var i: i64 = 1
283 while i<nn {
284 var d: i64 = 0
285 var p: i64 = par[i]
286 while p>0 { d=d+1; p=par[p] }
287 var k: i64 = 0
288 while k<d { uiq_w(" " as *u8); k=k+1 }
289 let r: i64 = role[i]
290 let a: i64 = aux[i]
291 at_rolename(r, a)
292 let nl: i64 = nlen[i]
293 if nl>0 {
294 uiq_w(" \"" as *u8)
295 let no: i64 = noff[i]
296 uiq_wslice(pb, no, nl)
297 uiq_w("\"" as *u8)
298 }
299 uiq_w("\n" as *u8)
300 i=i+1
301 }
302 return 0
303}
304
305func at_names_eq(ba: *u8, oa: i64, la: i64, bb: *u8, ob: i64, lb: i64) -> i64 {
306 if la!=lb { return 0 }
307 var k: i64 = 0
308 while k<la { let x: i64=ba[oa+k] as i64; let y: i64=bb[ob+k] as i64; if x!=y { return 0 } k=k+1 }
309 return 1
310}
311
312// label cost between node x of ta and node y of tb: 0 identical, 1 rename
313func at_lcost(ta: *i64, x: i64, tb: *i64, y: i64) -> i64 {
314 let ra: *i64 = at_arr(ta, AT_S_ROLE)
315 let rb: *i64 = at_arr(tb, AT_S_ROLE)
316 if ra[x]!=rb[y] { return 1 }
317 let aa: *i64 = at_arr(ta, AT_S_AUX)
318 let ab: *i64 = at_arr(tb, AT_S_AUX)
319 if aa[x]!=ab[y] { return 1 }
320 let noa: *i64 = at_arr(ta, AT_S_NOFF)
321 let nla: *i64 = at_arr(ta, AT_S_NLEN)
322 let nob: *i64 = at_arr(tb, AT_S_NOFF)
323 let nlb: *i64 = at_arr(tb, AT_S_NLEN)
324 let pa: *u8 = ta[2] as *u8
325 let pbb: *u8 = tb[2] as *u8
326 let eq: i64 = at_names_eq(pa, noa[x], nla[x], pbb, nob[y], nlb[y])
327 if eq==1 { return 0 }
328 return 1
329}
330
331// compute postorder po[], node->po poidx[], leftmost-leaf lpo[] (po coords), keyroot kr[] flags
332func at_order(t: *i64, po: *i64, poidx: *i64, lpo: *i64, kr: *i64) -> i64 {
333 let nn: i64 = t[0]
334 let par: *i64 = at_arr(t, AT_S_PAR)
335 let kcnt: *i64 = sys_mmap(nn*8+16) as *i64
336 let koff: *i64 = sys_mmap(nn*8+16) as *i64
337 let kfil: *i64 = sys_mmap(nn*8+16) as *i64
338 let kids: *i64 = sys_mmap(nn*8+16) as *i64
339 var i: i64 = 0
340 while i<nn { kcnt[i]=0; i=i+1 }
341 i=1
342 while i<nn { let p: i64=par[i]; kcnt[p]=kcnt[p]+1; i=i+1 }
343 koff[0]=0
344 i=1
345 while i<nn { koff[i]=koff[i-1]+kcnt[i-1]; i=i+1 }
346 i=0
347 while i<nn { kfil[i]=koff[i]; i=i+1 }
348 i=1
349 while i<nn { let p2: i64=par[i]; let s: i64=kfil[p2]; kids[s]=i; kfil[p2]=s+1; i=i+1 }
350 let stn: *i64 = sys_mmap(nn*8+16) as *i64
351 let stc: *i64 = sys_mmap(nn*8+16) as *i64
352 stn[0]=0
353 stc[0]=0
354 var sp: i64 = 0
355 var pn: i64 = 0
356 while sp>=0 {
357 let nd: i64 = stn[sp]
358 let cu: i64 = stc[sp]
359 if cu<kcnt[nd] {
360 stc[sp]=cu+1
361 let ko: i64 = koff[nd]
362 let ch: i64 = kids[ko+cu]
363 sp=sp+1
364 stn[sp]=ch
365 stc[sp]=0
366 } else {
367 po[pn]=nd
368 poidx[nd]=pn
369 pn=pn+1
370 sp=sp-1
371 }
372 }
373 var k: i64 = 0
374 while k<nn {
375 let nd2: i64 = po[k]
376 if kcnt[nd2]==0 { lpo[k]=k } else {
377 let ko2: i64 = koff[nd2]
378 let fc: i64 = kids[ko2]
379 let fpi: i64 = poidx[fc]
380 lpo[k]=lpo[fpi]
381 }
382 k=k+1
383 }
384 let seen: *i64 = sys_mmap(nn*8+16) as *i64
385 var q: i64 = nn-1
386 while q>=0 {
387 let lv: i64 = lpo[q]
388 if seen[lv]==0 { kr[q]=1; seen[lv]=1 } else { kr[q]=0 }
389 q=q-1
390 }
391 return pn
392}
393
394// Zhang-Shasha TED. returns edit distance, or -1 if either tree hit the node cap.
395func at_ted(ta: *i64, tb: *i64) -> i64 {
396 if ta[1]==1 { return 0-1 }
397 if tb[1]==1 { return 0-1 }
398 let na: i64 = ta[0]
399 let nb: i64 = tb[0]
400 let poa: *i64 = sys_mmap(na*8+16) as *i64
401 let pia: *i64 = sys_mmap(na*8+16) as *i64
402 let la: *i64 = sys_mmap(na*8+16) as *i64
403 let ka: *i64 = sys_mmap(na*8+16) as *i64
404 let pob: *i64 = sys_mmap(nb*8+16) as *i64
405 let pib: *i64 = sys_mmap(nb*8+16) as *i64
406 let lb: *i64 = sys_mmap(nb*8+16) as *i64
407 let kb: *i64 = sys_mmap(nb*8+16) as *i64
408 at_order(ta, poa, pia, la, ka)
409 at_order(tb, pob, pib, lb, kb)
410 let W: i64 = nb+2
411 let fd: *i64 = sys_mmap((na+2)*W*8+16) as *i64
412 let td: *i64 = sys_mmap(na*nb*8+16) as *i64
413 var ki: i64 = 0
414 while ki<na {
415 if ka[ki]==1 {
416 var kj: i64 = 0
417 while kj<nb {
418 if kb[kj]==1 {
419 let lia: i64 = la[ki]
420 let ljb: i64 = lb[kj]
421 fd[0]=0
422 var i: i64 = lia
423 while i<=ki { let r1: i64=(i-lia+1)*W; let r0: i64=(i-lia)*W; fd[r1]=fd[r0]+1; i=i+1 }
424 var j: i64 = ljb
425 while j<=kj { let c1: i64=j-ljb+1; fd[c1]=fd[c1-1]+1; j=j+1 }
426 i=lia
427 while i<=ki {
428 let ai: i64 = i-lia+1
429 j=ljb
430 while j<=kj {
431 let bj: i64 = j-ljb+1
432 var both: i64 = 0
433 if la[i]==lia { if lb[j]==ljb { both=1 } }
434 if both==1 {
435 let nxa: i64 = poa[i]
436 let nxb: i64 = pob[j]
437 let cc: i64 = at_lcost(ta, nxa, tb, nxb)
438 let i1: i64 = (ai-1)*W+bj
439 let i2: i64 = ai*W+bj-1
440 let i3: i64 = (ai-1)*W+bj-1
441 let d1: i64 = fd[i1]+1
442 let d2: i64 = fd[i2]+1
443 let d3: i64 = fd[i3]+cc
444 let mv: i64 = at_min3(d1, d2, d3)
445 let i0: i64 = ai*W+bj
446 fd[i0]=mv
447 let ti: i64 = i*nb+j
448 td[ti]=mv
449 } else {
450 let pai: i64 = la[i]-lia
451 let pbj: i64 = lb[j]-ljb
452 let ib: i64 = pai*W+pbj
453 let base0: i64 = fd[ib]
454 let ti: i64 = i*nb+j
455 let tdv: i64 = td[ti]
456 let i1: i64 = (ai-1)*W+bj
457 let i2: i64 = ai*W+bj-1
458 let d1: i64 = fd[i1]+1
459 let d2: i64 = fd[i2]+1
460 let d3: i64 = base0+tdv
461 let mv: i64 = at_min3(d1, d2, d3)
462 let i0: i64 = ai*W+bj
463 fd[i0]=mv
464 }
465 j=j+1
466 }
467 i=i+1
468 }
469 }
470 kj=kj+1
471 }
472 }
473 ki=ki+1
474 }
475 let fin: i64 = (na-1)*nb + nb-1
476 return td[fin]
477}
478
479// build both trees from html buffers and return TED (-1 = capped)
480func at_ted_html(ba: *u8, na: i64, bb: *u8, nb: i64) -> i64 {
481 let ta: *i64 = sys_mmap(AT_SLOTS*8) as *i64
482 let tb: *i64 = sys_mmap(AT_SLOTS*8) as *i64
483 at_build(ba, na, ta)
484 at_build(bb, nb, tb)
485 return at_ted(ta, tb)
486}
487
488// selftest: exact node counts + TED oracle 0/1/1/1 + symmetry + script-mask. 0 = GREEN.
489func at_selftest() -> i64 {
490 var f: i64 = 0
491 let fa: *u8 = AT_FIX_A
492 let fal: i64 = cs_len(fa)
493 let ta: *i64 = sys_mmap(AT_SLOTS*8) as *i64
494 let nA: i64 = at_build(fa, fal, ta)
495 if nA!=10 { f=f+1 }
496 let lab: *u8 = "<section aria-label=\"L\"><h2>S</h2></section>" as *u8
497 let labl: i64 = cs_len(lab)
498 let tl2: *i64 = sys_mmap(AT_SLOTS*8) as *i64
499 let nL: i64 = at_build(lab, labl, tl2)
500 if nL!=3 { f=f+1 }
501 let bare: *u8 = "<section><h2>S</h2></section>" as *u8
502 let barel: i64 = cs_len(bare)
503 let tb2: *i64 = sys_mmap(AT_SLOTS*8) as *i64
504 let nB: i64 = at_build(bare, barel, tb2)
505 if nB!=2 { f=f+1 }
506 let scp: *u8 = "<article><footer>x</footer></article>" as *u8
507 let scpl: i64 = cs_len(scp)
508 let tc2: *i64 = sys_mmap(AT_SLOTS*8) as *i64
509 let nC2: i64 = at_build(scp, scpl, tc2)
510 if nC2!=2 { f=f+1 }
511 let t0: i64 = at_ted_html(fa, fal, fa, fal)
512 if t0!=0 { f=f+1 }
513 let fc: *u8 = AT_FIX_C
514 let fcl: i64 = cs_len(fc)
515 let t1: i64 = at_ted_html(fa, fal, fc, fcl)
516 if t1!=1 { f=f+1 }
517 let t1s: i64 = at_ted_html(fc, fcl, fa, fal)
518 if t1s!=1 { f=f+1 }
519 let fdx: *u8 = AT_FIX_D
520 let fdl: i64 = cs_len(fdx)
521 let t2: i64 = at_ted_html(fa, fal, fdx, fdl)
522 if t2!=1 { f=f+1 }
523 let fe: *u8 = AT_FIX_E
524 let fel: i64 = cs_len(fe)
525 let t3: i64 = at_ted_html(fa, fal, fe, fel)
526 if t3!=1 { f=f+1 }
527 let fm: *u8 = AT_FIX_MASK2
528 let fml: i64 = cs_len(fm)
529 let tm: *i64 = sys_mmap(AT_SLOTS*8) as *i64
530 let nM: i64 = at_build(fm, fml, tm)
531 if nM!=3 { f=f+1 }
532 return f
533}