code wiki / (root) / nx_srcfmt.nx

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}