nx_srcfmt.nx source
↩ module page · 1096 lines · 51757 B
1// nx_srcfmt.nx -- THE CANONICAL SOURCE FORMATTER for NishiLang (lang.plan rung LN9, watch symbol
2// srcfmt_emit). One style, no options -- the gofmt / zig fmt / elm-format shape. The style was not
3// chosen, it was MEASURED: every rule below is the majority convention of the whole runtime corpus
4// (18,561 files, 2.97 M lines, census 2026-08-23; re-measure any time with `nx_srcfmt census`, which
5// uses this same scanner so the ruler and the rule cannot drift apart):
6// indent 4 spaces per brace depth (2,216,007 lines are multiples of 4 vs 9,644 that are not), tabs
7// never (0 tab-indented lines); a continuation line inside an unclosed ( or [ takes base + 4
8// (20,695 vs 7,210 at +0 vs 4,536 at +8), except a line that opens with ) or ] (base)
9// `{` stays on the line that opens it (179,575 vs 2 own-line); `} else {` on one line (33,968 vs
10// 5,444 split) -- a lone `}` line followed by an `else` line is JOINED
11// `, ` (1,098,119 vs 110,794) ; `; ` (455,500 vs 26,006) ; ` = ` and every binary operator spaced
12// (955,786 vs 276,475 for `=`; 385,897 vs 134,264 for + == != <= >= && ||) ; ` -> ` (103,535 vs 1)
13// `x: T` (781,324 vs 2,337; a space BEFORE the colon 10) ; `*u8` `&x` `-1` `!b` unary glued
14// (700,588 vs 2 for the type star) ; `f(` (966,045 vs 19,129) but `if (` `return (` `while (`
15// keyword-then-paren spaced (15,689 vs 1,315 for `if`) ; `{ stmt }` one-line blocks padded
16// (338,077 vs 11,657) and `{}` empty blocks closed (457 vs 143) ; `a..b` `a.b` `a::b` glued
17// (4,644 vs 91 for `..`)
18// struct field TYPE COLUMNS are aligned (measured 2026-08-23: of 1,712 multi-field struct runs,
19// 1,340 column-align their types, 193 single-space, 179 ragged) -- the type starts one space past
20// the run's longest field name; a blank line or a comment breaks the run. This is the ONE place a
21// token's spacing depends on other lines, and it is still whitespace between tokens so the oracle
22// and the byte-identical-build proof are untouched.
23// const / static / let / var declarations are NOT aligned -- single space after the colon. The
24// corpus does NOT decisively align these the way it aligns struct fields: const runs split 4,234
25// aligned vs 4,760 single (single is the plurality), static 47 vs 174 single, and body-level
26// let/var are single-space 437,751 vs 6,278. Where the corpus has no clear majority the simpler
27// rule wins, so aligned const blocks reformat to single-space -- expected, measured churn.
28// LF line endings (2,524 of 18,801 files carried CRLF), no trailing whitespace (1,914 lines had it),
29// blank-line runs collapsed to one (348 runs of two or more), exactly one final newline (53 files
30// had none)
31// PRESERVED BYTE-FOR-BYTE, by construction: every string literal (including the multi-line literals
32// this dialect permits -- 205 files carry one), every comment's text, every @-directive line
33// (@macro bodies through their real extent, found by the tokenizer's own lexm_body_end), and the
34// whitespace run that separates code from a trailing `//` comment (the corpus aligns those by column
35// -- widths 2..6 are all common -- so canonicalising them would destroy deliberate alignment; this is
36// a DECLARED floor, not an oversight).
37//
38// WHY THE TOKENIZER IS THE VERIFIER AND NOT THE EMITTER (measured, nx_tokenizer.nx): lex_source STRIPS
39// comments (skip_ws_comments), PRE-EXPANDS @macro into a new buffer (so token positions no longer
40// address the source), and CONSUMES @ifdef/@else/@endif branches at lex time (inactive code never
41// becomes a token). A formatter driven by that stream would drop comments, directives and dead
42// branches. So the emitter is a comment- and directive-aware whitespace scanner that never touches a
43// token's bytes, and lex_source is the ORACLE: srcfmt_verify lexes the input and the output and
44// refuses the output unless the two token streams are identical (kind, value, string bytes). A
45// formatter that could change a program is the worst defect it can have; here that is impossible by
46// construction -- nothing is written unless the oracle agrees.
47//
48// Line structure MAY change (the else-join, blank-run collapse, CRLF) because the default toolchain
49// embeds no line numbers: measured 2026-08-23, a blank-line insert and a CRLF rewrite of
50// nx_option_enforce_ok.nx both compiled to byte-identical asm (0 .loc/.file directives).
51//
52// CLI
53// nx_srcfmt <file> formatted text -> stdout; exit 0 = already canonical, 1 = would change
54// nx_srcfmt <file> --check no text, one verdict line; same exit codes
55// nx_srcfmt census [dir] per-rule agreement census over the corpus (gk_corpus_scan, or one dir)
56// exit 2 usage / unreadable ; exit 3 REFUSED: the oracle saw a different token stream (nothing
57// written) ; exit 4 REFUSED: output capacity (derived from the input; cannot happen, announced)
58// DECLARED FLOOR (measured 2026-08-23, full corpus): 6 of 18,815 files are REFUSED (exit 3), not
59// formatted -- every one uses a metaprogramming construct the tokenizer EXPANDS before parsing that
60// this formatter does not model: `@for(i in 0..N) { ... $i ... }` range-expansion (nx_for_probe,
61// nx_forlist_probe) and `@macro` bodies using `$$gensym` / `$param` substitution (nx_macro_v2/v5/v7/v8
62// _test). The oracle catches the token-stream difference and the formatter DECLINES rather than emit a
63// program-changing result -- the safe direction by construction. The other 18,809 files format
64// idempotently and build byte-identically. Extending @for/gensym preservation is a follow-on rung.
65// NEVER writes in place -- in-place is a separate verb with CAS, not shipped here.
66// license_tier: ORIGINAL No hw writes (Rule 26).
67import "nx_syscalls.nx"
68import "nx_tokenizer.nx"
69import "nx_gatekit_lib.nx"
70
71const SF_INDENT: i64 = 4 // the measured unit: 2,216,007 multiple-of-4 lines vs 9,644 other
72const SF_NAME_SLOT: i64 = 128 // per-path byte slot for the corpus census (same as MP_SLOT)
73const SF_CORPUS_CAP: i64 = 32768 // corpus is 18,561; 16,384 would REFUSE (same bar as nx_srclint)
74const SF_CENSUS_ROWS: i64 = 24 // rule rows the census verb reports
75
76// ---- previous-token classes (the only state the spacing rules read) ----
77const SP_NONE: i64 = 0 // start of line
78const SP_IDENT: i64 = 1
79const SP_KW: i64 = 2 // keyword that is not an operand (if while return as let ...)
80const SP_OPERAND: i64 = 3 // int / float / string / true / false
81const SP_LPAREN: i64 = 4
82const SP_RPAREN: i64 = 5
83const SP_LBRACK: i64 = 6
84const SP_RBRACK: i64 = 7
85const SP_LBRACE: i64 = 8
86const SP_RBRACE: i64 = 9
87const SP_COMMA: i64 = 10
88const SP_SEMI: i64 = 11
89const SP_COLON: i64 = 12
90const SP_GLUE: i64 = 13 // . .. :: -- no space either side
91const SP_BINOP: i64 = 14 // every binary operator, = -> => ? and the ternary :
92const SP_UNARY: i64 = 15 // - * & + ! ~ used as a prefix: nothing after it
93const SP_ATTR: i64 = 16 // @priv and any @name that is not a directive; #name
94
95// ---- census rule rows ----
96const CR_INDENT: i64 = 0
97const CR_TRAIL: i64 = 1
98const CR_CRLF: i64 = 2
99const CR_COMMA: i64 = 3
100const CR_SEMI: i64 = 4
101const CR_BINOP: i64 = 5
102const CR_ASSIGN: i64 = 6
103const CR_ARROW: i64 = 7
104const CR_COLON: i64 = 8
105const CR_UNARY: i64 = 9
106const CR_CALLPAREN: i64 = 10
107const CR_KWPAREN: i64 = 11
108const CR_LBRACE: i64 = 12
109const CR_BLOCKPAD: i64 = 13
110const CR_EMPTYBLOCK: i64 = 14
111const CR_GLUE: i64 = 15
112const CR_ELSEJOIN: i64 = 16
113const CR_BLANKRUN: i64 = 17
114const CR_CONT: i64 = 18
115const CR_FINALNL: i64 = 19
116const CR_TYPESUFFIX: i64 = 20
117const CR_BRACEOWN: i64 = 21
118const CR_STRUCTALIGN: i64 = 22
119
120struct Sf {
121 src: *u8,
122 n: i64,
123 out: *u8,
124 cap: i64,
125 o: i64,
126 depth: i64, // brace depth
127 pdepth: i64, // paren + bracket depth
128 tern: i64, // pending ? on this line (ternary colon detection)
129 prev: i64, // SP_* class of the last emitted token on this line
130 line_o: i64, // output offset where the current line's content began (after indent)
131 prev_nl: i64, // output offset of the newline that ended the previous emitted line
132 prev_kind: i64, // 0 none 1 code 2 code+comment 3 comment-only 4 blank 5 raw
133 prev_lone_rbrace: i64,
134 blank_pending: i64, // a blank line is owed before the next content line
135 any_content: i64, // something non-blank has been emitted
136 overflow: i64,
137 cen: *i64, // census counters (2 per rule: agree, disagree) or 0
138 tok_line: i64, // first token on the current line seen
139 in_struct_at: i64, // brace depth of the current struct body, or -1
140 pending_struct: i64, // the `struct` keyword was seen; the next `{` opens a struct body
141 run_target: i64, // aligned type column for the current field run (max name width), 0 = none
142 line_ntok: i64, // tokens emitted on the current line so far
143 line_tok0_ident: i64,// the line's first token was a plain identifier
144 line_name_len: i64, // byte length of that first identifier
145 align_pending: i64, // spaces to emit before the next token (struct field type), 0 = use sf_sep
146 line_had_field_colon: i64,
147 line_start_src: i64,
148}
149
150static g_sf_verify_tok: i64
151static g_sf_verify_line: i64
152
153// ---- small helpers ----
154func sf_at(s: *u8, i: i64) -> i64 { return s[i] as i64 }
155func sf_is_ws(c: i64) -> i64 { if c == 32 { return 1 } if c == 9 { return 1 } if c == 13 { return 1 } return 0 }
156func sf_is_idstart(c: i64) -> i64 { if is_alpha(c) == 1 { return 1 } if c == 95 { return 1 } return 0 }
157func sf_is_idch(c: i64) -> i64 { if is_alnum(c) == 1 { return 1 } if c == 95 { return 1 } return 0 }
158
159func sf_put(F: *Sf, c: i64) -> i64 {
160 if F.o >= F.cap { F.overflow = 1; return 0 }
161 let ob: *u8 = F.out
162 ob[F.o] = c as u8
163 F.o = F.o + 1
164 return 0
165}
166func sf_put_raw(F: *Sf, a: i64, b: i64) -> i64 {
167 var i: i64 = a
168 while i < b { sf_put(F, sf_at(F.src, i)); i = i + 1 }
169 return 0
170}
171func sf_spaces(F: *Sf, k: i64) -> i64 { var i: i64 = 0; while i < k { sf_put(F, 32); i = i + 1 } return 0 }
172
173// keyword table (the lexer's TK_FUNC..TK_THEN set). 2 = operand keyword (true/false), 1 = keyword, 0 = ident.
174func sf_kw_class(s: *u8, a: i64, b: i64) -> i64 {
175 let n: i64 = b - a
176 let p: *u8 = ((s as i64) + a) as *u8
177 if n == 2 { if streq_n(p, "if\x00" as *u8, 2) == 1 { return 1 } if streq_n(p, "in\x00" as *u8, 2) == 1 { return 1 } if streq_n(p, "as\x00" as *u8, 2) == 1 { return 1 } }
178 if n == 3 { if streq_n(p, "let\x00" as *u8, 3) == 1 { return 1 } if streq_n(p, "var\x00" as *u8, 3) == 1 { return 1 } if streq_n(p, "for\x00" as *u8, 3) == 1 { return 1 } }
179 if n == 4 {
180 if streq_n(p, "func\x00" as *u8, 4) == 1 { return 1 } if streq_n(p, "else\x00" as *u8, 4) == 1 { return 1 }
181 if streq_n(p, "loop\x00" as *u8, 4) == 1 { return 1 } if streq_n(p, "true\x00" as *u8, 4) == 1 { return 2 }
182 if streq_n(p, "enum\x00" as *u8, 4) == 1 { return 1 } if streq_n(p, "then\x00" as *u8, 4) == 1 { return 1 }
183 }
184 if n == 5 {
185 if streq_n(p, "const\x00" as *u8, 5) == 1 { return 1 } if streq_n(p, "while\x00" as *u8, 5) == 1 { return 1 }
186 if streq_n(p, "break\x00" as *u8, 5) == 1 { return 1 } if streq_n(p, "false\x00" as *u8, 5) == 1 { return 2 }
187 if streq_n(p, "match\x00" as *u8, 5) == 1 { return 1 }
188 }
189 if n == 6 {
190 if streq_n(p, "static\x00" as *u8, 6) == 1 { return 1 } if streq_n(p, "return\x00" as *u8, 6) == 1 { return 1 }
191 if streq_n(p, "struct\x00" as *u8, 6) == 1 { return 1 } if streq_n(p, "extern\x00" as *u8, 6) == 1 { return 1 }
192 if streq_n(p, "import\x00" as *u8, 6) == 1 { return 1 }
193 }
194 if n == 8 { if streq_n(p, "continue\x00" as *u8, 8) == 1 { return 1 } if streq_n(p, "comptime\x00" as *u8, 8) == 1 { return 1 } }
195 return 0
196}
197
198// Is the text at i (just after '@') one of the lexer's directives? Mirrors _lex_handle_preprocessor's
199// table exactly: macro endif undef if( elif( else ifdef ifndef. Returns 0, or 1 (line directive),
200// or 2 (@macro: body extent via lexm_body_end).
201func sf_directive(s: *u8, i: i64, n: i64) -> i64 {
202 let p: *u8 = ((s as i64) + i) as *u8
203 if i + 6 <= n { if _sf_eq(p, "macro \x00" as *u8, 6) == 1 { return 2 } }
204 if i + 5 <= n { if _sf_eq(p, "endif\x00" as *u8, 5) == 1 { return 1 } }
205 if i + 6 <= n { if _sf_eq(p, "undef \x00" as *u8, 6) == 1 { return 1 } }
206 if i + 3 <= n { if _sf_eq(p, "if(\x00" as *u8, 3) == 1 { return 1 } }
207 if i + 5 <= n { if _sf_eq(p, "elif(\x00" as *u8, 5) == 1 { return 1 } }
208 if i + 4 <= n { if _sf_eq(p, "else\x00" as *u8, 4) == 1 { return 1 } }
209 if i + 6 <= n { if _sf_eq(p, "ifdef \x00" as *u8, 6) == 1 { return 1 } }
210 if i + 7 <= n { if _sf_eq(p, "ifndef \x00" as *u8, 7) == 1 { return 1 } }
211 return 0
212}
213func _sf_eq(p: *u8, lit: *u8, k: i64) -> i64 {
214 var j: i64 = 0
215 while j < k { if p[j] != lit[j] { return 0 } j = j + 1 }
216 return 1
217}
218
219// End of a string literal that opens at i (src[i] == '"'): index just past the closing quote, or n.
220func sf_string_end(s: *u8, i: i64, n: i64) -> i64 {
221 var j: i64 = i + 1
222 var go: i64 = 1
223 while go == 1 {
224 if j >= n { go = 0 } else {
225 let c: i64 = sf_at(s, j)
226 if c == 92 { j = j + 2 } else {
227 if c == 34 { j = j + 1; go = 0 } else { j = j + 1 }
228 }
229 }
230 }
231 if j > n { j = n }
232 return j
233}
234
235// End of a @macro directive that starts at i (src[i] == '@'): the body extent the lexer itself uses.
236func sf_macro_end(s: *u8, i: i64, n: i64) -> i64 {
237 var np: i64 = i + 7
238 var g1: i64 = 1
239 while g1 == 1 { if np < n { if sf_at(s, np) == 32 { np = np + 1 } else { g1 = 0 } } else { g1 = 0 } }
240 var ne: i64 = np
241 var g2: i64 = 1
242 while g2 == 1 { if ne < n { if is_alnum(sf_at(s, ne)) == 1 { ne = ne + 1 } else { g2 = 0 } } else { g2 = 0 } }
243 var bp: i64 = ne
244 var g3: i64 = 1
245 while g3 == 1 { if bp < n { if sf_at(s, bp) == 32 { bp = bp + 1 } else { g3 = 0 } } else { g3 = 0 } }
246 let be: i64 = lexm_body_end(s, n, bp)
247 if be < bp { return bp }
248 if be > n { return n }
249 return be
250}
251
252// End of the current line: index of the '\n' (or n).
253func sf_eol(s: *u8, i: i64, n: i64) -> i64 {
254 var j: i64 = i
255 var go: i64 = 1
256 while go == 1 { if j < n { if sf_at(s, j) == 10 { go = 0 } else { j = j + 1 } } else { go = 0 } }
257 return j
258}
259
260// Strip trailing spaces / tabs / CR from [a, b).
261func sf_rtrim(s: *u8, a: i64, b: i64) -> i64 {
262 var e: i64 = b
263 while e > a { if sf_is_ws(sf_at(s, e - 1)) == 1 { e = e - 1 } else { return e } }
264 return e
265}
266
267func sf_cen(F: *Sf, rule: i64, agree: i64) -> i64 {
268 if (F.cen as i64) == 0 { return 0 }
269 let cen: *i64 = F.cen
270 if agree == 1 { cen[rule * 2] = cen[rule * 2] + 1 } else { cen[rule * 2 + 1] = cen[rule * 2 + 1] + 1 }
271 return 0
272}
273
274// Max field-name length over the contiguous run of `ident : type` lines starting at line-start `j`.
275// A field line is: optional indent, an identifier, optional spaces, ':'. The run breaks at the first
276// line that is not a field line (a blank line, a comment, the closing brace, or any other statement).
277// 0 means `j` itself is not a field line. This is what makes the alignment column depend on the run.
278func sf_field_run_target(s: *u8, j: i64, n: i64) -> i64 {
279 var best: i64 = 0
280 var p: i64 = j
281 var go: i64 = 1
282 while go == 1 {
283 // p is at the first non-ws byte of a line
284 if p >= n { go = 0 } else {
285 let c: i64 = sf_at(s, p)
286 if sf_is_idstart(c) == 0 { go = 0 } else {
287 var e: i64 = p + 1
288 var ge: i64 = 1
289 while ge == 1 { if e < n { if sf_is_idch(sf_at(s, e)) == 1 { e = e + 1 } else { ge = 0 } } else { ge = 0 } }
290 var q: i64 = e
291 var gq: i64 = 1
292 while gq == 1 { if q < n { if sf_at(s, q) == 32 { q = q + 1 } else { gq = 0 } } else { gq = 0 } }
293 if q >= n { go = 0 } else {
294 if sf_at(s, q) != 58 { go = 0 } else {
295 let nl: i64 = e - p
296 if nl > best { best = nl }
297 // advance to the next line's first non-ws byte
298 let eol: i64 = sf_eol(s, p, n)
299 if eol >= n { go = 0 } else {
300 var np: i64 = eol + 1
301 var gn: i64 = 1
302 while gn == 1 { if np < n { if sf_is_ws(sf_at(s, np)) == 1 { np = np + 1 } else { gn = 0 } } else { gn = 0 } }
303 p = np
304 }
305 }
306 }
307 }
308 }
309 }
310 return best
311}
312
313// ---- the spacing rule: how many spaces go between the previous token class and this one ----
314// cur_cls is the SP_ class the current token WILL have; cur_kind distinguishes punctuation.
315func sf_sep(F: *Sf, prev: i64, cur: i64) -> i64 {
316 if prev == SP_NONE { return 0 }
317 if cur == SP_COMMA { return 0 }
318 if cur == SP_SEMI { return 0 }
319 if cur == SP_RPAREN { return 0 }
320 if cur == SP_RBRACK { return 0 }
321 if cur == SP_GLUE { return 0 }
322 if cur == SP_COLON { return 0 }
323 if prev == SP_LPAREN { return 0 }
324 if prev == SP_LBRACK { return 0 }
325 if prev == SP_GLUE { return 0 }
326 if prev == SP_UNARY { return 0 }
327 if prev == SP_ATTR { if cur == SP_LPAREN { return 0 } return 1 }
328 if prev == SP_LBRACE { if cur == SP_RBRACE { return 0 } return 1 }
329 if cur == SP_RBRACE { return 1 }
330 if cur == SP_LBRACE { return 1 }
331 if cur == SP_LPAREN {
332 if prev == SP_IDENT { return 0 }
333 if prev == SP_RPAREN { return 0 }
334 if prev == SP_RBRACK { return 0 }
335 return 1
336 }
337 if cur == SP_LBRACK {
338 if prev == SP_IDENT { return 0 }
339 if prev == SP_RPAREN { return 0 }
340 if prev == SP_RBRACK { return 0 }
341 return 1
342 }
343 if prev == SP_RBRACK { if cur == SP_IDENT { return 0 } }
344 return 1
345}
346
347// Which census rule a (prev, cur) pair belongs to -- so the census can say, per rule, how much of the
348// corpus already agrees. -1 = not a census-tracked pair.
349func sf_cen_rule(prev: i64, cur: i64, kind_cur: i64, kind_prev: i64) -> i64 {
350 if prev == SP_COMMA { return CR_COMMA }
351 if prev == SP_SEMI { return CR_SEMI }
352 if cur == SP_UNARY { return CR_UNARY }
353 if prev == SP_UNARY { return CR_UNARY }
354 if kind_cur == 45 { return CR_ASSIGN }
355 if kind_prev == 45 { return CR_ASSIGN }
356 if kind_cur == 61 { return CR_ARROW }
357 if kind_prev == 61 { return CR_ARROW }
358 if cur == SP_BINOP { return CR_BINOP }
359 if prev == SP_BINOP { return CR_BINOP }
360 if cur == SP_COLON { return CR_COLON }
361 if prev == SP_COLON { return CR_COLON }
362 if cur == SP_LPAREN { if prev == SP_KW { return CR_KWPAREN } if prev == SP_IDENT { return CR_CALLPAREN } }
363 if cur == SP_LBRACE { return CR_LBRACE }
364 if prev == SP_LBRACE { if cur == SP_RBRACE { return CR_EMPTYBLOCK } return CR_BLOCKPAD }
365 if cur == SP_RBRACE { return CR_BLOCKPAD }
366 if cur == SP_GLUE { return CR_GLUE }
367 if prev == SP_GLUE { return CR_GLUE }
368 if prev == SP_RBRACK { if cur == SP_IDENT { return CR_TYPESUFFIX } }
369 return 0 - 1
370}
371
372// Emit the separator for a token whose class is `cur`, given the raw gap [gap_a, gap_b) in the source.
373func sf_emit_sep(F: *Sf, cur: i64, kind_cur: i64, kind_prev: i64, gap_a: i64, gap_b: i64) -> i64 {
374 if F.align_pending > 0 {
375 let want: i64 = F.align_pending
376 F.align_pending = 0
377 var raw2: i64 = gap_b - gap_a
378 if raw2 == want { sf_cen(F, CR_STRUCTALIGN, 1) } else { sf_cen(F, CR_STRUCTALIGN, 0) }
379 sf_spaces(F, want)
380 return 0
381 }
382 let k: i64 = sf_sep(F, F.prev, cur)
383 let r: i64 = sf_cen_rule(F.prev, cur, kind_cur, kind_prev)
384 if r >= 0 {
385 if F.prev != SP_NONE {
386 var raw: i64 = gap_b - gap_a
387 if raw > 1 { raw = 2 }
388 if raw == k { sf_cen(F, r, 1) } else { sf_cen(F, r, 0) }
389 }
390 }
391 sf_spaces(F, k)
392 return 0
393}
394
395// Is the operator at s[i] (one of - * & +) binary here? Binary iff the previous token ends an operand.
396func sf_is_binary(F: *Sf) -> i64 {
397 if F.prev == SP_IDENT { return 1 }
398 if F.prev == SP_OPERAND { return 1 }
399 if F.prev == SP_RPAREN { return 1 }
400 if F.prev == SP_RBRACK { return 1 }
401 return 0
402}
403
404// Classify the punctuation at s[i]; returns the token length (1 or 2) and writes kind (the lexer's
405// TK number) + the SP_ class through out[0..1].
406func sf_punct(s: *u8, i: i64, n: i64, out: *i64) -> i64 {
407 let c: i64 = sf_at(s, i)
408 var c2: i64 = 0
409 if i + 1 < n { c2 = sf_at(s, i + 1) }
410 if c == 45 { if c2 == 62 { out[0] = 61; out[1] = SP_BINOP; return 2 } } // ->
411 if c == 61 { if c2 == 61 { out[0] = 46; out[1] = SP_BINOP; return 2 } if c2 == 62 { out[0] = 76; out[1] = SP_BINOP; return 2 } } // == =>
412 if c == 33 { if c2 == 61 { out[0] = 47; out[1] = SP_BINOP; return 2 } } // !=
413 if c == 60 { if c2 == 61 { out[0] = 50; out[1] = SP_BINOP; return 2 } if c2 == 60 { out[0] = 59; out[1] = SP_BINOP; return 2 } }
414 if c == 62 { if c2 == 61 { out[0] = 51; out[1] = SP_BINOP; return 2 } if c2 == 62 { out[0] = 60; out[1] = SP_BINOP; return 2 } }
415 if c == 38 { if c2 == 38 { out[0] = 52; out[1] = SP_BINOP; return 2 } }
416 if c == 124 { if c2 == 124 { out[0] = 53; out[1] = SP_BINOP; return 2 } }
417 if c == 46 { if c2 == 46 { out[0] = 62; out[1] = SP_GLUE; return 2 } }
418 if c == 58 { if c2 == 58 { out[0] = 75; out[1] = SP_GLUE; return 2 } }
419 if c == 43 { out[0] = 40; out[1] = SP_BINOP; return 1 }
420 if c == 45 { out[0] = 41; out[1] = SP_BINOP; return 1 }
421 if c == 42 { out[0] = 42; out[1] = SP_BINOP; return 1 }
422 if c == 47 { out[0] = 43; out[1] = SP_BINOP; return 1 }
423 if c == 37 { out[0] = 44; out[1] = SP_BINOP; return 1 }
424 if c == 61 { out[0] = 45; out[1] = SP_BINOP; return 1 }
425 if c == 60 { out[0] = 48; out[1] = SP_BINOP; return 1 }
426 if c == 62 { out[0] = 49; out[1] = SP_BINOP; return 1 }
427 if c == 33 { out[0] = 54; out[1] = SP_UNARY; return 1 }
428 if c == 38 { out[0] = 55; out[1] = SP_BINOP; return 1 }
429 if c == 124 { out[0] = 56; out[1] = SP_BINOP; return 1 }
430 if c == 94 { out[0] = 57; out[1] = SP_BINOP; return 1 }
431 if c == 126 { out[0] = 58; out[1] = SP_UNARY; return 1 }
432 if c == 46 { out[0] = 63; out[1] = SP_GLUE; return 1 }
433 if c == 58 { out[0] = 64; out[1] = SP_COLON; return 1 }
434 if c == 59 { out[0] = 65; out[1] = SP_SEMI; return 1 }
435 if c == 44 { out[0] = 66; out[1] = SP_COMMA; return 1 }
436 if c == 40 { out[0] = 67; out[1] = SP_LPAREN; return 1 }
437 if c == 41 { out[0] = 68; out[1] = SP_RPAREN; return 1 }
438 if c == 123 { out[0] = 69; out[1] = SP_LBRACE; return 1 }
439 if c == 125 { out[0] = 70; out[1] = SP_RBRACE; return 1 }
440 if c == 91 { out[0] = 71; out[1] = SP_LBRACK; return 1 }
441 if c == 93 { out[0] = 72; out[1] = SP_RBRACK; return 1 }
442 if c == 63 { out[0] = 77; out[1] = SP_BINOP; return 1 }
443 // unknown byte: the lexer skips it; we copy it through as an attribute-like glued token
444 out[0] = 0; out[1] = SP_ATTR
445 return 1
446}
447
448// Count the `}` tokens that open a code line (for the indent of that line). Also reports whether the
449// first token is a closing ) or ] (a continuation line that gets base indent) and whether the line is
450// exactly one `{` / the keyword `else`.
451func sf_line_shape(s: *u8, i: i64, n: i64, out: *i64) -> i64 {
452 var j: i64 = i
453 var nclose: i64 = 0
454 var go: i64 = 1
455 out[1] = 0
456 out[2] = 0
457 out[3] = 0
458 while go == 1 {
459 var gw: i64 = 1
460 while gw == 1 { if j < n { if sf_is_ws(sf_at(s, j)) == 1 { j = j + 1 } else { gw = 0 } } else { gw = 0 } }
461 if j < n {
462 if sf_at(s, j) == 125 { nclose = nclose + 1; j = j + 1 } else { go = 0 }
463 } else { go = 0 }
464 }
465 out[0] = nclose
466 if nclose == 0 {
467 if j < n {
468 let c: i64 = sf_at(s, j)
469 if c == 41 { out[1] = 1 }
470 if c == 93 { out[1] = 1 }
471 if c == 123 {
472 let e: i64 = sf_rtrim(s, j + 1, sf_eol(s, j, n))
473 if e == j + 1 { out[2] = 1 }
474 }
475 if c == 101 { if j + 4 <= n { if _sf_eq(((s as i64) + j) as *u8, "else\x00" as *u8, 4) == 1 { var after: i64 = 0; if j + 4 < n { after = sf_at(s, j + 4) } if sf_is_idch(after) == 0 { out[3] = 1 } } } }
476 }
477 }
478 return 0
479}
480
481// Close the current output line: drop trailing whitespace, write '\n', record the line kind.
482func sf_end_line(F: *Sf, kind: i64) -> i64 {
483 var gt: i64 = 1
484 while gt == 1 { if F.o > F.line_o { if sf_is_ws(sf_at(F.out, F.o - 1)) == 1 { F.o = F.o - 1 } else { gt = 0 } } else { gt = 0 } }
485 var lone: i64 = 0
486 if kind == 1 { if F.o == F.line_o + 1 { if sf_at(F.out, F.o - 1) == 125 { lone = 1 } } }
487 // a line that became empty (e.g. only a CR) is a blank line
488 if F.o == F.line_o { if kind != 3 { F.blank_pending = 1; return 0 } }
489 F.prev_nl = F.o
490 sf_put(F, 10)
491 F.prev_kind = kind
492 F.prev_lone_rbrace = lone
493 F.any_content = 1
494 F.tern = 0
495 F.prev = SP_NONE
496 return 0
497}
498
499// Start a content line: pay any owed blank line, then the canonical indent (or a join).
500// first_tok: 1 = `else` keyword, 2 = lone `{`. nclose / closer drive the indent.
501func sf_begin_line(F: *Sf, nclose: i64, closer: i64, first_tok: i64, raw_indent: i64) -> i64 {
502 if F.blank_pending == 1 {
503 F.blank_pending = 0
504 if F.any_content == 1 { sf_put(F, 10); F.prev_kind = 4; F.prev_lone_rbrace = 0 }
505 }
506 var joined: i64 = 0
507 if first_tok == 1 { if F.prev_lone_rbrace == 1 { if F.prev_kind == 1 { joined = 1 } } }
508 if first_tok == 2 { if F.prev_kind == 1 { joined = 1 } }
509 if joined == 1 {
510 // rewind over the newline that ended the previous line and continue it with one space
511 F.o = F.prev_nl
512 F.line_o = F.o
513 if first_tok == 1 { sf_cen(F, CR_ELSEJOIN, 0) } else { sf_cen(F, CR_BRACEOWN, 0) }
514 F.prev = SP_RBRACE
515 if first_tok == 2 { F.prev = SP_IDENT }
516 return 0
517 }
518 var d: i64 = F.depth - nclose
519 if d < 0 { d = 0 }
520 var ind: i64 = d * SF_INDENT
521 if F.pdepth > 0 { if closer == 0 { ind = ind + SF_INDENT; if raw_indent == ind { sf_cen(F, CR_CONT, 1) } else { sf_cen(F, CR_CONT, 0) } } }
522 if raw_indent == ind { sf_cen(F, CR_INDENT, 1) } else { sf_cen(F, CR_INDENT, 0) }
523 sf_spaces(F, ind)
524 F.line_o = F.o
525 F.prev = SP_NONE
526 return 0
527}
528
529// ---- THE EMITTER (watch symbol). Returns the output length, or -1 when `cap` would be exceeded. ----
530func srcfmt_emit(src: *u8, n: i64, out: *u8, cap: i64) -> i64 {
531 let F_raw: *u8 = sys_mmap(256)
532 let F: *Sf = F_raw as *Sf
533 F.src = src; F.n = n; F.out = out; F.cap = cap; F.o = 0
534 F.depth = 0; F.pdepth = 0; F.tern = 0; F.prev = SP_NONE; F.line_o = 0; F.prev_nl = 0
535 F.prev_kind = 0; F.prev_lone_rbrace = 0; F.blank_pending = 0; F.any_content = 0; F.overflow = 0
536 F.cen = 0 as *i64
537 F.tok_line = 0
538 F.in_struct_at = 0 - 1; F.pending_struct = 0; F.run_target = 0
539 F.line_ntok = 0; F.line_tok0_ident = 0; F.line_name_len = 0; F.align_pending = 0
540 F.line_had_field_colon = 0; F.line_start_src = 0
541 sf_run(F)
542 if F.overflow == 1 { return 0 - 1 }
543 return F.o
544}
545
546// Same emitter with census counters attached (the `census` verb); out still receives the text.
547func srcfmt_emit_census(src: *u8, n: i64, out: *u8, cap: i64, cen: *i64) -> i64 {
548 let F_raw: *u8 = sys_mmap(256)
549 let F: *Sf = F_raw as *Sf
550 F.src = src; F.n = n; F.out = out; F.cap = cap; F.o = 0
551 F.depth = 0; F.pdepth = 0; F.tern = 0; F.prev = SP_NONE; F.line_o = 0; F.prev_nl = 0
552 F.prev_kind = 0; F.prev_lone_rbrace = 0; F.blank_pending = 0; F.any_content = 0; F.overflow = 0
553 F.cen = cen
554 F.tok_line = 0
555 F.in_struct_at = 0 - 1; F.pending_struct = 0; F.run_target = 0
556 F.line_ntok = 0; F.line_tok0_ident = 0; F.line_name_len = 0; F.align_pending = 0
557 F.line_had_field_colon = 0; F.line_start_src = 0
558 sf_run(F)
559 if F.overflow == 1 { return 0 - 1 }
560 return F.o
561}
562
563func sf_run(F: *Sf) -> i64 {
564 let s: *u8 = F.src
565 let n: i64 = F.n
566 let shape: *i64 = sys_mmap(64) as *i64
567 let pk: *i64 = sys_mmap(32) as *i64
568 var i: i64 = 0
569 var at_start: i64 = 1
570 var line_has_comment: i64 = 0
571 var gap_a: i64 = 0
572 var kind_prev: i64 = 0
573 var blank_run: i64 = 0
574 var crlf_seen: i64 = 0
575 var line_no: i64 = 1
576 while i < n {
577 if at_start == 1 {
578 // ---- classify the line ----
579 var j: i64 = i
580 var gj: i64 = 1
581 while gj == 1 { if j < n { if sf_is_ws(sf_at(s, j)) == 1 { j = j + 1 } else { gj = 0 } } else { gj = 0 } }
582 let raw_indent: i64 = j - i
583 var c0: i64 = 10
584 if j < n { c0 = sf_at(s, j) }
585 if c0 == 10 {
586 // blank line
587 blank_run = blank_run + 1
588 F.run_target = 0
589 if blank_run >= 2 { sf_cen(F, CR_BLANKRUN, 0) }
590 if j > i { sf_cen(F, CR_TRAIL, 0) } else { sf_cen(F, CR_TRAIL, 1) }
591 F.blank_pending = 1
592 i = j + 1
593 line_no = line_no + 1
594 continue
595 }
596 if blank_run == 1 { sf_cen(F, CR_BLANKRUN, 1) }
597 blank_run = 0
598 var c1: i64 = 0
599 if j + 1 < n { c1 = sf_at(s, j + 1) }
600 if c0 == 47 { if c1 == 47 {
601 // comment-only line: canonical indent, comment text verbatim
602 let e: i64 = sf_eol(s, j, n)
603 let et: i64 = sf_rtrim(s, j, e)
604 if et < e { sf_cen(F, CR_TRAIL, 0) } else { sf_cen(F, CR_TRAIL, 1) }
605 F.run_target = 0
606 sf_begin_line(F, 0, 0, 0, raw_indent)
607 sf_put_raw(F, j, et)
608 sf_end_line(F, 3)
609 i = e + 1
610 line_no = line_no + 1
611 continue
612 } }
613 if c0 == 64 {
614 let dk: i64 = sf_directive(s, j + 1, n)
615 if dk > 0 {
616 // directive: the whole line (or the macro body extent) byte-for-byte
617 var e2: i64 = sf_eol(s, j, n)
618 if dk == 2 { let me: i64 = sf_macro_end(s, j, n); if me > e2 { e2 = me } }
619 let et2: i64 = sf_rtrim(s, i, e2)
620 if F.blank_pending == 1 { F.blank_pending = 0; if F.any_content == 1 { sf_put(F, 10) } }
621 F.run_target = 0
622 F.line_o = F.o
623 sf_put_raw(F, i, et2)
624 // count the newlines we swallowed inside a multi-line body
625 var q: i64 = i
626 while q < et2 { if sf_at(s, q) == 10 { line_no = line_no + 1 } q = q + 1 }
627 let eol2: i64 = sf_eol(s, j, n)
628 if e2 > eol2 {
629 // a multi-line body: whatever follows the closing brace is still this line
630 F.prev = SP_ATTR
631 at_start = 0
632 line_has_comment = 0
633 kind_prev = 73
634 i = e2
635 gap_a = i
636 continue
637 }
638 sf_end_line(F, 5)
639 F.prev_kind = 5
640 i = e2 + 1
641 line_no = line_no + 1
642 continue
643 }
644 }
645 // code line
646 sf_line_shape(s, j, n, shape)
647 var ft: i64 = 0
648 if shape[3] == 1 { ft = 1 }
649 if shape[2] == 1 { ft = 2 }
650 sf_begin_line(F, shape[0], shape[1], ft, raw_indent)
651 at_start = 0
652 line_has_comment = 0
653 kind_prev = 0
654 F.line_ntok = 0
655 F.line_tok0_ident = 0
656 F.line_name_len = 0
657 F.line_had_field_colon = 0
658 F.line_start_src = j
659 i = j
660 gap_a = j
661 continue
662 }
663 // ---- inside a line ----
664 let c: i64 = sf_at(s, i)
665 if c == 10 {
666 if F.in_struct_at >= 0 { if F.depth == F.in_struct_at { if F.line_had_field_colon == 0 { F.run_target = 0 } } }
667 sf_end_line(F, 1 + line_has_comment)
668 at_start = 1
669 i = i + 1
670 line_no = line_no + 1
671 continue
672 }
673 if sf_is_ws(c) == 1 {
674 if c == 13 { if i + 1 < n { if sf_at(s, i + 1) == 10 { crlf_seen = 1; sf_cen(F, CR_CRLF, 0) } } }
675 i = i + 1
676 continue
677 }
678 var c2: i64 = 0
679 if i + 1 < n { c2 = sf_at(s, i + 1) }
680 if c == 47 { if c2 == 47 {
681 // trailing comment: keep the raw gap (declared floor), then the comment text
682 let e3: i64 = sf_eol(s, i, n)
683 let et3: i64 = sf_rtrim(s, i, e3)
684 if et3 < e3 { sf_cen(F, CR_TRAIL, 0) } else { sf_cen(F, CR_TRAIL, 1) }
685 var g: i64 = gap_a
686 while g < i { let gc: i64 = sf_at(s, g); if gc != 13 { sf_put(F, gc) } g = g + 1 }
687 sf_put_raw(F, i, et3)
688 line_has_comment = 1
689 i = e3
690 continue
691 } }
692 if c == 34 {
693 let se: i64 = sf_string_end(s, i, n)
694 sf_emit_sep(F, SP_OPERAND, 3, kind_prev, gap_a, i)
695 sf_put_raw(F, i, se)
696 // newlines inside the literal are part of the literal, but the line counter must follow
697 var q2: i64 = i
698 while q2 < se { if sf_at(s, q2) == 10 { line_no = line_no + 1 } q2 = q2 + 1 }
699 F.prev = SP_OPERAND
700 kind_prev = 3
701 F.line_ntok = F.line_ntok + 1
702 i = se
703 gap_a = i
704 continue
705 }
706 if c == 64 {
707 let dk2: i64 = sf_directive(s, i + 1, n)
708 if dk2 > 0 {
709 var e4: i64 = sf_eol(s, i, n)
710 if dk2 == 2 { let me2: i64 = sf_macro_end(s, i, n); if me2 > e4 { e4 = me2 } }
711 let et4: i64 = sf_rtrim(s, i, e4)
712 sf_emit_sep(F, SP_ATTR, 73, kind_prev, gap_a, i)
713 sf_put_raw(F, i, et4)
714 var q3: i64 = i
715 while q3 < et4 { if sf_at(s, q3) == 10 { line_no = line_no + 1 } q3 = q3 + 1 }
716 F.prev = SP_ATTR
717 kind_prev = 73
718 i = e4
719 gap_a = i
720 continue
721 }
722 // @name attribute: glue the name
723 sf_emit_sep(F, SP_ATTR, 73, kind_prev, gap_a, i)
724 sf_put(F, 64)
725 var k2: i64 = i + 1
726 var gk2: i64 = 1
727 while gk2 == 1 { if k2 < n { if sf_is_idch(sf_at(s, k2)) == 1 { k2 = k2 + 1 } else { gk2 = 0 } } else { gk2 = 0 } }
728 sf_put_raw(F, i + 1, k2)
729 F.prev = SP_ATTR
730 kind_prev = 73
731 F.line_ntok = F.line_ntok + 1
732 i = k2
733 gap_a = i
734 continue
735 }
736 if sf_is_idstart(c) == 1 {
737 var k3: i64 = i + 1
738 var gk3: i64 = 1
739 while gk3 == 1 { if k3 < n { if sf_is_idch(sf_at(s, k3)) == 1 { k3 = k3 + 1 } else { gk3 = 0 } } else { gk3 = 0 } }
740 let kc: i64 = sf_kw_class(s, i, k3)
741 var cls: i64 = SP_IDENT
742 if kc == 1 { cls = SP_KW }
743 if kc == 2 { cls = SP_OPERAND }
744 if kc == 1 { if F.prev == SP_RBRACE { if k3 - i == 4 { if _sf_eq(((s as i64) + i) as *u8, "else\x00" as *u8, 4) == 1 { sf_cen(F, CR_ELSEJOIN, 1) } } } }
745 if F.line_ntok == 0 { if cls == SP_IDENT { F.line_tok0_ident = 1; F.line_name_len = k3 - i } }
746 if kc == 1 { if k3 - i == 6 { if _sf_eq(((s as i64) + i) as *u8, "struct\x00" as *u8, 6) == 1 { F.pending_struct = 1 } } }
747 sf_emit_sep(F, cls, 2, kind_prev, gap_a, i)
748 sf_put_raw(F, i, k3)
749 F.prev = cls
750 kind_prev = 2
751 F.line_ntok = F.line_ntok + 1
752 i = k3
753 gap_a = i
754 continue
755 }
756 if is_digit(c) == 1 {
757 var k4: i64 = i + 1
758 var go4: i64 = 1
759 while go4 == 1 {
760 if k4 >= n { go4 = 0 } else {
761 let d: i64 = sf_at(s, k4)
762 if sf_is_idch(d) == 1 { k4 = k4 + 1 } else {
763 if d == 46 { var d2: i64 = 0; if k4 + 1 < n { d2 = sf_at(s, k4 + 1) } if is_digit(d2) == 1 { k4 = k4 + 2 } else { go4 = 0 } } else { go4 = 0 }
764 }
765 }
766 }
767 sf_emit_sep(F, SP_OPERAND, 1, kind_prev, gap_a, i)
768 sf_put_raw(F, i, k4)
769 F.prev = SP_OPERAND
770 kind_prev = 1
771 F.line_ntok = F.line_ntok + 1
772 i = k4
773 gap_a = i
774 continue
775 }
776 if c == 35 {
777 // #name: glued like @name
778 sf_emit_sep(F, SP_ATTR, 74, kind_prev, gap_a, i)
779 sf_put(F, 35)
780 F.prev = SP_ATTR
781 kind_prev = 74
782 F.line_ntok = F.line_ntok + 1
783 i = i + 1
784 gap_a = i
785 continue
786 }
787 // punctuation
788 let pl: i64 = sf_punct(s, i, n, pk)
789 var kind: i64 = pk[0]
790 var cls2: i64 = pk[1]
791 if cls2 == SP_BINOP {
792 if kind == 41 { if sf_is_binary(F) == 0 { cls2 = SP_UNARY } } // -
793 if kind == 42 { if sf_is_binary(F) == 0 { cls2 = SP_UNARY } } // *
794 if kind == 55 { if sf_is_binary(F) == 0 { cls2 = SP_UNARY } } // &
795 if kind == 40 { if sf_is_binary(F) == 0 { cls2 = SP_UNARY } } // +
796 }
797 if kind == 77 { F.tern = F.tern + 1 }
798 if cls2 == SP_COLON { if F.tern > 0 { cls2 = SP_BINOP; F.tern = F.tern - 1 } }
799 // struct field colon: first token was the field name, we are at struct-body depth -> align the type.
800 // The padding goes AFTER the colon (the type column is aligned), so it is set once the colon is
801 // emitted, to apply to the next token's separator -- not to the colon's own (which stays glued).
802 var field_colon_want: i64 = 0
803 if cls2 == SP_COLON {
804 if F.in_struct_at >= 0 { if F.depth == F.in_struct_at { if F.line_ntok == 1 { if F.line_tok0_ident == 1 {
805 if F.run_target == 0 { F.run_target = sf_field_run_target(s, F.line_start_src, n) }
806 field_colon_want = F.run_target - F.line_name_len + 1
807 if field_colon_want < 1 { field_colon_want = 1 }
808 F.line_had_field_colon = 1
809 } } } }
810 }
811 sf_emit_sep(F, cls2, kind, kind_prev, gap_a, i)
812 sf_put_raw(F, i, i + pl)
813 if field_colon_want > 0 { F.align_pending = field_colon_want }
814 if kind == 67 { F.pdepth = F.pdepth + 1 }
815 if kind == 71 { F.pdepth = F.pdepth + 1 }
816 if kind == 68 { F.pdepth = F.pdepth - 1; if F.pdepth < 0 { F.pdepth = 0 } }
817 if kind == 72 { F.pdepth = F.pdepth - 1; if F.pdepth < 0 { F.pdepth = 0 } }
818 if kind == 69 { F.depth = F.depth + 1; if F.pending_struct == 1 { F.in_struct_at = F.depth; F.pending_struct = 0; F.run_target = 0 } }
819 if kind == 70 { F.depth = F.depth - 1; if F.depth < 0 { F.depth = 0 } if F.in_struct_at >= 0 { if F.depth < F.in_struct_at { F.in_struct_at = 0 - 1; F.run_target = 0 } } }
820 F.prev = cls2
821 kind_prev = kind
822 F.line_ntok = F.line_ntok + 1
823 i = i + pl
824 gap_a = i
825 }
826 // EOF: close an open line, drop trailing blank lines, exactly one final newline
827 F.pending_struct = 0
828 if at_start == 0 { sf_end_line(F, 1 + line_has_comment) }
829 if n > 0 { if sf_at(s, n - 1) == 10 { if F.blank_pending == 0 { sf_cen(F, CR_FINALNL, 1) } else { sf_cen(F, CR_FINALNL, 0) } } else { sf_cen(F, CR_FINALNL, 0) } }
830 F.blank_pending = 0
831 return 0
832}
833
834// Output capacity derived from the input: every byte can survive, every token can gain one space,
835// every line can gain an indent bounded by the brace count, plus the final newline.
836// First attempt: every byte survives, every line may gain two indent units and every byte one space.
837// If that overflows the caller doubles and retries (srcfmt_emit reports -1, never truncates); the
838// absolute ceiling is lines * 4 * braces, which no real file approaches.
839func srcfmt_cap(src: *u8, n: i64) -> i64 {
840 var lines: i64 = 1
841 var i: i64 = 0
842 while i < n { if sf_at(src, i) == 10 { lines = lines + 1 } i = i + 1 }
843 return n * 2 + lines * SF_INDENT * 2 + 64
844}
845func srcfmt_cap_ceiling(src: *u8, n: i64) -> i64 {
846 var lines: i64 = 1
847 var braces: i64 = 1
848 var i: i64 = 0
849 while i < n { let c: i64 = sf_at(src, i); if c == 10 { lines = lines + 1 } if c == 123 { braces = braces + 1 } i = i + 1 }
850 return n * 2 + lines * (braces * SF_INDENT + SF_INDENT * 2) + 64
851}
852// Emit with a measured capacity: grow on -1 up to the ceiling. Returns the length and writes the
853// buffer + its capacity through outp/capp; -4 only if the ceiling itself overflowed.
854func srcfmt_emit_grow(src: *u8, n: i64, outp: *i64, capp: *i64, cen: *i64) -> i64 {
855 var cap: i64 = srcfmt_cap(src, n)
856 let ceil: i64 = srcfmt_cap_ceiling(src, n)
857 var go: i64 = 1
858 var m: i64 = 0 - 4
859 while go == 1 {
860 let out: *u8 = sys_mmap(cap + 16)
861 if (cen as i64) == 0 { m = srcfmt_emit(src, n, out, cap) } else { m = srcfmt_emit_census(src, n, out, cap, cen) }
862 if m >= 0 { outp[0] = out as i64; capp[0] = cap + 16; go = 0 } else {
863 sys_munmap(out, cap + 16)
864 if cap >= ceil { go = 0; m = 0 - 4 } else { cap = cap * 2; if cap > ceil { cap = ceil } }
865 }
866 }
867 return m
868}
869
870// ---- THE ORACLE: both texts lex to the same token stream? 0 = same; else 1-based index of the
871// first differing token (g_sf_verify_tok) with its line in `a` (g_sf_verify_line). ----
872func srcfmt_verify(a: *u8, an: i64, b: *u8, bn: i64) -> i64 {
873 let ta: *Tok = lex_source(a, an + 2)
874 let tb: *Tok = lex_source(b, bn + 2)
875 var k: i64 = 0
876 var go: i64 = 1
877 var bad: i64 = 0
878 while go == 1 {
879 let x: *Tok = (((ta as i64) + k * TOK_BYTES)) as *Tok
880 let y: *Tok = (((tb as i64) + k * TOK_BYTES)) as *Tok
881 if x.kind != y.kind { bad = k + 1; go = 0 } else {
882 if sf_tok_same(x, y) == 0 { bad = k + 1; go = 0 }
883 if x.kind == 0 { go = 0 }
884 }
885 if go == 1 { k = k + 1 }
886 if bad != 0 { g_sf_verify_tok = bad; g_sf_verify_line = x.line }
887 }
888 sys_munmap(ta as *u8, (an + 2) * TOK_BYTES + TOK_BYTES)
889 sys_munmap(tb as *u8, (bn + 2) * TOK_BYTES + TOK_BYTES)
890 if bad == 0 { g_sf_verify_tok = 0; g_sf_verify_line = 0 }
891 return bad
892}
893
894func sf_tok_same(x: *Tok, y: *Tok) -> i64 {
895 if x.int_val != y.int_val { return 0 }
896 if x.kind == 3 {
897 if x.str_len != y.str_len { return 0 }
898 if x.str_data != 0 { if y.str_data != 0 {
899 let p: *u8 = x.str_data as *u8
900 let q: *u8 = y.str_data as *u8
901 var i: i64 = 0
902 while i < x.str_len { if p[i] != q[i] { return 0 } i = i + 1 }
903 return 1
904 } }
905 }
906 if x.text0 != y.text0 { return 0 }
907 if x.text1 != y.text1 { return 0 }
908 if x.text2 != y.text2 { return 0 }
909 if x.text3 != y.text3 { return 0 }
910 if x.text4 != y.text4 { return 0 }
911 if x.text5 != y.text5 { return 0 }
912 if x.text6 != y.text6 { return 0 }
913 if x.text7 != y.text7 { return 0 }
914 return 1
915}
916
917// ---- output helpers ----
918func sf_puts(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 }
919func sf_putn(fd: i64, v: i64) -> i64 {
920 let b: *u8 = sys_mmap(32)
921 var m: i64 = v
922 var neg: i64 = 0
923 if m < 0 { neg = 1; m = 0 - m }
924 var k: i64 = 31
925 b[k] = 0 as u8
926 if m == 0 { k = k - 1; b[k] = 48 as u8 }
927 while m > 0 { k = k - 1; b[k] = (48 + m % 10) as u8; m = m / 10 }
928 if neg == 1 { k = k - 1; b[k] = 45 as u8 }
929 sf_puts(fd, ((b as i64) + k) as *u8)
930 return 0
931}
932func sf_streq(a: *u8, b: *u8) -> i64 {
933 var i: i64 = 0
934 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
935 if b[i] != (0 as u8) { return 0 }
936 return 1
937}
938func sf_same_bytes(a: *u8, an: i64, b: *u8, bn: i64) -> i64 {
939 if an != bn { return 0 }
940 var i: i64 = 0
941 while i < an { if a[i] != b[i] { return 0 } i = i + 1 }
942 return 1
943}
944
945// Format one file in memory: returns the output length, or a negative code (-2 unreadable, -3 oracle
946// refused, -4 capacity). `outp` receives the buffer; `inp`/`inn` the input (for the caller's diff).
947func srcfmt_file(path: *u8, outp: *i64, inp: *i64, inn: *i64) -> i64 {
948 let szp: *i64 = sys_mmap(16) as *i64
949 let src: *u8 = sys_read_file(path, szp)
950 if (src as i64) == 0 { return 0 - 2 }
951 let n: i64 = szp[0]
952 let capp: *i64 = sys_mmap(16) as *i64
953 let m: i64 = srcfmt_emit_grow(src, n, outp, capp, 0 as *i64)
954 inp[0] = src as i64
955 inn[0] = n
956 if m < 0 { return 0 - 4 }
957 let out: *u8 = outp[0] as *u8
958 out[m] = 0 as u8
959 if srcfmt_verify(src, n, out, m) != 0 { return 0 - 3 }
960 return m
961}
962
963func sf_census_name(r: i64) -> *u8 {
964 if r == CR_INDENT { return "indent-4-per-depth\x00" as *u8 }
965 if r == CR_TRAIL { return "no-trailing-whitespace\x00" as *u8 }
966 if r == CR_CRLF { return "lf-line-endings\x00" as *u8 }
967 if r == CR_COMMA { return "comma-then-one-space\x00" as *u8 }
968 if r == CR_SEMI { return "semicolon-then-one-space\x00" as *u8 }
969 if r == CR_BINOP { return "binary-operator-spaced\x00" as *u8 }
970 if r == CR_ASSIGN { return "assign-spaced\x00" as *u8 }
971 if r == CR_ARROW { return "arrow-spaced\x00" as *u8 }
972 if r == CR_COLON { return "decl-colon-x-colon-space-T\x00" as *u8 }
973 if r == CR_UNARY { return "unary-glued\x00" as *u8 }
974 if r == CR_CALLPAREN { return "call-paren-glued\x00" as *u8 }
975 if r == CR_KWPAREN { return "keyword-paren-spaced\x00" as *u8 }
976 if r == CR_LBRACE { return "space-before-open-brace\x00" as *u8 }
977 if r == CR_BLOCKPAD { return "one-line-block-padded\x00" as *u8 }
978 if r == CR_EMPTYBLOCK { return "empty-block-closed\x00" as *u8 }
979 if r == CR_GLUE { return "dot-dotdot-coloncolon-glued\x00" as *u8 }
980 if r == CR_ELSEJOIN { return "else-joined-to-closing-brace\x00" as *u8 }
981 if r == CR_BLANKRUN { return "blank-runs-collapsed\x00" as *u8 }
982 if r == CR_CONT { return "continuation-plus-4\x00" as *u8 }
983 if r == CR_FINALNL { return "one-final-newline\x00" as *u8 }
984 if r == CR_TYPESUFFIX { return "array-type-suffix-glued\x00" as *u8 }
985 if r == CR_BRACEOWN { return "open-brace-joined-to-its-line\x00" as *u8 }
986 if r == CR_STRUCTALIGN { return "struct-field-type-column-aligned\x00" as *u8 }
987 return "?\x00" as *u8
988}
989
990func sf_usage() -> i64 {
991 sf_puts(2, "usage: nx_srcfmt <file> [--check] | nx_srcfmt census [dir]\n exit 0 canonical, 1 would change, 2 usage/unreadable, 3 REFUSED token stream differs, 4 REFUSED capacity\n\x00" as *u8)
992 return 2
993}
994
995func sf_census(dir: *u8) -> i64 {
996 let names: *u8 = sys_mmap(SF_CORPUS_CAP * SF_NAME_SLOT)
997 var cnt: i64 = 0
998 if (dir as i64) == 0 { cnt = gk_corpus_scan(names, SF_NAME_SLOT, SF_CORPUS_CAP) } else { cnt = gk_dirscan(dir, 0 as *u8, names, SF_NAME_SLOT, SF_CORPUS_CAP, 0) }
999 if cnt <= 0 {
1000 sf_puts(1, "SRCFMT-CENSUS REFUSED: could not enumerate the corpus (rc=\x00" as *u8); sf_putn(1, cnt); sf_puts(1, ") -- a census over an unknown population is not a census\n\x00" as *u8)
1001 return 2
1002 }
1003 let cen: *i64 = sys_mmap(SF_CENSUS_ROWS * 16 + 64) as *i64
1004 var f: i64 = 0
1005 var unreadable: i64 = 0
1006 var canonical: i64 = 0
1007 var changed: i64 = 0
1008 var refused: i64 = 0
1009 let szp: *i64 = sys_mmap(16) as *i64
1010 let outp: *i64 = sys_mmap(16) as *i64
1011 let capp: *i64 = sys_mmap(16) as *i64
1012 while f < cnt {
1013 let p: *u8 = ((names as i64) + f * SF_NAME_SLOT) as *u8
1014 let src: *u8 = sys_read_file(p, szp)
1015 if (src as i64) == 0 { unreadable = unreadable + 1 } else {
1016 let n: i64 = szp[0]
1017 let m: i64 = srcfmt_emit_grow(src, n, outp, capp, cen)
1018 if m < 0 { refused = refused + 1 } else {
1019 let out: *u8 = outp[0] as *u8
1020 if sf_same_bytes(src, n, out, m) == 1 { canonical = canonical + 1 } else { changed = changed + 1 }
1021 sys_munmap(out, capp[0])
1022 }
1023 sys_free_file(src, n)
1024 }
1025 f = f + 1
1026 }
1027 sf_puts(1, "=== nx_srcfmt census -- per-rule agreement of the corpus with the canonical style (same scanner as the emitter) ===\n\x00" as *u8)
1028 sf_puts(1, "files=\x00" as *u8); sf_putn(1, cnt)
1029 sf_puts(1, " canonical=\x00" as *u8); sf_putn(1, canonical)
1030 sf_puts(1, " would_change=\x00" as *u8); sf_putn(1, changed)
1031 sf_puts(1, " unreadable=\x00" as *u8); sf_putn(1, unreadable)
1032 sf_puts(1, " capacity_refused=\x00" as *u8); sf_putn(1, refused)
1033 sf_puts(1, " (partition sums to files)\n\x00" as *u8)
1034 var r: i64 = 0
1035 while r < SF_CENSUS_ROWS {
1036 let ag: i64 = cen[r * 2]
1037 let dg: i64 = cen[r * 2 + 1]
1038 if ag + dg > 0 {
1039 sf_puts(1, "RULE \x00" as *u8); sf_puts(1, sf_census_name(r))
1040 sf_puts(1, " agree=\x00" as *u8); sf_putn(1, ag)
1041 sf_puts(1, " disagree=\x00" as *u8); sf_putn(1, dg)
1042 sf_puts(1, " permil=\x00" as *u8); sf_putn(1, (ag * 1000) / (ag + dg))
1043 sf_puts(1, "\n\x00" as *u8)
1044 }
1045 r = r + 1
1046 }
1047 sf_puts(1, "SRCFMT-CENSUS done files=\x00" as *u8); sf_putn(1, cnt); sf_puts(1, "\n\x00" as *u8)
1048 return 0
1049}
1050
1051func main(argc: i64, argv: *i64) -> i64 {
1052 if argc < 2 { return sf_usage() }
1053 let a1: *u8 = argv[1] as *u8
1054 if sf_streq(a1, "census\x00" as *u8) == 1 {
1055 var d: *u8 = 0 as *u8
1056 if argc >= 3 { d = argv[2] as *u8 }
1057 return sf_census(d)
1058 }
1059 var check: i64 = 0
1060 if argc >= 3 { if sf_streq(argv[2] as *u8, "--check\x00" as *u8) == 1 { check = 1 } }
1061 let outp: *i64 = sys_mmap(16) as *i64
1062 let inp: *i64 = sys_mmap(16) as *i64
1063 let inn: *i64 = sys_mmap(16) as *i64
1064 let m: i64 = srcfmt_file(a1, outp, inp, inn)
1065 if m == (0 - 2) { sf_puts(2, "nx_srcfmt: cannot read \x00" as *u8); sf_puts(2, a1); sf_puts(2, "\n\x00" as *u8); return 2 }
1066 if m == (0 - 4) { sf_puts(2, "nx_srcfmt: REFUSED output capacity exceeded (derived bound; report this) for \x00" as *u8); sf_puts(2, a1); sf_puts(2, "\n\x00" as *u8); return 4 }
1067 if m == (0 - 3) {
1068 sf_puts(2, "nx_srcfmt: REFUSED -- the tokenizer sees a DIFFERENT token stream after formatting \x00" as *u8); sf_puts(2, a1)
1069 sf_puts(2, " (first differing token #\x00" as *u8); sf_putn(2, g_sf_verify_tok); sf_puts(2, " at source line \x00" as *u8); sf_putn(2, g_sf_verify_line)
1070 sf_puts(2, "); nothing written. A formatter that changes a program is the defect this check exists to make impossible.\n\x00" as *u8)
1071 return 3
1072 }
1073 let src: *u8 = inp[0] as *u8
1074 let n: i64 = inn[0]
1075 let out: *u8 = outp[0] as *u8
1076 let same: i64 = sf_same_bytes(src, n, out, m)
1077 if check == 1 {
1078 sf_puts(1, "SRCFMT \x00" as *u8); sf_puts(1, a1)
1079 if same == 1 { sf_puts(1, " canonical\x00" as *u8) } else { sf_puts(1, " would-change\x00" as *u8) }
1080 sf_puts(1, " bytes_in=\x00" as *u8); sf_putn(1, n); sf_puts(1, " bytes_out=\x00" as *u8); sf_putn(1, m); sf_puts(1, " oracle=token-stream-identical\n\x00" as *u8)
1081 } else {
1082 mp_write_all_sf(1, out, m)
1083 }
1084 if same == 1 { return 0 }
1085 return 1
1086}
1087
1088func mp_write_all_sf(fd: i64, buf: *u8, n: i64) -> i64 {
1089 var done: i64 = 0
1090 while done < n {
1091 let w: i64 = sys_write(fd, ((buf as i64) + done) as *u8, n - done)
1092 if w <= 0 { return done }
1093 done = done + w
1094 }
1095 return done
1096}