nx_glsl_tok_lib.nx source
↩ module page · 1013 lines · 46704 B
1// nx_glsl_tok_lib.nx -- THE SHADER TOKEN RULER (gameengine S12c accept-rule instrument, 2026-09-05).
2//
3// WHY: the cast port's pre-declared accept rule says the IR-emitted GLSL must match the hand-written MVS/MFS at ZERO
4// normalised-token distance before its WGSL twin is trusted. A byte diff cannot be that ruler (the hand shader has
5// formatting freedom, comments, `.5` for `0.5`, `br/=nr` for `br=br/nr`) and a string-run ruler cannot either (it
6// has no grammar). This lib is ONE tokenizer + ONE canonicaliser + ONE compound-assignment expander + ONE differ,
7// composed in-process by nx_glsl_tokdiff (the program) and nx_glsl_tokdiff_gate (planted fixtures, both directions).
8// NORMALISATIONS, each named: whitespace and comments dropped; numeric literals canonicalised (`.5`->`0.5`, `1.`->`1.0`,
9// trailing f/F/u/U dropped); `x op= y` expanded to `x = x op y` for op in + - * / where x is an lvalue of the grammar
10// ident ( '.' ident | '[' ... ']' )* -- any other shape is left literal and COUNTED (expand_refused=), never guessed.
11// Everything else must match token for token: a renamed identifier, a moved parenthesis, a changed constant is a DIFF,
12// and the first differing token is named with both spellings.
13// Token = 3 words (start, len, kind); kinds 1 ident, 2 number, 3 operator/punct. Buffers are sized from the input
14// (one token cannot be shorter than one byte, so 3*n words is a bound that cannot overflow -- no guessed cap).
15// license_tier: ORIGINAL No hw writes (Rule 26).
16import "nx_syscalls.nx"
17
18const GT_K_IDENT: i64 = 1
19const GT_K_NUM: i64 = 2
20const GT_K_OP: i64 = 3
21const GT_TW: i64 = 3
22const GT_R_TOKA: i64 = 0
23const GT_R_TOKB: i64 = 1
24const GT_R_EXPA: i64 = 2
25const GT_R_EXPB: i64 = 3
26const GT_R_FIRSTDIFF: i64 = 4
27const GT_R_VERDICT: i64 = 5
28const GT_R_REFUSED: i64 = 6
29const GT_R_SLOTS: i64 = 20
30const GT_V_IDENTICAL: i64 = 0
31const GT_V_DIFFERS: i64 = 1
32const GT_V_UNREADABLE: i64 = 2
33const GT_CANON_CAP: i64 = 512
34const GT_NUMBUF: i64 = 32
35const C_TAB: i64 = 9
36const C_NL: i64 = 10
37const C_CR: i64 = 13
38const C_SP: i64 = 32
39const C_BANG: i64 = 33
40const C_AMP: i64 = 38
41const C_LPAR: i64 = 40
42const C_RPAR: i64 = 41
43const C_STAR: i64 = 42
44const C_PLUS: i64 = 43
45const C_MINUS: i64 = 45
46const C_DOT: i64 = 46
47const C_SLASH: i64 = 47
48const C_0: i64 = 48
49const C_9: i64 = 57
50const C_LT: i64 = 60
51const C_EQ: i64 = 61
52const C_GT: i64 = 62
53const C_A: i64 = 65
54const C_E: i64 = 69
55const C_F: i64 = 70
56const C_U: i64 = 85
57const C_Z: i64 = 90
58const C_LBR: i64 = 91
59const C_RBR: i64 = 93
60const C_USCORE: i64 = 95
61const C_a: i64 = 97
62const C_e: i64 = 101
63const C_f: i64 = 102
64const C_u: i64 = 117
65const C_z: i64 = 122
66const C_BAR: i64 = 124
67
68func gt_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
69func gt_fputs(fd: i64, s: *u8) -> i64 { sys_write(fd, s, gt_slen(s)); return 0 }
70func gt_fputn(fd: i64, v: i64) -> i64 {
71 let t: *u8 = sys_mmap(GT_NUMBUF)
72 let o: *u8 = sys_mmap(GT_NUMBUF)
73 var m: i64 = v
74 if m < 0 { sys_write(fd, "-" as *u8, 1); m = 0 - m }
75 var k: i64 = 0
76 if m == 0 { t[0] = C_0 as u8; k = 1 }
77 while m > 0 { t[k] = (C_0 + (m % 10)) as u8; m = m / 10; k = k + 1 }
78 var i: i64 = 0
79 while i < k { o[i] = t[k - 1 - i]; i = i + 1 }
80 sys_write(fd, o, k)
81 return 0
82}
83func gt_isalpha(c: i64) -> i64 {
84 if c >= C_A { if c <= C_Z { return 1 } }
85 if c >= C_a { if c <= C_z { return 1 } }
86 if c == C_USCORE { return 1 }
87 return 0
88}
89func gt_isdigit(c: i64) -> i64 { if c >= C_0 { if c <= C_9 { return 1 } } return 0 }
90func gt_isspace(c: i64) -> i64 {
91 if c == C_SP { return 1 }
92 if c == C_NL { return 1 }
93 if c == C_TAB { return 1 }
94 if c == C_CR { return 1 }
95 return 0
96}
97// is the two-char sequence at i one operator? (<< >> <= >= == != && || += -= *= /= ++ --)
98func gt_op2(a: i64, b: i64) -> i64 {
99 if b == C_EQ {
100 if a == C_LT { return 1 }
101 if a == C_GT { return 1 }
102 if a == C_EQ { return 1 }
103 if a == C_BANG { return 1 }
104 if a == C_PLUS { return 1 }
105 if a == C_MINUS { return 1 }
106 if a == C_STAR { return 1 }
107 if a == C_SLASH { return 1 }
108 }
109 if a == C_LT { if b == C_LT { return 1 } }
110 if a == C_GT { if b == C_GT { return 1 } }
111 if a == C_AMP { if b == C_AMP { return 1 } }
112 if a == C_BAR { if b == C_BAR { return 1 } }
113 if a == C_PLUS { if b == C_PLUS { return 1 } }
114 if a == C_MINUS { if b == C_MINUS { return 1 } }
115 return 0
116}
117
118// tokenize src[0,n) into toks (3 words each). Returns the token count. toks must hold 3*n words (bound, not a guess).
119func gt_tokenize(src: *u8, n: i64, toks: *i64) -> i64 {
120 var i: i64 = 0
121 var k: i64 = 0
122 while i < n {
123 let c: i64 = src[i] as i64
124 var done: i64 = 0
125 if gt_isspace(c) == 1 { i = i + 1; done = 1 }
126 if done == 0 { if c == C_SLASH { if i + 1 < n {
127 let c2: i64 = src[i + 1] as i64
128 if c2 == C_SLASH {
129 var go: i64 = 1
130 while go == 1 { if i >= n { go = 0 } else { if (src[i] as i64) == C_NL { go = 0 } else { i = i + 1 } } }
131 done = 1
132 }
133 if done == 0 { if c2 == C_STAR {
134 i = i + 2
135 var go2: i64 = 1
136 while go2 == 1 {
137 if i + 1 >= n { i = n; go2 = 0 } else {
138 if (src[i] as i64) == C_STAR { if (src[i + 1] as i64) == C_SLASH { i = i + 2; go2 = 0 } else { i = i + 1 } } else { i = i + 1 }
139 }
140 }
141 done = 1
142 } }
143 } } }
144 if done == 0 { if gt_isalpha(c) == 1 {
145 let s: i64 = i
146 var go3: i64 = 1
147 while go3 == 1 {
148 if i >= n { go3 = 0 } else {
149 let cc: i64 = src[i] as i64
150 if gt_isalpha(cc) == 1 { i = i + 1 } else { if gt_isdigit(cc) == 1 { i = i + 1 } else { go3 = 0 } }
151 }
152 }
153 toks[k * GT_TW] = s; toks[k * GT_TW + 1] = i - s; toks[k * GT_TW + 2] = GT_K_IDENT; k = k + 1
154 done = 1
155 } }
156 if done == 0 {
157 var isnum: i64 = gt_isdigit(c)
158 if isnum == 0 { if c == C_DOT { if i + 1 < n { if gt_isdigit(src[i + 1] as i64) == 1 { isnum = 1 } } } }
159 if isnum == 1 {
160 let s: i64 = i
161 var go4: i64 = 1
162 while go4 == 1 {
163 if i >= n { go4 = 0 } else {
164 let cc: i64 = src[i] as i64
165 if gt_isdigit(cc) == 1 { i = i + 1 } else { if cc == C_DOT { i = i + 1 } else {
166 if cc == C_e { i = i + 1; if i < n { if (src[i] as i64) == C_MINUS { i = i + 1 } else { if (src[i] as i64) == C_PLUS { i = i + 1 } } } } else {
167 if cc == C_E { i = i + 1; if i < n { if (src[i] as i64) == C_MINUS { i = i + 1 } else { if (src[i] as i64) == C_PLUS { i = i + 1 } } } } else {
168 if cc == C_f { i = i + 1 } else { if cc == C_F { i = i + 1 } else { if cc == C_u { i = i + 1 } else { if cc == C_U { i = i + 1 } else { go4 = 0 } } } } } } } }
169 }
170 }
171 toks[k * GT_TW] = s; toks[k * GT_TW + 1] = i - s; toks[k * GT_TW + 2] = GT_K_NUM; k = k + 1
172 done = 1
173 }
174 }
175 if done == 0 {
176 var ln: i64 = 1
177 if i + 1 < n { if gt_op2(c, src[i + 1] as i64) == 1 { ln = 2 } }
178 toks[k * GT_TW] = i; toks[k * GT_TW + 1] = ln; toks[k * GT_TW + 2] = GT_K_OP; k = k + 1
179 i = i + ln
180 }
181 }
182 return k
183}
184
185// canonical spelling of token t of src into out; returns its length. Numbers: strip f/F/u/U, `.5`->`0.5`, `1.`->`1.0`.
186func gt_canon(src: *u8, toks: *i64, t: i64, out: *u8, cap: i64) -> i64 {
187 let s: i64 = toks[t * GT_TW]
188 let ln: i64 = toks[t * GT_TW + 1]
189 let kd: i64 = toks[t * GT_TW + 2]
190 var o: i64 = 0
191 if kd != GT_K_NUM {
192 var i: i64 = 0
193 while i < ln { if o < cap { out[o] = src[s + i]; o = o + 1 } i = i + 1 }
194 return o
195 }
196 var e: i64 = ln
197 var go: i64 = 1
198 while go == 1 {
199 if e <= 0 { go = 0 } else {
200 let cc: i64 = src[s + e - 1] as i64
201 if cc == C_f { e = e - 1 } else { if cc == C_F { e = e - 1 } else { if cc == C_u { e = e - 1 } else { if cc == C_U { e = e - 1 } else { go = 0 } } } }
202 }
203 }
204 if (src[s] as i64) == C_DOT { out[o] = C_0 as u8; o = o + 1 }
205 var j: i64 = 0
206 while j < e { if o < cap { out[o] = src[s + j]; o = o + 1 } j = j + 1 }
207 if e > 0 { if (src[s + e - 1] as i64) == C_DOT { if o < cap { out[o] = C_0 as u8; o = o + 1 } } }
208 return o
209}
210
211// is token t of src the op `X=` for X in + - * / (never == <= >= !=)?
212func gt_is_compound(src: *u8, toks: *i64, t: i64) -> i64 {
213 if toks[t * GT_TW + 2] != GT_K_OP { return 0 }
214 if toks[t * GT_TW + 1] != 2 { return 0 }
215 let s: i64 = toks[t * GT_TW]
216 if (src[s + 1] as i64) != C_EQ { return 0 }
217 let a: i64 = src[s] as i64
218 if a == C_PLUS { return 1 }
219 if a == C_MINUS { return 1 }
220 if a == C_STAR { return 1 }
221 if a == C_SLASH { return 1 }
222 return 0
223}
224func gt_tok_is(src: *u8, toks: *i64, t: i64, ch: i64) -> i64 {
225 if toks[t * GT_TW + 2] != GT_K_OP { return 0 }
226 if toks[t * GT_TW + 1] != 1 { return 0 }
227 if (src[toks[t * GT_TW]] as i64) == ch { return 1 }
228 return 0
229}
230// the start index of the lvalue ending at token `last` (an ident, or `]` closing an index) under the grammar
231// ident ( '.' ident | '[' ... ']' )*; -1 when the shape is not an lvalue.
232func gt_lvalue_start(src: *u8, toks: *i64, last: i64) -> i64 {
233 var j: i64 = last
234 var go: i64 = 1
235 while go == 1 {
236 if j < 0 { return 0 - 1 }
237 if gt_tok_is(src, toks, j, C_RBR) == 1 {
238 var depth: i64 = 0
239 var found: i64 = 0 - 1
240 var q: i64 = j
241 var go2: i64 = 1
242 while go2 == 1 {
243 if q < 0 { go2 = 0 } else {
244 if gt_tok_is(src, toks, q, C_RBR) == 1 { depth = depth + 1 }
245 if gt_tok_is(src, toks, q, C_LBR) == 1 { depth = depth - 1; if depth == 0 { found = q; go2 = 0 } }
246 if go2 == 1 { q = q - 1 }
247 }
248 }
249 if found < 1 { return 0 - 1 }
250 j = found - 1
251 }
252 if j < 0 { return 0 - 1 }
253 if toks[j * GT_TW + 2] != GT_K_IDENT { return 0 - 1 }
254 if j >= 1 { if gt_tok_is(src, toks, j - 1, C_DOT) == 1 { j = j - 2 } else { go = 0 } } else { go = 0 }
255 }
256 return j
257}
258
259// expand compound assignments: xt receives the rewritten token list (3 words each; a synthesised `=` or op points INTO
260// the compound token's own bytes, so every token still names a byte range of src). Returns the new count; box[0]
261// receives the number of compound tokens whose lvalue shape was refused (left literal). xt must hold 3*(3*k) words.
262func gt_expand(src: *u8, toks: *i64, k: i64, xt: *i64, box: *i64) -> i64 {
263 var t: i64 = 0
264 var o: i64 = 0
265 box[0] = 0
266 while t < k {
267 var handled: i64 = 0
268 if gt_is_compound(src, toks, t) == 1 { if o >= 1 {
269 let ls: i64 = gt_lvalue_start(src, xt, o - 1)
270 if ls >= 0 {
271 let cs: i64 = toks[t * GT_TW]
272 xt[o * GT_TW] = cs + 1; xt[o * GT_TW + 1] = 1; xt[o * GT_TW + 2] = GT_K_OP; o = o + 1
273 var c: i64 = ls
274 let hi: i64 = o - 1
275 while c < hi { xt[o * GT_TW] = xt[c * GT_TW]; xt[o * GT_TW + 1] = xt[c * GT_TW + 1]; xt[o * GT_TW + 2] = xt[c * GT_TW + 2]; o = o + 1; c = c + 1 }
276 xt[o * GT_TW] = cs; xt[o * GT_TW + 1] = 1; xt[o * GT_TW + 2] = GT_K_OP; o = o + 1
277 handled = 1
278 } else { box[0] = box[0] + 1 }
279 } }
280 if handled == 0 { xt[o * GT_TW] = toks[t * GT_TW]; xt[o * GT_TW + 1] = toks[t * GT_TW + 1]; xt[o * GT_TW + 2] = toks[t * GT_TW + 2]; o = o + 1 }
281 t = t + 1
282 }
283 return o
284}
285
286// the ruler: fills r (GT_R_SLOTS words) and returns the verdict code.
287func gt_diff(a: *u8, na: i64, b: *u8, nb: i64, r: *i64) -> i64 {
288 let ta: *i64 = sys_mmap(GT_TW * 8 * (na + 1)) as *i64
289 let tb: *i64 = sys_mmap(GT_TW * 8 * (nb + 1)) as *i64
290 let ka: i64 = gt_tokenize(a, na, ta)
291 let kb: i64 = gt_tokenize(b, nb, tb)
292 let xa: *i64 = sys_mmap(GT_TW * 8 * (3 * ka + 3)) as *i64
293 let xb: *i64 = sys_mmap(GT_TW * 8 * (3 * kb + 3)) as *i64
294 let boxa: *i64 = sys_mmap(16) as *i64
295 let boxb: *i64 = sys_mmap(16) as *i64
296 let ea: i64 = gt_expand(a, ta, ka, xa, boxa)
297 let eb: i64 = gt_expand(b, tb, kb, xb, boxb)
298 r[GT_R_TOKA] = ka; r[GT_R_TOKB] = kb; r[GT_R_EXPA] = ea; r[GT_R_EXPB] = eb
299 r[GT_R_REFUSED] = boxa[0] + boxb[0]
300 r[GT_R_FIRSTDIFF] = 0 - 1
301 let ca: *u8 = sys_mmap(GT_CANON_CAP)
302 let cb: *u8 = sys_mmap(GT_CANON_CAP)
303 var i: i64 = 0
304 var lim: i64 = ea
305 if eb < lim { lim = eb }
306 var go: i64 = 1
307 while go == 1 {
308 if i >= lim { go = 0 } else {
309 let la: i64 = gt_canon(a, xa, i, ca, GT_CANON_CAP)
310 let lb: i64 = gt_canon(b, xb, i, cb, GT_CANON_CAP)
311 var same: i64 = 1
312 if la != lb { same = 0 } else {
313 var q: i64 = 0
314 while q < la { if ca[q] != cb[q] { same = 0; q = la } else { q = q + 1 } }
315 }
316 if same == 0 { r[GT_R_FIRSTDIFF] = i; go = 0 } else { i = i + 1 }
317 }
318 }
319 if r[GT_R_FIRSTDIFF] < 0 { if ea != eb { r[GT_R_FIRSTDIFF] = lim } }
320 if r[GT_R_FIRSTDIFF] < 0 { r[GT_R_VERDICT] = GT_V_IDENTICAL } else { r[GT_R_VERDICT] = GT_V_DIFFERS }
321 return r[GT_R_VERDICT]
322}
323
324// print the canonical token i of the EXPANDED stream of src (re-derived; the report never trusts a cached string)
325func gt_put_expanded_tok(fd: i64, src: *u8, n: i64, i: i64) -> i64 {
326 let t: *i64 = sys_mmap(GT_TW * 8 * (n + 1)) as *i64
327 let k: i64 = gt_tokenize(src, n, t)
328 let x: *i64 = sys_mmap(GT_TW * 8 * (3 * k + 3)) as *i64
329 let bx: *i64 = sys_mmap(16) as *i64
330 let e: i64 = gt_expand(src, t, k, x, bx)
331 if i < 0 { gt_fputs(fd, "-" as *u8); return 0 }
332 if i >= e { gt_fputs(fd, "<end>" as *u8); return 0 }
333 let c: *u8 = sys_mmap(GT_CANON_CAP)
334 let l: i64 = gt_canon(src, x, i, c, GT_CANON_CAP)
335 sys_write(fd, c, l)
336 return 0
337}
338func gt_report(fd: i64, a: *u8, na: i64, b: *u8, nb: i64, r: *i64) -> i64 {
339 gt_fputs(fd, "tokens_a=" as *u8); gt_fputn(fd, r[GT_R_TOKA])
340 gt_fputs(fd, " tokens_b=" as *u8); gt_fputn(fd, r[GT_R_TOKB])
341 gt_fputs(fd, " expanded_a=" as *u8); gt_fputn(fd, r[GT_R_EXPA])
342 gt_fputs(fd, " expanded_b=" as *u8); gt_fputn(fd, r[GT_R_EXPB])
343 gt_fputs(fd, " expand_refused=" as *u8); gt_fputn(fd, r[GT_R_REFUSED])
344 gt_fputs(fd, " first_diff=" as *u8); gt_fputn(fd, r[GT_R_FIRSTDIFF])
345 if r[GT_R_FIRSTDIFF] >= 0 {
346 gt_fputs(fd, " a_tok=" as *u8); gt_put_expanded_tok(fd, a, na, r[GT_R_FIRSTDIFF])
347 gt_fputs(fd, " b_tok=" as *u8); gt_put_expanded_tok(fd, b, nb, r[GT_R_FIRSTDIFF])
348 }
349 gt_fputs(fd, "\n" as *u8)
350 if r[GT_R_VERDICT] == GT_V_IDENTICAL { gt_fputs(fd, "verdict=IDENTICAL\n" as *u8) } else { gt_fputs(fd, "verdict=DIFFERS\n" as *u8) }
351 return 0
352}
353
354// ---- S12c-2 (2026-09-05): THE CANONICAL FORM -- redundant parentheses, multi-declarators, brace-less branches, counted loops ----
355// WHY: the IR backends parenthesise EVERY binary, negation and select node ((a+b)*c is spelled ((a+b)*c), -x is (-x),
356// c?a:b is (c?a:b)), spell one declarator per statement, brace every branch and lower a counted loop to
357// init;while(true){if(until){break;}body;step;} -- while a person writes the minimal form. Token identity between the two
358// is therefore unreachable BY SPELLING while the programs are identical BY STRUCTURE. The honest ruler is structural: both
359// sides are parsed with the GLSL ES 3.00 precedence table into one expression tree per statement and RE-PRINTED in the
360// backend's own fully-parenthesised form, so a redundant parenthesis vanishes and a meaningful one survives ((a+b)*c and
361// a+b*c print differently because they ARE different). The emitters are untouched: a live shader's bytes never move for a
362// ruler's convenience, and the world shader's pinned hand text stays pinned.
363// NORMALISATIONS ADDED, each named and each bitten in the gate: (1) precedence re-parenthesisation of every expression;
364// (2) `T a=x,b=y;` -> `T a=x;T b=y;`; (3) a brace-less if/else/while body is braced and `else if` becomes `else{if ...}`;
365// (4) `for(init;cond;step)body` -> `init;while(true){if(not-cond){break;}body;step;}` with `i++`/`i--`/`++i` spelled
366// `i=(i+1)` / `i=(i-1)`; (5) not-of-a-comparison prints as the inverted comparison (`not-(a<b)` -> `(a>=b)`, six ops)
367// because the IR carries no unary not and its authors spell the inverted comparison -- the ONE semantic caveat of this
368// ruler: it identifies two forms that differ only on NaN, declared here rather than hidden. An input the grammar cannot
369// parse is REFUSED and the refusing token is named by byte offset and spelling (verdict UNREADABLE), never passed through.
370// GRAMMAR COVERED: literals, identifiers, calls/constructors/casts (an identifier followed by '('), [index], .member,
371// unary - and not, prefix/postfix ++ --, the binary table || && | ^ & == != < > <= >= << >> + - * / %, ?:, =, declarations
372// (T name[N]=expr with const/highp/mediump/lowp qualifiers), if/else, while, for, return, discard, break, continue, blocks;
373// module scope is copied token for token except that the right side of an `=` is canonicalised.
374// SHAPE: ONE recursive-descent function per layer that only ever calls ITSELF (gp_parse over modes, gp_stmt over wrap), so
375// no function is referenced before its definition and there is exactly one parser.
376const GN_KIND: i64 = 0
377const GN_TOK: i64 = 1
378const GN_A: i64 = 2
379const GN_B: i64 = 3
380const GN_C: i64 = 4
381const GN_NEXT: i64 = 5
382const GN_SLOTS: i64 = 6
383const GN_LEAF: i64 = 1
384const GN_BIN: i64 = 2
385const GN_UN: i64 = 3
386const GN_TERN: i64 = 4
387const GN_CALL: i64 = 5
388const GN_INDEX: i64 = 6
389const GN_SWZ: i64 = 7
390const GN_ASSIGN: i64 = 8
391const GN_INCDEC: i64 = 9
392const GP_POS: i64 = 0
393const GP_NTOK: i64 = 1
394const GP_OUT: i64 = 2
395const GP_ERR: i64 = 3
396const GP_NCOUNT: i64 = 4
397const GP_SRC: i64 = 5
398const GP_TOKS: i64 = 6
399const GP_NODES: i64 = 7
400const GP_OUTBUF: i64 = 8
401const GP_CAP: i64 = 9
402const GP_OVER: i64 = 10
403const GP_TMP: i64 = 11
404const GP_SLOTS: i64 = 12
405const GP_M_ASSIGN: i64 = 1
406const GP_M_TERN: i64 = 2
407const GP_M_BIN: i64 = 3
408const GP_M_UNARY: i64 = 4
409const GP_P_LOR: i64 = 1
410const GP_P_LAND: i64 = 2
411const GP_P_BOR: i64 = 3
412const GP_P_BXOR: i64 = 4
413const GP_P_BAND: i64 = 5
414const GP_P_EQ: i64 = 6
415const GP_P_REL: i64 = 7
416const GP_P_SHIFT: i64 = 8
417const GP_P_ADD: i64 = 9
418const GP_P_MUL: i64 = 10
419const GP_INV_NONE: i64 = 0
420const GP_INV_GE: i64 = 1
421const GP_INV_LE: i64 = 2
422const GP_INV_GT: i64 = 3
423const GP_INV_LT: i64 = 4
424const GP_INV_NE: i64 = 5
425const GP_INV_EQ: i64 = 6
426const C_QMARK: i64 = 63
427const C_COLON: i64 = 58
428const C_SEMI: i64 = 59
429const C_COMMA: i64 = 44
430const C_LBRACE: i64 = 123
431const C_RBRACE: i64 = 125
432const C_CARET: i64 = 94
433const C_PCT: i64 = 37
434const C_1: i64 = 49
435// canonical text bound, derived: a token of one byte prints as at most "( x ) " (6 bytes) under wrapping and a `for`
436// lowering adds a fixed head, so MULT bytes per source byte plus HEAD is a bound, not a guess
437const GT_CANON_OUT_MULT: i64 = 8
438const GT_CANON_OUT_HEAD: i64 = 4096
439// result slots past the incumbent's 7 (GT_R_SLOTS is 20, declared at the head of this file)
440const GT_R_CANA: i64 = 7
441const GT_R_CANB: i64 = 8
442const GT_R_ERRA: i64 = 9
443const GT_R_ERRB: i64 = 10
444const GT_R_ERRLA: i64 = 11
445const GT_R_ERRLB: i64 = 12
446const GT_R_CAPTR_A: i64 = 13
447const GT_R_CALEN_A: i64 = 14
448const GT_R_CAPTR_B: i64 = 15
449const GT_R_CALEN_B: i64 = 16
450const GT_R_SRCA: i64 = 17
451const GT_R_SRCB: i64 = 18
452const GT_R_OVER: i64 = 19
453
454func gp_tk(ps: *i64, t: i64, w: i64) -> i64 { let tk: *i64 = ps[GP_TOKS] as *i64; return tk[t * GT_TW + w] }
455func gp_byte(ps: *i64, t: i64, i: i64) -> i64 { let s: *u8 = ps[GP_SRC] as *u8; return s[gp_tk(ps, t, 0) + i] as i64 }
456func gp_kind(ps: *i64, t: i64) -> i64 {
457 if t < 0 { return 0 }
458 if t >= ps[GP_NTOK] { return 0 }
459 return gp_tk(ps, t, 2)
460}
461func gp_is1(ps: *i64, t: i64, ch: i64) -> i64 {
462 if gp_kind(ps, t) != GT_K_OP { return 0 }
463 if gp_tk(ps, t, 1) != 1 { return 0 }
464 if gp_byte(ps, t, 0) == ch { return 1 }
465 return 0
466}
467func gp_is2(ps: *i64, t: i64, a: i64, b: i64) -> i64 {
468 if gp_kind(ps, t) != GT_K_OP { return 0 }
469 if gp_tk(ps, t, 1) != 2 { return 0 }
470 if gp_byte(ps, t, 0) != a { return 0 }
471 if gp_byte(ps, t, 1) == b { return 1 }
472 return 0
473}
474func gp_isword(ps: *i64, t: i64, w: *u8) -> i64 {
475 if gp_kind(ps, t) != GT_K_IDENT { return 0 }
476 let ln: i64 = gp_tk(ps, t, 1)
477 if gt_slen(w) != ln { return 0 }
478 var i: i64 = 0
479 while i < ln { if gp_byte(ps, t, i) != (w[i] as i64) { return 0 } i = i + 1 }
480 return 1
481}
482func gp_fail(ps: *i64, t: i64) -> i64 { if ps[GP_ERR] < 0 { ps[GP_ERR] = t } return 0 }
483func gp_n(ps: *i64, id: i64, w: i64) -> i64 { let nd: *i64 = ps[GP_NODES] as *i64; return nd[id * GN_SLOTS + w] }
484func gp_nset(ps: *i64, id: i64, w: i64, v: i64) -> i64 { let nd: *i64 = ps[GP_NODES] as *i64; nd[id * GN_SLOTS + w] = v; return 0 }
485func gp_node(ps: *i64, kind: i64, tok: i64, a: i64, b: i64, c: i64) -> i64 {
486 let id: i64 = ps[GP_NCOUNT]
487 ps[GP_NCOUNT] = id + 1
488 gp_nset(ps, id, GN_KIND, kind); gp_nset(ps, id, GN_TOK, tok); gp_nset(ps, id, GN_A, a); gp_nset(ps, id, GN_B, b); gp_nset(ps, id, GN_C, c); gp_nset(ps, id, GN_NEXT, 0)
489 return id
490}
491func gp_putc(ps: *i64, ch: i64) -> i64 {
492 if ps[GP_OUT] + 1 >= ps[GP_CAP] { ps[GP_OVER] = 1; return 0 }
493 let o: *u8 = ps[GP_OUTBUF] as *u8
494 o[ps[GP_OUT]] = ch as u8
495 ps[GP_OUT] = ps[GP_OUT] + 1
496 return 0
497}
498func gp_emit_c(ps: *i64, ch: i64) -> i64 { gp_putc(ps, ch); gp_putc(ps, C_SP); return 0 }
499func gp_emit_s(ps: *i64, s: *u8) -> i64 {
500 var i: i64 = 0
501 while s[i] != (0 as u8) { gp_putc(ps, s[i] as i64); i = i + 1 }
502 gp_putc(ps, C_SP)
503 return 0
504}
505// the canonical spelling of token t (numbers canonicalised by gt_canon), then one space so re-tokenising cannot merge tokens
506func gp_emit_tok(ps: *i64, t: i64) -> i64 {
507 let tmp: *u8 = ps[GP_TMP] as *u8
508 let l: i64 = gt_canon(ps[GP_SRC] as *u8, ps[GP_TOKS] as *i64, t, tmp, GT_CANON_CAP)
509 var i: i64 = 0
510 while i < l { gp_putc(ps, tmp[i] as i64); i = i + 1 }
511 gp_putc(ps, C_SP)
512 return 0
513}
514func gp_take(ps: *i64) -> i64 { ps[GP_POS] = ps[GP_POS] + 1; return 0 }
515// the one-char op ch at the cursor: emit + consume, or refuse there
516func gp_expect(ps: *i64, ch: i64) -> i64 {
517 if gp_is1(ps, ps[GP_POS], ch) == 0 { return gp_fail(ps, ps[GP_POS]) }
518 gp_emit_c(ps, ch); gp_take(ps)
519 return 1
520}
521// the binary precedence of token t (GLSL ES 3.00 table, higher binds tighter), 0 when t is not a binary operator
522func gp_binprec(ps: *i64, t: i64) -> i64 {
523 if gp_kind(ps, t) != GT_K_OP { return 0 }
524 let ln: i64 = gp_tk(ps, t, 1)
525 let a: i64 = gp_byte(ps, t, 0)
526 if ln == 2 {
527 let b: i64 = gp_byte(ps, t, 1)
528 if a == C_BAR { if b == C_BAR { return GP_P_LOR } }
529 if a == C_AMP { if b == C_AMP { return GP_P_LAND } }
530 if b == C_EQ {
531 if a == C_EQ { return GP_P_EQ }
532 if a == C_BANG { return GP_P_EQ }
533 if a == C_LT { return GP_P_REL }
534 if a == C_GT { return GP_P_REL }
535 }
536 if a == C_LT { if b == C_LT { return GP_P_SHIFT } }
537 if a == C_GT { if b == C_GT { return GP_P_SHIFT } }
538 return 0
539 }
540 if a == C_BAR { return GP_P_BOR }
541 if a == C_CARET { return GP_P_BXOR }
542 if a == C_AMP { return GP_P_BAND }
543 if a == C_LT { return GP_P_REL }
544 if a == C_GT { return GP_P_REL }
545 if a == C_PLUS { return GP_P_ADD }
546 if a == C_MINUS { return GP_P_ADD }
547 if a == C_STAR { return GP_P_MUL }
548 if a == C_SLASH { return GP_P_MUL }
549 if a == C_PCT { return GP_P_MUL }
550 return 0
551}
552// ONE recursive descent over the expanded token stream; `mode` selects the layer (assignment > ternary > binary(minp) >
553// unary+postfix) so the function only ever calls ITSELF. Returns a node id, 0 on refusal (the refusing token is recorded once).
554func gp_parse(ps: *i64, mode: i64, minp: i64) -> i64 {
555 if mode == GP_M_ASSIGN {
556 let l: i64 = gp_parse(ps, GP_M_TERN, 0)
557 if l == 0 { return 0 }
558 // a compound assignment is parsed STRUCTURALLY: `x op= y` is ASSIGN(x, BIN(op, x, y)) with y a whole assignment-
559 // expression -- so `x*=a+b` is x=(x*(a+b)), never the token-level x=x*a+b the raw expander spells (that expander cannot
560 // see where y ends; this parser can -- measured 2026-09-05 on the hand MVS line `aP2.x*=1.+.08*w2`, which the raw
561 // expansion would have identified with the WRONG tree). The BIN carries GN_C=1 so its printer spells the single-
562 // character op of the compound token.
563 let tc: i64 = ps[GP_POS]
564 if gt_is_compound(ps[GP_SRC] as *u8, ps[GP_TOKS] as *i64, tc) == 1 {
565 gp_take(ps)
566 let rc: i64 = gp_parse(ps, GP_M_ASSIGN, 0)
567 if rc == 0 { return 0 }
568 return gp_node(ps, GN_ASSIGN, 0, l, gp_node(ps, GN_BIN, tc, l, rc, 1), 0)
569 }
570 if gp_is1(ps, ps[GP_POS], C_EQ) == 1 {
571 gp_take(ps)
572 let r: i64 = gp_parse(ps, GP_M_ASSIGN, 0)
573 if r == 0 { return 0 }
574 return gp_node(ps, GN_ASSIGN, 0, l, r, 0)
575 }
576 return l
577 }
578 if mode == GP_M_TERN {
579 let c: i64 = gp_parse(ps, GP_M_BIN, GP_P_LOR)
580 if c == 0 { return 0 }
581 if gp_is1(ps, ps[GP_POS], C_QMARK) == 1 {
582 gp_take(ps)
583 let a: i64 = gp_parse(ps, GP_M_TERN, 0)
584 if a == 0 { return 0 }
585 if gp_is1(ps, ps[GP_POS], C_COLON) == 0 { return gp_fail(ps, ps[GP_POS]) }
586 gp_take(ps)
587 let b: i64 = gp_parse(ps, GP_M_TERN, 0)
588 if b == 0 { return 0 }
589 return gp_node(ps, GN_TERN, 0, c, a, b)
590 }
591 return c
592 }
593 if mode == GP_M_BIN {
594 var left: i64 = gp_parse(ps, GP_M_UNARY, 0)
595 if left == 0 { return 0 }
596 var go: i64 = 1
597 while go == 1 {
598 let tb: i64 = ps[GP_POS]
599 let pr: i64 = gp_binprec(ps, tb)
600 if pr < minp { go = 0 } else { if pr == 0 { go = 0 } else {
601 gp_take(ps)
602 let right: i64 = gp_parse(ps, GP_M_BIN, pr + 1)
603 if right == 0 { return 0 }
604 left = gp_node(ps, GN_BIN, tb, left, right, 0)
605 } }
606 }
607 return left
608 }
609 // GP_M_UNARY: prefix operators, then a primary with its postfix chain
610 let t0: i64 = ps[GP_POS]
611 if gp_is1(ps, t0, C_MINUS) == 1 { gp_take(ps); let e1: i64 = gp_parse(ps, GP_M_UNARY, 0); if e1 == 0 { return 0 } return gp_node(ps, GN_UN, C_MINUS, e1, 0, 0) }
612 if gp_is1(ps, t0, C_BANG) == 1 { gp_take(ps); let e2: i64 = gp_parse(ps, GP_M_UNARY, 0); if e2 == 0 { return 0 } return gp_node(ps, GN_UN, C_BANG, e2, 0, 0) }
613 if gp_is2(ps, t0, C_PLUS, C_PLUS) == 1 { gp_take(ps); let e3: i64 = gp_parse(ps, GP_M_UNARY, 0); if e3 == 0 { return 0 } return gp_node(ps, GN_INCDEC, t0, e3, 0, 0) }
614 if gp_is2(ps, t0, C_MINUS, C_MINUS) == 1 { gp_take(ps); let e4: i64 = gp_parse(ps, GP_M_UNARY, 0); if e4 == 0 { return 0 } return gp_node(ps, GN_INCDEC, t0, e4, 0, 0) }
615 var base: i64 = 0
616 let k: i64 = gp_kind(ps, t0)
617 if k == GT_K_NUM { gp_take(ps); base = gp_node(ps, GN_LEAF, t0, 0, 0, 0) }
618 if k == GT_K_IDENT {
619 gp_take(ps)
620 if gp_is1(ps, t0 + 1, C_LPAR) == 1 {
621 gp_take(ps)
622 base = gp_node(ps, GN_CALL, t0, 0, 0, 0)
623 if gp_is1(ps, ps[GP_POS], C_RPAR) == 1 { gp_take(ps) } else {
624 var last: i64 = 0
625 var ga: i64 = 1
626 while ga == 1 {
627 let ea: i64 = gp_parse(ps, GP_M_ASSIGN, 0)
628 if ea == 0 { return 0 }
629 if last == 0 { gp_nset(ps, base, GN_A, ea) } else { gp_nset(ps, last, GN_NEXT, ea) }
630 last = ea
631 if gp_is1(ps, ps[GP_POS], C_COMMA) == 1 { gp_take(ps) } else {
632 if gp_is1(ps, ps[GP_POS], C_RPAR) == 1 { gp_take(ps); ga = 0 } else { return gp_fail(ps, ps[GP_POS]) }
633 }
634 }
635 }
636 } else { base = gp_node(ps, GN_LEAF, t0, 0, 0, 0) }
637 }
638 if base == 0 { if gp_is1(ps, t0, C_LPAR) == 1 {
639 gp_take(ps)
640 let g: i64 = gp_parse(ps, GP_M_ASSIGN, 0)
641 if g == 0 { return 0 }
642 if gp_is1(ps, ps[GP_POS], C_RPAR) == 0 { return gp_fail(ps, ps[GP_POS]) }
643 gp_take(ps)
644 base = g
645 } }
646 if base == 0 { return gp_fail(ps, t0) }
647 var go2: i64 = 1
648 while go2 == 1 {
649 let tp: i64 = ps[GP_POS]
650 if gp_is1(ps, tp, C_LBR) == 1 {
651 gp_take(ps)
652 let ix: i64 = gp_parse(ps, GP_M_ASSIGN, 0)
653 if ix == 0 { return 0 }
654 if gp_is1(ps, ps[GP_POS], C_RBR) == 0 { return gp_fail(ps, ps[GP_POS]) }
655 gp_take(ps)
656 base = gp_node(ps, GN_INDEX, 0, base, ix, 0)
657 } else { if gp_is1(ps, tp, C_DOT) == 1 {
658 if gp_kind(ps, tp + 1) != GT_K_IDENT { return gp_fail(ps, tp + 1) }
659 gp_take(ps); gp_take(ps)
660 base = gp_node(ps, GN_SWZ, tp + 1, base, 0, 0)
661 } else { if gp_is2(ps, tp, C_PLUS, C_PLUS) == 1 { gp_take(ps); base = gp_node(ps, GN_INCDEC, tp, base, 0, 0) }
662 else { if gp_is2(ps, tp, C_MINUS, C_MINUS) == 1 { gp_take(ps); base = gp_node(ps, GN_INCDEC, tp, base, 0, 0) }
663 else { go2 = 0 } } } }
664 }
665 return base
666}
667// the inverted spelling of a comparison operator token, or GP_INV_NONE
668func gp_inv(ps: *i64, t: i64) -> i64 {
669 if gp_kind(ps, t) != GT_K_OP { return GP_INV_NONE }
670 let ln: i64 = gp_tk(ps, t, 1)
671 let a: i64 = gp_byte(ps, t, 0)
672 if ln == 1 {
673 if a == C_LT { return GP_INV_GE }
674 if a == C_GT { return GP_INV_LE }
675 return GP_INV_NONE
676 }
677 if gp_byte(ps, t, 1) != C_EQ { return GP_INV_NONE }
678 if a == C_LT { return GP_INV_GT }
679 if a == C_GT { return GP_INV_LT }
680 if a == C_EQ { return GP_INV_NE }
681 if a == C_BANG { return GP_INV_EQ }
682 return GP_INV_NONE
683}
684func gp_emit_inv(ps: *i64, inv: i64) -> i64 {
685 if inv == GP_INV_GE { gp_putc(ps, C_GT); gp_putc(ps, C_EQ) }
686 if inv == GP_INV_LE { gp_putc(ps, C_LT); gp_putc(ps, C_EQ) }
687 if inv == GP_INV_GT { gp_putc(ps, C_GT) }
688 if inv == GP_INV_LT { gp_putc(ps, C_LT) }
689 if inv == GP_INV_NE { gp_putc(ps, C_BANG); gp_putc(ps, C_EQ) }
690 if inv == GP_INV_EQ { gp_putc(ps, C_EQ); gp_putc(ps, C_EQ) }
691 gp_putc(ps, C_SP)
692 return 0
693}
694// print a tree in the backend's own shape: every binary / unary / select node wrapped, calls and postfix bare
695func gp_print(ps: *i64, id: i64) -> i64 {
696 if id == 0 { return 0 }
697 let k: i64 = gp_n(ps, id, GN_KIND)
698 let a: i64 = gp_n(ps, id, GN_A)
699 let b: i64 = gp_n(ps, id, GN_B)
700 if k == GN_LEAF { gp_emit_tok(ps, gp_n(ps, id, GN_TOK)); return 0 }
701 if k == GN_BIN {
702 gp_emit_c(ps, C_LPAR); gp_print(ps, a)
703 // GN_C=1 marks the BIN synthesised from a compound assignment: spell the op's first byte, never the `op=` token
704 if gp_n(ps, id, GN_C) == 1 { gp_emit_c(ps, gp_byte(ps, gp_n(ps, id, GN_TOK), 0)) } else { gp_emit_tok(ps, gp_n(ps, id, GN_TOK)) }
705 gp_print(ps, b); gp_emit_c(ps, C_RPAR)
706 return 0
707 }
708 if k == GN_UN {
709 let ch: i64 = gp_n(ps, id, GN_TOK)
710 if ch == C_BANG { if gp_n(ps, a, GN_KIND) == GN_BIN {
711 let inv: i64 = gp_inv(ps, gp_n(ps, a, GN_TOK))
712 if inv != GP_INV_NONE {
713 gp_emit_c(ps, C_LPAR); gp_print(ps, gp_n(ps, a, GN_A)); gp_emit_inv(ps, inv); gp_print(ps, gp_n(ps, a, GN_B)); gp_emit_c(ps, C_RPAR)
714 return 0
715 }
716 } }
717 gp_emit_c(ps, C_LPAR); gp_emit_c(ps, ch); gp_print(ps, a); gp_emit_c(ps, C_RPAR)
718 return 0
719 }
720 if k == GN_TERN { gp_emit_c(ps, C_LPAR); gp_print(ps, a); gp_emit_c(ps, C_QMARK); gp_print(ps, b); gp_emit_c(ps, C_COLON); gp_print(ps, gp_n(ps, id, GN_C)); gp_emit_c(ps, C_RPAR); return 0 }
721 if k == GN_CALL {
722 gp_emit_tok(ps, gp_n(ps, id, GN_TOK)); gp_emit_c(ps, C_LPAR)
723 var e: i64 = a
724 var first: i64 = 1
725 while e != 0 {
726 if first == 0 { gp_emit_c(ps, C_COMMA) }
727 first = 0
728 gp_print(ps, e)
729 e = gp_n(ps, e, GN_NEXT)
730 }
731 gp_emit_c(ps, C_RPAR)
732 return 0
733 }
734 if k == GN_INDEX { gp_print(ps, a); gp_emit_c(ps, C_LBR); gp_print(ps, b); gp_emit_c(ps, C_RBR); return 0 }
735 if k == GN_SWZ { gp_print(ps, a); gp_emit_c(ps, C_DOT); gp_emit_tok(ps, gp_n(ps, id, GN_TOK)); return 0 }
736 if k == GN_ASSIGN { gp_print(ps, a); gp_emit_c(ps, C_EQ); gp_print(ps, b); return 0 }
737 if k == GN_INCDEC {
738 gp_print(ps, a); gp_emit_c(ps, C_EQ); gp_emit_c(ps, C_LPAR); gp_print(ps, a); gp_emit_c(ps, gp_byte(ps, gp_n(ps, id, GN_TOK), 0)); gp_emit_c(ps, C_1); gp_emit_c(ps, C_RPAR)
739 return 0
740 }
741 return 0
742}
743// one statement; wrap=1 braces a statement that is not itself a block (an if / else / while body). Returns 1, or 0 refused.
744func gp_stmt(ps: *i64, wrap: i64) -> i64 {
745 let t: i64 = ps[GP_POS]
746 if t >= ps[GP_NTOK] { return gp_fail(ps, t) }
747 if gp_is1(ps, t, C_LBRACE) == 1 {
748 gp_take(ps); gp_emit_c(ps, C_LBRACE)
749 var gb0: i64 = 1
750 while gb0 == 1 {
751 if ps[GP_POS] >= ps[GP_NTOK] { return gp_fail(ps, ps[GP_POS]) }
752 if gp_is1(ps, ps[GP_POS], C_RBRACE) == 1 { gb0 = 0 } else { if gp_stmt(ps, 0) == 0 { return 0 } }
753 }
754 gp_take(ps); gp_emit_c(ps, C_RBRACE)
755 return 1
756 }
757 if wrap == 1 { gp_emit_c(ps, C_LBRACE) }
758 var handled: i64 = 0
759 if gp_isword(ps, t, "if" as *u8) == 1 {
760 handled = 1
761 gp_take(ps); gp_emit_s(ps, "if" as *u8)
762 if gp_expect(ps, C_LPAR) == 0 { return 0 }
763 let c: i64 = gp_parse(ps, GP_M_ASSIGN, 0)
764 if c == 0 { return 0 }
765 gp_print(ps, c)
766 if gp_expect(ps, C_RPAR) == 0 { return 0 }
767 if gp_stmt(ps, 1) == 0 { return 0 }
768 if gp_isword(ps, ps[GP_POS], "else" as *u8) == 1 {
769 gp_take(ps); gp_emit_s(ps, "else" as *u8)
770 if gp_stmt(ps, 1) == 0 { return 0 }
771 }
772 }
773 if handled == 0 { if gp_isword(ps, t, "while" as *u8) == 1 {
774 handled = 1
775 gp_take(ps); gp_emit_s(ps, "while" as *u8)
776 if gp_expect(ps, C_LPAR) == 0 { return 0 }
777 let c2: i64 = gp_parse(ps, GP_M_ASSIGN, 0)
778 if c2 == 0 { return 0 }
779 gp_print(ps, c2)
780 if gp_expect(ps, C_RPAR) == 0 { return 0 }
781 if gp_stmt(ps, 1) == 0 { return 0 }
782 } }
783 if handled == 0 { if gp_isword(ps, t, "for" as *u8) == 1 {
784 handled = 1
785 gp_take(ps)
786 if gp_is1(ps, ps[GP_POS], C_LPAR) == 0 { return gp_fail(ps, ps[GP_POS]) }
787 gp_take(ps)
788 if gp_is1(ps, ps[GP_POS], C_SEMI) == 1 { gp_take(ps) } else { if gp_stmt(ps, 0) == 0 { return 0 } }
789 gp_emit_s(ps, "while" as *u8); gp_emit_c(ps, C_LPAR); gp_emit_s(ps, "true" as *u8); gp_emit_c(ps, C_RPAR); gp_emit_c(ps, C_LBRACE)
790 if gp_is1(ps, ps[GP_POS], C_SEMI) == 1 { gp_take(ps) } else {
791 let cnd: i64 = gp_parse(ps, GP_M_ASSIGN, 0)
792 if cnd == 0 { return 0 }
793 if gp_is1(ps, ps[GP_POS], C_SEMI) == 0 { return gp_fail(ps, ps[GP_POS]) }
794 gp_take(ps)
795 let nt: i64 = gp_node(ps, GN_UN, C_BANG, cnd, 0, 0)
796 gp_emit_s(ps, "if" as *u8); gp_emit_c(ps, C_LPAR); gp_print(ps, nt); gp_emit_c(ps, C_RPAR)
797 gp_emit_c(ps, C_LBRACE); gp_emit_s(ps, "break" as *u8); gp_emit_c(ps, C_SEMI); gp_emit_c(ps, C_RBRACE)
798 }
799 var step: i64 = 0
800 if gp_is1(ps, ps[GP_POS], C_RPAR) == 1 { gp_take(ps) } else {
801 step = gp_parse(ps, GP_M_ASSIGN, 0)
802 if step == 0 { return 0 }
803 if gp_is1(ps, ps[GP_POS], C_RPAR) == 0 { return gp_fail(ps, ps[GP_POS]) }
804 gp_take(ps)
805 }
806 if gp_is1(ps, ps[GP_POS], C_LBRACE) == 1 {
807 gp_take(ps)
808 var gb: i64 = 1
809 while gb == 1 {
810 if ps[GP_POS] >= ps[GP_NTOK] { return gp_fail(ps, ps[GP_POS]) }
811 if gp_is1(ps, ps[GP_POS], C_RBRACE) == 1 { gb = 0 } else { if gp_stmt(ps, 0) == 0 { return 0 } }
812 }
813 gp_take(ps)
814 } else { if gp_stmt(ps, 0) == 0 { return 0 } }
815 if step != 0 { gp_print(ps, step); gp_emit_c(ps, C_SEMI) }
816 gp_emit_c(ps, C_RBRACE)
817 } }
818 if handled == 0 { if gp_isword(ps, t, "return" as *u8) == 1 {
819 handled = 1
820 gp_take(ps); gp_emit_s(ps, "return" as *u8)
821 if gp_is1(ps, ps[GP_POS], C_SEMI) == 0 {
822 let re: i64 = gp_parse(ps, GP_M_ASSIGN, 0)
823 if re == 0 { return 0 }
824 gp_print(ps, re)
825 }
826 if gp_expect(ps, C_SEMI) == 0 { return 0 }
827 } }
828 if handled == 0 {
829 var bare: i64 = 0
830 if gp_isword(ps, t, "discard" as *u8) == 1 { bare = 1 }
831 if gp_isword(ps, t, "break" as *u8) == 1 { bare = 1 }
832 if gp_isword(ps, t, "continue" as *u8) == 1 { bare = 1 }
833 if bare == 1 {
834 handled = 1
835 gp_emit_tok(ps, t); gp_take(ps)
836 if gp_expect(ps, C_SEMI) == 0 { return 0 }
837 }
838 }
839 if handled == 0 { if gp_kind(ps, t) == GT_K_IDENT { if gp_kind(ps, t + 1) == GT_K_IDENT {
840 // a declaration: [qualifiers] T name[N] [= expr] (, name[N] [= expr])* ; -- one declarator per emitted statement
841 handled = 1
842 var ty: i64 = t
843 var gq: i64 = 1
844 while gq == 1 {
845 var isq: i64 = 0
846 if gp_isword(ps, ty, "const" as *u8) == 1 { isq = 1 }
847 if gp_isword(ps, ty, "highp" as *u8) == 1 { isq = 1 }
848 if gp_isword(ps, ty, "mediump" as *u8) == 1 { isq = 1 }
849 if gp_isword(ps, ty, "lowp" as *u8) == 1 { isq = 1 }
850 if isq == 1 { gp_emit_tok(ps, ty); ty = ty + 1 } else { gq = 0 }
851 }
852 if gp_kind(ps, ty) != GT_K_IDENT { return gp_fail(ps, ty) }
853 gp_emit_tok(ps, ty)
854 ps[GP_POS] = ty + 1
855 var gd: i64 = 1
856 while gd == 1 {
857 let nm: i64 = ps[GP_POS]
858 if gp_kind(ps, nm) != GT_K_IDENT { return gp_fail(ps, nm) }
859 gp_emit_tok(ps, nm); gp_take(ps)
860 if gp_is1(ps, ps[GP_POS], C_LBR) == 1 {
861 gp_take(ps); gp_emit_c(ps, C_LBR)
862 let an: i64 = gp_parse(ps, GP_M_ASSIGN, 0)
863 if an == 0 { return 0 }
864 gp_print(ps, an)
865 if gp_expect(ps, C_RBR) == 0 { return 0 }
866 }
867 if gp_is1(ps, ps[GP_POS], C_EQ) == 1 {
868 gp_take(ps); gp_emit_c(ps, C_EQ)
869 let iv: i64 = gp_parse(ps, GP_M_ASSIGN, 0)
870 if iv == 0 { return 0 }
871 gp_print(ps, iv)
872 }
873 if gp_is1(ps, ps[GP_POS], C_COMMA) == 1 { gp_take(ps); gp_emit_c(ps, C_SEMI); gp_emit_tok(ps, ty) } else {
874 if gp_expect(ps, C_SEMI) == 0 { return 0 }
875 gd = 0
876 }
877 }
878 } } }
879 if handled == 0 {
880 let ex: i64 = gp_parse(ps, GP_M_ASSIGN, 0)
881 if ex == 0 { return 0 }
882 gp_print(ps, ex)
883 if gp_expect(ps, C_SEMI) == 0 { return 0 }
884 }
885 if wrap == 1 { gp_emit_c(ps, C_RBRACE) }
886 return 1
887}
888// module scope: tokens copied (numbers canonicalised); a block is parsed as statements; the right side of `=` canonicalised
889func gp_toplevel(ps: *i64) -> i64 {
890 while ps[GP_POS] < ps[GP_NTOK] {
891 let t: i64 = ps[GP_POS]
892 if gp_is1(ps, t, C_LBRACE) == 1 { if gp_stmt(ps, 0) == 0 { return 0 } } else {
893 if gp_is1(ps, t, C_EQ) == 1 {
894 gp_take(ps); gp_emit_c(ps, C_EQ)
895 let e: i64 = gp_parse(ps, GP_M_TERN, 0)
896 if e == 0 { return 0 }
897 gp_print(ps, e)
898 } else { gp_emit_tok(ps, t); gp_take(ps) }
899 }
900 if ps[GP_ERR] >= 0 { return 0 }
901 }
902 return 1
903}
904// canonical text of src[0,n) into out (cap bytes). Returns the canonical length, or -1 refused: box[0] = byte offset of the
905// refusing token (n when the input ended early), box[1] = its length, box[2] = raw tokens, box[3] = expanded tokens,
906// box[4] = compounds refused by the expander, box[5] = 1 when the output bound was hit (never a silent truncation).
907func gt_canon_text(src: *u8, n: i64, out: *u8, cap: i64, box: *i64) -> i64 {
908 let toks: *i64 = sys_mmap(GT_TW * 8 * (n + 1)) as *i64
909 let k: i64 = gt_tokenize(src, n, toks)
910 let xt: *i64 = sys_mmap(GT_TW * 8 * (3 * k + 3)) as *i64
911 let bx: *i64 = sys_mmap(16) as *i64
912 let e: i64 = gt_expand(src, toks, k, xt, bx)
913 box[0] = 0 - 1; box[1] = 0; box[2] = k; box[3] = e; box[4] = bx[0]; box[5] = 0
914 let ps: *i64 = sys_mmap(GP_SLOTS * 8) as *i64
915 // the parser reads the RAW tokens: compound assignment is parsed structurally (gp_parse), never token-expanded, so the
916 // right-hand side keeps its own tree; the expander runs only to report expanded_a/expanded_b beside the raw count
917 ps[GP_POS] = 0; ps[GP_NTOK] = k; ps[GP_OUT] = 0; ps[GP_ERR] = 0 - 1; ps[GP_NCOUNT] = 1
918 ps[GP_SRC] = src as i64; ps[GP_TOKS] = toks as i64
919 ps[GP_NODES] = sys_mmap(GN_SLOTS * 8 * (3 * k + 16)) as i64
920 ps[GP_OUTBUF] = out as i64; ps[GP_CAP] = cap; ps[GP_OVER] = 0
921 ps[GP_TMP] = sys_mmap(GT_CANON_CAP) as i64
922 let ok: i64 = gp_toplevel(ps)
923 if ps[GP_OVER] == 1 { box[5] = 1; return 0 - 1 }
924 if ok == 0 {
925 var et: i64 = ps[GP_ERR]
926 if et < 0 { et = ps[GP_POS] }
927 if et >= k { box[0] = n; box[1] = 0 } else { box[0] = toks[et * GT_TW]; box[1] = toks[et * GT_TW + 1] }
928 return 0 - 1
929 }
930 return ps[GP_OUT]
931}
932// THE CANONICAL RULER: canonicalise both sides, then run the incumbent token differ over the two canonical texts.
933func gt_diff_canon(a: *u8, na: i64, b: *u8, nb: i64, r: *i64) -> i64 {
934 let capa: i64 = GT_CANON_OUT_MULT * na + GT_CANON_OUT_HEAD
935 let capb: i64 = GT_CANON_OUT_MULT * nb + GT_CANON_OUT_HEAD
936 let ca: *u8 = sys_mmap(capa)
937 let cb: *u8 = sys_mmap(capb)
938 let ba: *i64 = sys_mmap(64) as *i64
939 let bb: *i64 = sys_mmap(64) as *i64
940 let la: i64 = gt_canon_text(a, na, ca, capa, ba)
941 let lb: i64 = gt_canon_text(b, nb, cb, capb, bb)
942 r[GT_R_TOKA] = ba[2]; r[GT_R_TOKB] = bb[2]; r[GT_R_EXPA] = ba[3]; r[GT_R_EXPB] = bb[3]
943 r[GT_R_REFUSED] = ba[4] + bb[4]
944 r[GT_R_ERRA] = ba[0]; r[GT_R_ERRLA] = ba[1]; r[GT_R_ERRB] = bb[0]; r[GT_R_ERRLB] = bb[1]
945 r[GT_R_OVER] = ba[5] + bb[5]
946 r[GT_R_SRCA] = a as i64; r[GT_R_SRCB] = b as i64
947 r[GT_R_CAPTR_A] = ca as i64; r[GT_R_CAPTR_B] = cb as i64
948 r[GT_R_CALEN_A] = 0; r[GT_R_CALEN_B] = 0
949 if la >= 0 { r[GT_R_CALEN_A] = la }
950 if lb >= 0 { r[GT_R_CALEN_B] = lb }
951 r[GT_R_CANA] = 0; r[GT_R_CANB] = 0; r[GT_R_FIRSTDIFF] = 0 - 1
952 if la < 0 { r[GT_R_VERDICT] = GT_V_UNREADABLE; return GT_V_UNREADABLE }
953 if lb < 0 { r[GT_R_VERDICT] = GT_V_UNREADABLE; return GT_V_UNREADABLE }
954 let r2: *i64 = sys_mmap(GT_R_SLOTS * 8) as *i64
955 let v: i64 = gt_diff(ca, la, cb, lb, r2)
956 r[GT_R_CANA] = r2[GT_R_EXPA]; r[GT_R_CANB] = r2[GT_R_EXPB]
957 r[GT_R_FIRSTDIFF] = r2[GT_R_FIRSTDIFF]
958 r[GT_R_VERDICT] = v
959 return v
960}
961// THE PER-SLICE ACCEPT SIGNAL. A transcribed slice is emitted as a WHOLE function, so its canonical stream ends with the closing
962// brace of main while the hand text continues: the shorter side is a prefix of the longer UP TO that brace. slice_prefix=1 when
963// IDENTICAL, when the shorter is a pure prefix (first_diff at its length), or when the first difference is the shorter's LAST
964// token and that token is `}`; 0 for every real divergence and for an unreadable side. Derived from the canonical texts the
965// ruler already holds, so the report and the gate read the same predicate.
966func gt_slice_prefix(r: *i64) -> i64 {
967 if r[GT_R_VERDICT] == GT_V_UNREADABLE { return 0 }
968 if r[GT_R_VERDICT] == GT_V_IDENTICAL { return 1 }
969 if r[GT_R_CANA] == r[GT_R_CANB] { return 0 }
970 var mn: i64 = r[GT_R_CANA]
971 var sp: i64 = r[GT_R_CAPTR_A]
972 var sl: i64 = r[GT_R_CALEN_A]
973 if r[GT_R_CANB] < mn { mn = r[GT_R_CANB]; sp = r[GT_R_CAPTR_B]; sl = r[GT_R_CALEN_B] }
974 if r[GT_R_FIRSTDIFF] == mn { return 1 }
975 if r[GT_R_FIRSTDIFF] != mn - 1 { return 0 }
976 if sl < 2 { return 0 }
977 let s: *u8 = sp as *u8
978 if (s[sl - 1] as i64) != C_SP { return 0 }
979 if (s[sl - 2] as i64) != C_RBRACE { return 0 }
980 return 1
981}
982func gt_report_canon(fd: i64, r: *i64) -> i64 {
983 gt_fputs(fd, "tokens_a=" as *u8); gt_fputn(fd, r[GT_R_TOKA])
984 gt_fputs(fd, " tokens_b=" as *u8); gt_fputn(fd, r[GT_R_TOKB])
985 gt_fputs(fd, " expanded_a=" as *u8); gt_fputn(fd, r[GT_R_EXPA])
986 gt_fputs(fd, " expanded_b=" as *u8); gt_fputn(fd, r[GT_R_EXPB])
987 gt_fputs(fd, " expand_refused=" as *u8); gt_fputn(fd, r[GT_R_REFUSED])
988 gt_fputs(fd, " canon_a=" as *u8); gt_fputn(fd, r[GT_R_CANA])
989 gt_fputs(fd, " canon_b=" as *u8); gt_fputn(fd, r[GT_R_CANB])
990 gt_fputs(fd, " canon_refused_a=" as *u8); gt_fputn(fd, r[GT_R_ERRA])
991 gt_fputs(fd, " canon_refused_b=" as *u8); gt_fputn(fd, r[GT_R_ERRB])
992 gt_fputs(fd, " canon_overflow=" as *u8); gt_fputn(fd, r[GT_R_OVER])
993 gt_fputs(fd, " first_diff=" as *u8); gt_fputn(fd, r[GT_R_FIRSTDIFF])
994 var pre: i64 = 0
995 if r[GT_R_FIRSTDIFF] >= 0 {
996 var mn: i64 = r[GT_R_CANA]
997 if r[GT_R_CANB] < mn { mn = r[GT_R_CANB] }
998 if r[GT_R_FIRSTDIFF] == mn { if r[GT_R_CANA] != r[GT_R_CANB] { pre = 1 } }
999 }
1000 gt_fputs(fd, " shorter_is_prefix=" as *u8); gt_fputn(fd, pre)
1001 gt_fputs(fd, " slice_prefix=" as *u8); gt_fputn(fd, gt_slice_prefix(r))
1002 if r[GT_R_FIRSTDIFF] >= 0 {
1003 gt_fputs(fd, " a_tok=" as *u8); gt_put_expanded_tok(fd, r[GT_R_CAPTR_A] as *u8, r[GT_R_CALEN_A], r[GT_R_FIRSTDIFF])
1004 gt_fputs(fd, " b_tok=" as *u8); gt_put_expanded_tok(fd, r[GT_R_CAPTR_B] as *u8, r[GT_R_CALEN_B], r[GT_R_FIRSTDIFF])
1005 }
1006 if r[GT_R_ERRA] >= 0 { gt_fputs(fd, " canon_refused_a_tok=" as *u8); sys_write(fd, ((r[GT_R_SRCA] + r[GT_R_ERRA]) as *u8), r[GT_R_ERRLA]) }
1007 if r[GT_R_ERRB] >= 0 { gt_fputs(fd, " canon_refused_b_tok=" as *u8); sys_write(fd, ((r[GT_R_SRCB] + r[GT_R_ERRB]) as *u8), r[GT_R_ERRLB]) }
1008 gt_fputs(fd, "\n" as *u8)
1009 if r[GT_R_VERDICT] == GT_V_IDENTICAL { gt_fputs(fd, "verdict=IDENTICAL\n" as *u8) } else {
1010 if r[GT_R_VERDICT] == GT_V_UNREADABLE { gt_fputs(fd, "verdict=UNREADABLE\n" as *u8) } else { gt_fputs(fd, "verdict=DIFFERS\n" as *u8) }
1011 }
1012 return 0
1013}