Line data Source code
1 : // wok_print -- see wok_print.h for the contract and the canonical decisions.
2 : //
3 : // The printer INVERTS wok_parse.c production by production, so its three
4 : // precedence ladders are copies of that file's call graph, not a second
5 : // opinion about it:
6 : //
7 : // type > arrow > typeapp > atomtype
8 : // expr > chain > app > atom
9 : // pat > patapp > atompat
10 : //
11 : // Two facts about the language shape everything here.
12 : //
13 : // FACT ONE: a block is opened by COLUMN, so only the LAST thing on a line
14 : // may open one. Every construct with a continuation lead after a body
15 : // (`in`, `then`, `else`, `where`) therefore has two shapes, and which one is
16 : // used is decided by opens_block() on the part that precedes the lead --
17 : // never by a width budget.
18 : //
19 : // FACT TWO: inside brackets layout is suspended (L2), so a newline emitted
20 : // there produces NO layout token and a block written there would silently
21 : // flatten. `flat` marks that region: within it no BLOCK may be opened.
22 : // Since the parser cannot BUILD an indented block inside brackets either,
23 : // the two restrictions match exactly, and a tree that violates this could
24 : // not have come from a parse. A FILL break is a different thing and is legal
25 : // there -- see "line filling" below.
26 : //
27 : // FACT THREE, which decides the whole of line filling: a line break CHANGES
28 : // THE PARSE. A new line whose first token is an ITEM LEAD either starts a new
29 : // block item (the tree silently changed) or is rejected outright for being
30 : // deeper than its block. Only two breaks are safe, and the filler makes no
31 : // others:
32 : //
33 : // * immediately before a CONTINUATION LEAD, which L4 answers with DEDENTs
34 : // and no separator, so the logical line continues; and
35 : // * anywhere inside a bracket group, where L2 suspends layout entirely.
36 : //
37 : // Application juxtaposition owns no separator, so it cannot be broken and a
38 : // long application stays long. Manufacturing a break point by adding
39 : // parentheses is deliberately NOT done: punctuation the author did not write
40 : // is a worse trade than one long line.
41 :
42 : // The prelude comes FIRST: it carries the POSIX feature-test macros, which
43 : // have no effect once a system header has been read. wok_base.h hard-errors
44 : // if it is reached too late.
45 : #include "wok_base.h"
46 :
47 : #include "wok_print.h"
48 :
49 : #include <inttypes.h>
50 : #include <stdint.h>
51 : #include <string.h>
52 :
53 : #include "wok_token.h"
54 : #include "wok_trivia.h"
55 :
56 : // ------------------------------------------------------------------- sink
57 : //
58 : // One algorithm, three destinations: a FILE, an arena-backed growable
59 : // buffer, and a MEASURE-ONLY sink. The third is what alignment needs: the
60 : // width of every head in a block must be known before the first head is
61 : // written, and re-rendering into a counter is cheaper than teaching every
62 : // head printer to report its own width.
63 :
64 : typedef struct {
65 : bool to_file;
66 : bool count_only;
67 : FILE *file;
68 : WokArena *arena;
69 : char *buf;
70 : usize len, cap;
71 :
72 : const char *src;
73 : const char *path; // named in an overflow report; null means <input>
74 : const WokTrivia *tv; // the comments, or null for a tree that has none
75 : usize cols; // columns written on the current line, in codepoints
76 : u32 depth;
77 : bool quiet; // measuring: a fault is reported by the real pass, not twice
78 :
79 : // ---- line filling ----
80 : bool nofill; // measuring a flat width: never introduce a break
81 : bool nl_seen; // a line was broken during the measurement
82 : bool check_lead; // the next byte written opens a FILL line
83 : u32 line; // 1-based output line, for the overflow report
84 : bool line_over; // this line has already crossed the width
85 : bool in_comment; // writing a comment, which is copied verbatim
86 : bool over_comment; // the crossing happened inside one
87 : u16 over_tag; // the construct blamed for that crossing
88 :
89 : // The open block columns, innermost last. A block is the ONLY thing that
90 : // may hold items at a column of its own, so this is the whole of what the
91 : // safety property compares against.
92 : u16 blk[WOK_PRINT_BLOCK_MAX];
93 : int nblk;
94 :
95 : // The nodes currently being written, outermost first. Read only to NAME
96 : // the construct in an overflow report.
97 : u16 tstack[WOK_PRINT_MAX_DEPTH];
98 : int tn;
99 : } Pr;
100 :
101 : // The two latches; see wok_print.h for why they are process-wide.
102 : static unsigned print_faults = 0;
103 : static unsigned print_overflows = 0;
104 : static unsigned print_unprintables = 0;
105 :
106 12729 : unsigned wok_print_fault_count(void) { return print_faults; }
107 5047 : unsigned wok_print_overflow_count(void) { return print_overflows; }
108 5334 : unsigned wok_print_unprintable_count(void) { return print_unprintables; }
109 12729 : void wok_print_reports_reset(void) {
110 12729 : print_faults = 0;
111 12729 : print_overflows = 0;
112 12729 : print_unprintables = 0;
113 12729 : }
114 :
115 : // A break that would change the tree rather than the text. Counted in every
116 : // build, because a caller about to overwrite a source file has to be able to
117 : // ask whether this happened.
118 0 : static void fault(Pr *p, const char *what) {
119 0 : if (p->count_only) return;
120 0 : print_faults++;
121 0 : if (p->quiet) return;
122 0 : (void)fprintf(stderr, "wok_print: INTERNAL: %s:%d: %s\n",
123 0 : p->path ? p->path : "<input>", p->line, what);
124 : }
125 :
126 1560003 : static void sink_reserve(Pr *p, usize extra) {
127 1560003 : if (p->len + extra <= p->cap) return;
128 23586 : usize new_cap = p->cap ? p->cap * 2 : 256;
129 23586 : while (new_cap < p->len + extra) new_cap *= 2;
130 23586 : char *grown = WOK_NEW_N(p->arena, char, new_cap);
131 23586 : if (p->len) memcpy(grown, p->buf, p->len);
132 23586 : p->buf = grown;
133 23586 : p->cap = new_cap;
134 : }
135 :
136 : // THE LEAD TEST, applied to the bytes the filler is about to write. It answers
137 : // the same question wok_token_is_continuation_lead answers about a scanned
138 : // token, for the far smaller set of leads a fill break can produce; test_fill
139 : // pins the two against each other for every one of them, which is what keeps
140 : // this from becoming a second, drifting opinion about the lexis.
141 : //
142 : // The symbol charset itself is NOT restated here -- wok_token.c owns it, and a
143 : // second copy is precisely how `-->` stops being a comment in one of them.
144 6956 : static bool lead_is_continuation(const char *s, usize n) {
145 6956 : if (n == 0) return false;
146 6956 : unsigned char c0 = (unsigned char)s[0];
147 : // Every symbol run scans as an operator, and every operator is a lead. A
148 : // run of two or more dashes is a COMMENT, whose line produces no token at
149 : // all (L1), so it is safe on the same side of the answer.
150 6956 : if (wok_test_is_sym(c0)) return true;
151 3602 : switch (c0) {
152 : case ',':
153 : case '.':
154 : case '`':
155 : case ')':
156 : case ']':
157 : case '}':
158 : return true;
159 : default:
160 : break;
161 : }
162 : // The keywords that can never begin a block item.
163 : static const char *const words[] = {"with", "in", "then", "else",
164 : "where", "of", "as"};
165 528 : for (usize k = 0; k < sizeof words / sizeof *words; k++) {
166 528 : usize wl = strlen(words[k]);
167 528 : if (n < wl || memcmp(s, words[k], wl) != 0) continue;
168 : // A prefix is not the word: `within` begins an item.
169 528 : if (n == wl) return true;
170 528 : unsigned char after = (unsigned char)s[wl];
171 528 : if (!wok_test_is_ident_cont(after)) return true;
172 : }
173 : return false;
174 : }
175 :
176 : // The construct an over-width line is blamed on: the innermost node being
177 : // written when the line crossed the width, which for an unbreakable run names
178 : // the long application, literal or identifier directly.
179 918 : static void blame(Pr *p) {
180 918 : p->over_comment = p->in_comment;
181 918 : p->over_tag = p->tn > 0 ? p->tstack[p->tn - 1] : (u16)W_File;
182 918 : }
183 :
184 : // Property 2: an overflow is STATED, so a long line reads as a limitation the
185 : // printer names rather than as a formatter bug. It is never an error -- there
186 : // is no other text this tree could have.
187 918 : static void report_overflow(Pr *p) {
188 918 : print_overflows++;
189 918 : if (p->quiet) return;
190 918 : const char *where = p->path ? p->path : "<input>";
191 918 : if (p->over_comment) {
192 72 : (void)fprintf(stderr,
193 : "wok_print: %s:%d: %zu columns over the %d-column width: a "
194 : "comment, which a formatter may move but not rewrite\n",
195 : where, p->line, p->cols, WOK_PRINT_WIDTH);
196 72 : return;
197 : }
198 846 : (void)fprintf(stderr,
199 : "wok_print: %s:%d: %zu columns over the %d-column width: an "
200 : "unbreakable run in %s -- juxtaposition, a literal and a name "
201 : "own no separator to break at\n",
202 : where, p->line, p->cols, WOK_PRINT_WIDTH,
203 846 : wok_node_desc[p->over_tag].tag);
204 : }
205 :
206 2192922 : static void put_n(Pr *p, const char *s, usize n) {
207 : // The first bytes after a fill break must be a continuation lead, or the
208 : // new line is a new ITEM and the tree just changed. Checked on the bytes
209 : // themselves rather than on what the caller meant to write.
210 2192922 : if (p->check_lead && n != 0) {
211 6956 : p->check_lead = false;
212 6956 : if (!lead_is_continuation(s, n))
213 0 : fault(p, "a filled line does not begin with a continuation lead");
214 : }
215 : // Columns are counted in codepoints rather than bytes so a non-ASCII
216 : // literal in a pattern does not skew a block's alignment.
217 7260221 : for (usize i = 0; i < n; i++) {
218 5067299 : unsigned char ch = (unsigned char)s[i];
219 5067299 : if (ch == '\n') {
220 151924 : p->nl_seen = true;
221 151924 : if (!p->count_only) {
222 151647 : if (p->cols > WOK_PRINT_WIDTH) report_overflow(p);
223 151647 : p->line++;
224 151647 : p->line_over = false;
225 : }
226 151924 : p->cols = 0;
227 4915375 : } else if ((ch & 0xC0u) != 0x80u) {
228 4915375 : p->cols++;
229 4915375 : if (!p->count_only && !p->line_over && p->cols > WOK_PRINT_WIDTH) {
230 918 : p->line_over = true;
231 918 : blame(p);
232 : }
233 : }
234 : }
235 2192922 : if (p->count_only) return;
236 1542040 : if (p->to_file) {
237 0 : (void)fwrite(s, 1, n, p->file);
238 0 : return;
239 : }
240 1542040 : sink_reserve(p, n);
241 1542040 : memcpy(p->buf + p->len, s, n);
242 1542040 : p->len += n;
243 : }
244 :
245 774585 : static void put_c(Pr *p, char c) { put_n(p, &c, 1); }
246 :
247 621186 : static void put_z(Pr *p, const char *s) { put_n(p, s, strlen(s)); }
248 :
249 534400 : static void put_span(Pr *p, WokSpan s) { put_n(p, p->src + s.off, s.len); }
250 :
251 25543 : static void put_uint(Pr *p, u64 v) {
252 25543 : char buf[32];
253 25543 : int len = snprintf(buf, sizeof buf, "%" PRIu64, v);
254 25543 : put_n(p, buf, (usize)(len < 0 ? 0 : len));
255 25543 : }
256 :
257 8107 : static void put_spaces(Pr *p, usize n) {
258 177923 : for (usize i = 0; i < n; i++) put_c(p, ' ');
259 8107 : }
260 :
261 : // What a new line CLAIMS to be. The layout filter reads that claim off the
262 : // line's first token, so stating it here is what makes it checkable.
263 : typedef enum {
264 : LN_ITEM, // a block item: its column must be its block's own column
265 : LN_LEAD, // a hanging continuation lead (`in`, `then`, `else`, `where`)
266 : LN_COMMENT, // a comment-only line, which produces no token at all (L1)
267 : } LineKind;
268 :
269 : // The innermost open block's column, or 0 for the top level, whose items sit
270 : // at column 0 and which no p_seq_block pushes. SIZE_MAX once nesting has
271 : // outrun the tracked depth: a tree that deep cannot have come from a parse
272 : // (the layout filter caps block nesting), and switching the check off is
273 : // better than reporting a violation the printer did not commit.
274 82876 : static usize block_col(const Pr *p) {
275 82876 : if (p->nblk > WOK_PRINT_BLOCK_MAX) return SIZE_MAX;
276 82876 : return p->nblk > 0 ? p->blk[p->nblk - 1] : 0;
277 : }
278 :
279 : // A line break plus `ind` levels of indentation. With nl_fill below, the ONLY
280 : // place a newline is written, so "never inside brackets" and "an item line
281 : // sits at its block's column" are both properties of these two functions.
282 75876 : static void nl(Pr *p, u32 ind, LineKind kind) {
283 : // THE SAFETY PROPERTY, first half. An item line at any column but its
284 : // block's own either opens a block the parser does not expect or starts an
285 : // item in the wrong one; a continuation lead makes no such claim (L5) and
286 : // is deliberately allowed to sit shallower, which is how a hanging `in`
287 : // closes the binding's block.
288 75876 : usize bc = block_col(p);
289 75876 : if (kind == LN_ITEM && bc != SIZE_MAX && (usize)ind * 2 != bc)
290 0 : fault(p, "a block item is not at its block's column");
291 75876 : put_c(p, '\n');
292 247182 : for (u32 i = 0; i < ind; i++) put_n(p, " ", 2);
293 75876 : }
294 :
295 : // A FILL break: a new line at absolute column `col`, whose first token the
296 : // caller writes next. `in_bracket` says layout is suspended here (L2), where
297 : // no column can mean anything; everywhere else the new line must be STRICTLY
298 : // DEEPER than the enclosing block, since a continuation lead at or above that
299 : // column emits DEDENTs (L4) and closes the block early.
300 6956 : static void nl_fill(Pr *p, usize col, bool in_bracket) {
301 6956 : usize bc = block_col(p);
302 6956 : if (!in_bracket && bc != SIZE_MAX && col <= bc)
303 0 : fault(p, "a filled line is not deeper than its block");
304 6956 : put_c(p, '\n');
305 6956 : put_spaces(p, col);
306 6956 : p->check_lead = true; // the next bytes written are checked, in put_n
307 6956 : }
308 :
309 4452 : static bool is_trailing_blank(char c) {
310 4452 : return c == ' ' || c == '\t' || c == '\r';
311 : }
312 :
313 : // A node the printer cannot render in canonical form. Reported rather than
314 : // silently mangled, and the text that follows is expected to fail to
315 : // re-parse -- exactly like the violation it is reporting.
316 0 : static void unprintable(Pr *p, const char *what) {
317 : // Counted BEFORE the quiet check and skipped on a measuring pass, exactly as
318 : // `fault` is: every node is rendered at least twice, once into a width
319 : // counter and once for real, so counting on the measuring pass would double
320 : // every answer.
321 0 : if (p->count_only) return;
322 0 : print_unprintables++;
323 0 : if (p->quiet) return;
324 0 : (void)fprintf(stderr, "wok_print: %s cannot be written in canonical form\n",
325 : what);
326 : }
327 :
328 : // ----------------------------------------------------------------- trivia
329 : //
330 : // The comments, put back. wok_trivia decided WHICH item each one belongs to;
331 : // this decides only how it is written, and the whole of that is: a leading
332 : // comment occupies the item's own line at the item's own indentation, and a
333 : // trailing one follows the item after two spaces.
334 : //
335 : // A block comment is emitted VERBATIM, interior line breaks and all. A
336 : // formatter may move a comment; it may not rewrite one.
337 :
338 138315 : static const WokTriviaEntry *trivia_of(const Pr *p, const WokNode *n) {
339 138315 : return wok_trivia_lookup(p->tv, n);
340 : }
341 :
342 4452 : static void put_comment(Pr *p, u32 index) {
343 4452 : const WokComment *c = &p->tv->comment[index];
344 : // Blanks at the end of a comment are not part of what the author wrote, and
345 : // leaving them would put trailing whitespace on the line.
346 4452 : usize len = c->len;
347 4452 : while (len != 0 && is_trailing_blank(p->src[c->off + len - 1])) len--;
348 4452 : p->in_comment = true; // an over-width comment is not an unbreakable RUN
349 4452 : put_n(p, p->src + c->off, len);
350 4452 : p->in_comment = false;
351 4452 : }
352 :
353 : // The paragraph break and the leading comments of one block item, each on its
354 : // own line at `ind`. The item's own line break is the caller's.
355 59337 : static void p_lead(Pr *p, const WokTriviaEntry *e, u32 ind, bool blank_ok) {
356 59337 : if (e == nullptr) return;
357 79 : if (blank_ok && e->blank_before != 0) put_c(p, '\n');
358 130 : for (u32 k = 0; k < e->lead_n; k++) {
359 51 : nl(p, ind, LN_COMMENT);
360 51 : put_comment(p, e->lead_first + k);
361 : }
362 : }
363 :
364 : // The comment written after an item, on the item's own line. A node whose
365 : // trailing range belongs to a block it CLOSES is skipped here and written by
366 : // p_block_close instead; writing both would print the comment twice.
367 102013 : static void p_trail(Pr *p, const WokNode *n, const WokTriviaEntry *e) {
368 102013 : if (e == nullptr || wok_trivia_trail_closes_block(n)) return;
369 1146 : for (u32 k = 0; k < e->trail_n; k++) {
370 34 : put_z(p, " ");
371 34 : put_comment(p, e->trail_first + k);
372 : }
373 : }
374 :
375 : // Rule 3: the comments that followed a block's last item, written at that
376 : // block's own indentation.
377 34406 : static void p_block_close(Pr *p, const WokNode *owner, u32 ind) {
378 34406 : if (owner == nullptr) return;
379 18339 : const WokTriviaEntry *e = trivia_of(p, owner);
380 18339 : if (e == nullptr) return;
381 84 : for (u32 k = 0; k < e->trail_n; k++) {
382 18 : nl(p, ind, LN_COMMENT);
383 18 : put_comment(p, e->trail_first + k);
384 : }
385 : }
386 :
387 : // ------------------------------------------------------------- precedence
388 : //
389 : // One ladder per family. The numbers of two families are never compared,
390 : // because a node is only ever asked for the level its own family uses.
391 :
392 : enum { TP_TYPE = 0, TP_ARROW = 1, TP_APP = 2, TP_ATOM = 3 };
393 : enum { PP_PAT = 0, PP_APP = 1, PP_ATOM = 2 };
394 : // EP_NEG sits BETWEEN the chain and the application, and it is not cosmetic.
395 : // parse_app has two arms -- `- <the whole application to the right>`, and a
396 : // juxtaposition of atoms -- and they share a rung without being
397 : // interchangeable: a leading `-` SWALLOWS the rest of the rung, so negation is
398 : // legal only at the HEAD of an application spine, never as the function of
399 : // one. On one rung the printer wrote `E_App(E_Neg a, b)` as `-a b`, which
400 : // reads back as `-(a b)`; the split is what asks for the bracket, and it also
401 : // subsumes the hand-written case that used to keep `- -x` from lexing as a
402 : // comment opener.
403 : enum { EP_EXPR = 0, EP_CHAIN = 1, EP_NEG = 2, EP_APP = 3, EP_ATOM = 4 };
404 :
405 : // Nodes that occupy no position where re-association is possible. It must be
406 : // at least the top rung of EVERY ladder above, so that a node which cannot
407 : // re-associate is never bracketed wherever it is placed. Inserting a rung and
408 : // forgetting to raise this would put brackets around an N_Name in a module
409 : // path or an H_ChainOp -- where a bracket is SYNTAX, not grouping -- so the
410 : // invariant is asserted rather than described.
411 : enum { PREC_FIXED = 4 };
412 : // The casts carry weight: PREC_FIXED and the *_ATOM rungs are enumerators of
413 : // DIFFERENT anonymous enums, and GCC's -Wenum-compare rejects comparing two
414 : // unrelated enum types. Comparing their integer values is precisely what is
415 : // meant here, so say so.
416 : static_assert((int)PREC_FIXED >= (int)TP_ATOM &&
417 : (int)PREC_FIXED >= (int)PP_ATOM &&
418 : (int)PREC_FIXED >= (int)EP_ATOM,
419 : "PREC_FIXED must top every ladder; see the note above");
420 :
421 : typedef struct {
422 : u32 prec; // the level this position demands
423 : u32 ind; // indentation of the line this node's head sits on
424 : bool flat; // inside brackets: no BLOCK may be opened here
425 : bool lead; // this node writes the FIRST token of its line
426 : bool brk; // this node must break EVERY separator it owns
427 : usize pad; // arrow alignment target, in columns; 0 for none
428 : // Columns that will CERTAINLY follow this node on its line: what the
429 : // unbreakable construct around it still has to write before the next place
430 : // a break is even possible. "Fits in the columns remaining" means remaining
431 : // after that, or a group measures itself, fits, and is then pushed over the
432 : // width by the juxtaposition it sits inside.
433 : usize rest;
434 : // The column a wrapped line inside this region takes, or 0 to derive it
435 : // from `ind`. Set by a bracketed group for its own contents, so a chain
436 : // wrapped inside a wrapped list lands under that list's items rather than
437 : // under the enclosing ITEM, where it would read as belonging to neither.
438 : usize wrap;
439 : } Ctx;
440 :
441 1088440 : static u32 node_prec(const WokNode *n) {
442 1088440 : switch ((WokTag)n->tag) {
443 : // Positions where a parenthesis would be meaningless or illegal.
444 : case N_Name:
445 : case N_ModPath:
446 : case D_Module:
447 : case D_Import:
448 : case D_Type:
449 : case D_Alias:
450 : case D_Effect:
451 : case D_Class:
452 : case D_Instance:
453 : case D_Foreign:
454 : case D_Fixity:
455 : case H_FixRel:
456 : case D_ExternType:
457 : case D_Sig:
458 : case D_Equation:
459 : case D_Error:
460 : case H_TyParam:
461 : case H_ConDef:
462 : case H_FieldType:
463 : case H_OpSig:
464 : case H_ForeignMember:
465 : case H_SigName:
466 : case L_Prefix:
467 : case L_Infix:
468 : case H_RowEntry:
469 : case H_FieldPat:
470 : case H_ChainOp:
471 : case H_Bind:
472 : case H_UseBind:
473 : case H_Alt:
474 : case H_Clause:
475 : case H_Field:
476 : case S_Let:
477 : case S_Handle:
478 : case S_Use:
479 : case S_Discard:
480 : case W_File:
481 : return PREC_FIXED;
482 :
483 278099 : case T_Var:
484 : case T_Con:
485 : case T_List:
486 : case T_Tuple:
487 : case T_Unit:
488 : case T_RowArg:
489 278099 : return TP_ATOM;
490 19185 : case T_App:
491 : case T_Transfer:
492 19185 : return TP_APP;
493 26777 : case T_Fun:
494 26777 : return TP_ARROW;
495 42712 : case T_Qual:
496 : case T_With:
497 42712 : return TP_TYPE;
498 :
499 93916 : case P_Var:
500 : case P_Wild:
501 : case P_Int:
502 : case P_Str:
503 : case P_Char:
504 : case P_Tuple:
505 : case P_List:
506 : case P_Unit:
507 : case P_As:
508 : case P_Record:
509 93916 : return PP_ATOM;
510 : // A bare constructor IS an atompat; an applied one is not.
511 3592 : case P_Con:
512 3592 : return P_Con_args(n).n != 0 ? PP_APP : PP_ATOM;
513 3448 : case P_Cons:
514 3448 : return PP_PAT;
515 :
516 : case E_Var:
517 : case E_Con:
518 : case E_Int:
519 : case E_Str:
520 : case E_Char:
521 : case E_Unit:
522 : case E_OpRef:
523 : case E_Dot:
524 : case E_List:
525 : case E_Tuple:
526 : case E_Record:
527 : case E_Error:
528 : return EP_ATOM;
529 5326 : case E_App:
530 5326 : return EP_APP;
531 2404 : case E_Neg:
532 2404 : return EP_NEG;
533 5649 : case E_Chain:
534 5649 : return EP_CHAIN;
535 20634 : case E_Lambda:
536 : case E_LetIn:
537 : case E_HandleIn:
538 : case E_UseIn:
539 : case E_If:
540 : case E_Case:
541 : case E_Handler:
542 : case E_Assign:
543 : case E_Block:
544 20634 : return EP_EXPR;
545 :
546 : case WOK_TAG_COUNT:
547 : break;
548 : }
549 0 : WOK_UNREACHABLE();
550 : }
551 :
552 : // Does this node begin with `-`? There are TWO positions where that is misread,
553 : // and only `-` can arise in either: prefix negation, and a negative integer
554 : // pattern.
555 : //
556 : // 1. the first token of a block ITEM. The layout filter reads an operator
557 : // lead as a CONTINUATION, so the line would join the previous one instead
558 : // of starting an item. Bracketing it is the repair the corpus already
559 : // writes by hand (`(-1) -> 2`).
560 : // 2. the first ARGUMENT of a prefix left-hand side. parse_lhs decides
561 : // prefix-vs-infix on the single token after the name, so `f -1 = 2` is
562 : // read as the infix equation `f - 1 = 2` -- a different program that
563 : // still parses. A binding does not need the bracket (parse_bind has no
564 : // such lookahead), but the printer cannot tell an equation's left-hand
565 : // side from a binding's, so it writes the safe one for both.
566 178256 : static bool needs_lead_paren(const WokNode *n) {
567 178256 : if (n->tag == E_Neg) return true;
568 179351 : return n->tag == P_Int && P_Int_negative(n);
569 : }
570 :
571 : // True when writing `n` at indent i leaves the cursor inside an indented
572 : // block, so a following continuation lead (`in`, `then`, `else`, `where`)
573 : // must start a new line shallower than that block. Written as a loop rather
574 : // than a recursion because it descends only the LAST position of a node, and
575 : // the loop cannot outrun the C stack.
576 7211 : static bool opens_block(const WokNode *n) {
577 8012 : for (int guard = 0; guard < WOK_PRINT_MAX_DEPTH; guard++) {
578 8012 : switch ((WokTag)n->tag) {
579 : case E_Block:
580 : case E_Case:
581 : case E_Handler:
582 : return true;
583 58 : case E_LetIn:
584 58 : n = E_LetIn_body(n);
585 58 : break;
586 360 : case E_HandleIn:
587 360 : n = E_HandleIn_body(n);
588 360 : break;
589 65 : case E_UseIn:
590 65 : n = E_UseIn_body(n);
591 65 : break;
592 131 : case E_Lambda:
593 131 : n = E_Lambda_body(n);
594 131 : break;
595 88 : case E_If:
596 88 : n = E_If_else_(n);
597 88 : break;
598 99 : case E_Assign:
599 99 : n = E_Assign_value(n);
600 99 : break;
601 :
602 6934 : case N_Name:
603 : case N_ModPath:
604 : case D_Module:
605 : case D_Import:
606 : case D_Type:
607 : case D_Alias:
608 : case D_Effect:
609 : case D_Class:
610 : case D_Instance:
611 : case D_Foreign:
612 : case D_Fixity:
613 : case H_FixRel:
614 : case D_ExternType:
615 : case D_Sig:
616 : case D_Equation:
617 : case D_Error:
618 : case H_TyParam:
619 : case H_ConDef:
620 : case H_FieldType:
621 : case H_OpSig:
622 : case H_ForeignMember:
623 : case H_SigName:
624 : case L_Prefix:
625 : case L_Infix:
626 : case T_Var:
627 : case T_Con:
628 : case T_App:
629 : case T_Fun:
630 : case T_Qual:
631 : case T_With:
632 : case T_List:
633 : case T_Tuple:
634 : case T_Unit:
635 : case T_RowArg:
636 : case T_Transfer:
637 : case H_RowEntry:
638 : case P_Var:
639 : case P_Wild:
640 : case P_Int:
641 : case P_Str:
642 : case P_Char:
643 : case P_Con:
644 : case P_Cons:
645 : case P_Tuple:
646 : case P_List:
647 : case P_Unit:
648 : case P_As:
649 : case P_Record:
650 : case H_FieldPat:
651 : case E_Var:
652 : case E_Con:
653 : case E_Int:
654 : case E_Str:
655 : case E_Char:
656 : case E_Unit:
657 : case E_OpRef:
658 : case E_App:
659 : case E_Chain:
660 : case E_Dot:
661 : case E_Neg:
662 : case E_List:
663 : case E_Tuple:
664 : case E_Record:
665 : case E_Error:
666 : case S_Let:
667 : case S_Handle:
668 : case S_Use:
669 : case S_Discard:
670 : case H_ChainOp:
671 : case H_Bind:
672 : case H_UseBind:
673 : case H_Alt:
674 : case H_Clause:
675 : case H_Field:
676 : case W_File:
677 : case WOK_TAG_COUNT:
678 6934 : return false;
679 : }
680 : }
681 : return false;
682 : }
683 :
684 : // --------------------------------------------------------------- helpers
685 :
686 : static void p_node(Pr *p, const WokNode *n, Ctx c);
687 :
688 : // Every child starts with `brk` cleared: a group decides for itself whether it
689 : // fits, so breaking an outer chain does not force an inner list to break.
690 : //
691 : // `rest` is cleared too, and that is exact rather than merely safe wherever a
692 : // GROUP is the parent: if the group fit, everything inside it fit with room
693 : // for the group's own `rest`, and if the group broke, this child is the last
694 : // thing on its line. Only the UNBREAKABLE constructs -- juxtaposition, `.`,
695 : // an arrow head -- have to pass a reserve on, and they say so with sub_rest.
696 632395 : static Ctx sub(Ctx c, u32 prec) {
697 632395 : c.prec = prec;
698 632395 : c.lead = false;
699 632395 : c.brk = false;
700 632395 : c.pad = 0;
701 632395 : c.rest = 0;
702 632395 : return c;
703 : }
704 :
705 : // Keeps `lead`: this child writes the first token of the parent's line.
706 89126 : static Ctx lead_sub(Ctx c, u32 prec) {
707 89126 : c.prec = prec;
708 89126 : c.brk = false;
709 89126 : c.pad = 0;
710 89126 : c.rest = 0;
711 89126 : return c;
712 : }
713 :
714 81699 : static Ctx sub_rest(Ctx c, u32 prec, usize extra) {
715 81699 : usize keep = c.rest + extra;
716 81699 : Ctx r = sub(c, prec);
717 81699 : r.rest = keep;
718 81699 : return r;
719 : }
720 :
721 37028 : static Ctx lead_rest(Ctx c, u32 prec, usize extra) {
722 37028 : usize keep = c.rest + extra;
723 37028 : Ctx r = lead_sub(c, prec);
724 37028 : r.rest = keep;
725 37028 : return r;
726 : }
727 :
728 : // Everything inside a bracket group. Layout is suspended there, so `flat` is
729 : // what stops a BLOCK from being written where it would silently flatten.
730 147115 : static Ctx bracket(Ctx c, u32 prec) {
731 147115 : c.prec = prec;
732 147115 : c.flat = true;
733 147115 : c.lead = false;
734 147115 : c.brk = false;
735 147115 : c.pad = 0;
736 147115 : c.rest = 0;
737 147115 : return c;
738 : }
739 :
740 2647 : static bool span_eq(const Pr *p, WokSpan a, WokSpan b) {
741 2647 : return a.len == b.len && memcmp(p->src + a.off, p->src + b.off, a.len) == 0;
742 : }
743 :
744 : // A signature and the equation it describes are ONE unit to a reader, and
745 : // D23 makes the ordering law. A blank line between them, as "one blank line
746 : // between top-level items" would give, reads as two unrelated declarations.
747 : // Same for a run of imports under a module header.
748 24714 : static bool decls_are_one_unit(const Pr *p, const WokNode *prev,
749 : const WokNode *cur) {
750 24714 : if ((prev->tag == D_Module || prev->tag == D_Import) &&
751 3137 : (cur->tag == D_Module || cur->tag == D_Import))
752 : return true;
753 : // A run of `fixity` lines is one unit for the same reason a run of imports
754 : // is: each line is a fact about one operator, and the table they build is
755 : // read as a block. prelude/Base.wok writes twelve of them together.
756 23989 : if (prev->tag == D_Fixity && cur->tag == D_Fixity) return true;
757 23533 : if (prev->tag != D_Sig || cur->tag != D_Equation) return false;
758 1657 : const WokNode *lhs = D_Equation_lhs(cur);
759 1657 : if (lhs->tag != L_Prefix) return false; // an infix LHS names no signature
760 1609 : WokSpan want = L_Prefix_name(lhs);
761 1609 : WokSeq names = D_Sig_names(prev);
762 1749 : for (u32 i = 0; i < names.n; i++)
763 1635 : if (span_eq(p, H_SigName_name(names.items[i]), want)) return true;
764 : return false;
765 : }
766 :
767 : // Backticks are a fact about the operator's SPELLING, not a stored bit: a
768 : // name can only have been written between backticks, a symbol run only bare.
769 34107 : static bool op_is_backticked(const Pr *p, WokSpan op) {
770 34107 : unsigned char c0 = op.len != 0 ? (unsigned char)p->src[op.off] : '+';
771 34107 : return (c0 >= 'a' && c0 <= 'z') || (c0 >= 'A' && c0 <= 'Z') || c0 == '_' ||
772 25403 : c0 >= 0x80;
773 : }
774 :
775 21224 : static void p_op(Pr *p, WokSpan op) {
776 21224 : if (op_is_backticked(p, op)) {
777 5315 : put_c(p, '`');
778 5315 : put_span(p, op);
779 5315 : put_c(p, '`');
780 : } else {
781 15909 : put_span(p, op);
782 : }
783 21224 : }
784 :
785 : // The columns p_op will write for `op`, backticks included.
786 12883 : static usize op_cols(const Pr *p, WokSpan op) {
787 12883 : return op.len + (op_is_backticked(p, op) ? 2u : 0u);
788 : }
789 :
790 136143 : static void p_list(Pr *p, WokSeq s, Ctx c, const char *sep) {
791 294952 : for (u32 i = 0; i < s.n; i++) {
792 158809 : if (i != 0) put_z(p, sep);
793 158809 : p_node(p, s.items[i], c);
794 : }
795 136143 : }
796 :
797 : // Defined with the filler below; declared here because juxtaposition has to
798 : // tell each argument how much of the line the arguments after it will take.
799 : static usize flat_after(const Pr *p, const WokNode *n, Ctx c);
800 :
801 : // Space-separated arguments, each at `prec`. Juxtaposition owns no separator,
802 : // so every one of these arguments is UNCONDITIONALLY on this line: an
803 : // argument's reserve is the flat width of the ones that follow it.
804 : // `guard_first` marks the one caller whose FIRST argument sits in a position
805 : // where a leading `-` is misread; see needs_lead_paren. Every other caller
806 : // passes false, and says so at the call site rather than leaving it to be
807 : // inferred from a default.
808 52939 : static void p_args(Pr *p, WokSeq s, Ctx c, u32 prec, bool guard_first) {
809 91301 : for (u32 i = 0; i < s.n; i++) {
810 38362 : usize after = 0;
811 49777 : for (u32 j = i + 1; j < s.n; j++)
812 11415 : after += 1 + flat_after(p, s.items[j], sub(c, prec));
813 38362 : put_c(p, ' ');
814 38362 : Ctx ac = sub_rest(c, prec, after);
815 38362 : ac.lead = guard_first && i == 0;
816 38362 : p_node(p, s.items[i], ac);
817 : }
818 52939 : }
819 :
820 : // ----------------------------------------------------------- line filling
821 : //
822 : // The GROUPS: the nodes that own a separator the layout filter reads as a
823 : // continuation lead, plus the bracketed lists, where layout is suspended
824 : // outright. Every other node is unbreakable, so a long one overflows and says
825 : // so. A group with one element owns no separator and so owns no break.
826 :
827 637346 : static bool is_group(const WokNode *n) {
828 637346 : switch ((WokTag)n->tag) {
829 2495 : case E_Chain:
830 2495 : return E_Chain_ops(n).n != 0;
831 : case T_With:
832 : case T_Fun:
833 : return true;
834 3148 : case D_Type:
835 3148 : return D_Type_cons(n).n > 1;
836 2552 : case E_List:
837 2552 : return E_List_items(n).n > 1;
838 2082 : case E_Tuple:
839 2082 : return E_Tuple_items(n).n > 1;
840 9132 : case T_Tuple:
841 9132 : return T_Tuple_items(n).n > 1;
842 2259 : case P_List:
843 2259 : return P_List_items(n).n > 1;
844 2721 : case P_Tuple:
845 2721 : return P_Tuple_items(n).n > 1;
846 2109 : case E_Record:
847 2736 : return E_Record_fields(n).n + (E_Record_spread(n) != nullptr ? 1u : 0u) >
848 : 1u;
849 4241 : case P_Record:
850 6132 : return P_Record_fields(n).n + (P_Record_is_open(n) ? 1u : 0u) > 1u;
851 6550 : case H_ConDef:
852 8250 : return H_ConDef_is_record(n) && H_ConDef_fields(n).n > 1;
853 :
854 579907 : case N_Name:
855 : case N_ModPath:
856 : case D_Module:
857 : case D_Import:
858 : case D_Alias:
859 : case D_Effect:
860 : case D_Class:
861 : case D_Instance:
862 : case D_Foreign:
863 : case D_ExternType:
864 : case D_Sig:
865 : case D_Fixity:
866 : case H_FixRel:
867 : case D_Equation:
868 : case D_Error:
869 : case H_TyParam:
870 : case H_FieldType:
871 : case H_OpSig:
872 : case H_ForeignMember:
873 : case H_SigName:
874 : case L_Prefix:
875 : case L_Infix:
876 : case T_Var:
877 : case T_Con:
878 : case T_App:
879 : case T_Qual:
880 : case T_List:
881 : case T_Unit:
882 : case T_RowArg:
883 : case T_Transfer:
884 : case H_RowEntry:
885 : case P_Var:
886 : case P_Wild:
887 : case P_Int:
888 : case P_Str:
889 : case P_Char:
890 : case P_Con:
891 : case P_Cons:
892 : case P_Unit:
893 : case P_As:
894 : case H_FieldPat:
895 : case E_Var:
896 : case E_Con:
897 : case E_Int:
898 : case E_Str:
899 : case E_Char:
900 : case E_Unit:
901 : case E_OpRef:
902 : case E_App:
903 : case E_Dot:
904 : case E_Neg:
905 : case E_Lambda:
906 : case E_LetIn:
907 : case E_HandleIn:
908 : case E_UseIn:
909 : case E_If:
910 : case E_Case:
911 : case E_Handler:
912 : case E_Assign:
913 : case E_Block:
914 : case E_Error:
915 : case S_Let:
916 : case S_Handle:
917 : case S_Use:
918 : case S_Discard:
919 : case H_ChainOp:
920 : case H_Bind:
921 : case H_UseBind:
922 : case H_Alt:
923 : case H_Clause:
924 : case H_Field:
925 : case W_File:
926 : case WOK_TAG_COUNT:
927 579907 : return false;
928 : }
929 0 : WOK_UNREACHABLE();
930 : }
931 :
932 : // The width `n` would occupy written on one line. Re-rendering into a counter
933 : // is what the measure-only sink is for; `nofill` makes the measured pass take
934 : // the flat arm of every group, so a measurement never recurses into another.
935 : // A subtree that HAS to break a line -- a `case` written after `->`, say --
936 : // has no flat width at all, and m.cols would report only its last line's,
937 : // which is smaller than the truth. WOK_PRINT_UNBOUNDED says so instead.
938 : enum { WOK_PRINT_UNBOUNDED = WOK_PRINT_WIDTH * 100 };
939 :
940 105346 : static usize flat_cols(const Pr *p, const WokNode *n, Ctx c) {
941 105346 : Pr m = *p;
942 105346 : m.to_file = false;
943 105346 : m.count_only = true;
944 105346 : m.file = nullptr;
945 105346 : m.buf = nullptr;
946 105346 : m.len = 0;
947 105346 : m.cap = 0;
948 105346 : m.cols = 0;
949 105346 : m.quiet = true;
950 105346 : m.nofill = true;
951 105346 : m.nl_seen = false;
952 105346 : p_node(&m, n, c);
953 105346 : return m.nl_seen ? (usize)WOK_PRINT_UNBOUNDED : m.cols;
954 : }
955 :
956 : // The same width, or 0 during a measuring pass. A measuring pass never breaks,
957 : // so it never needs to know what follows -- and asking would make measuring a
958 : // juxtaposition cost one measurement per argument per level.
959 82705 : static usize flat_after(const Pr *p, const WokNode *n, Ctx c) {
960 82705 : return p->nofill ? 0 : flat_cols(p, n, c);
961 : }
962 :
963 : // THE DECISION, and the whole of it: a group that does not fit in the columns
964 : // remaining breaks at EVERY opportunity it owns. It depends only on the tree
965 : // and on the column reached so far -- never on the text already emitted --
966 : // which is what leaves the fixed point a corollary of tree identity rather
967 : // than a separate risk.
968 1088440 : static bool decide_break(const Pr *p, const WokNode *n, Ctx c) {
969 1088440 : if (p->nofill) return false;
970 637346 : if (!is_group(n)) return false;
971 43718 : return p->cols + flat_cols(p, n, c) + c.rest > WOK_PRINT_WIDTH;
972 : }
973 :
974 : // True when p_body will write `body` on the current line rather than as an
975 : // indented block of its own.
976 69651 : static bool body_is_inline(const WokNode *body, bool flat) {
977 69651 : return !(body->tag == E_Block && !flat);
978 : }
979 :
980 : // The columns p_body will add to the CURRENT line: the space and the body
981 : // itself, or nothing when the body is a block of its own.
982 14587 : static usize body_cols(const Pr *p, const WokNode *body, Ctx c) {
983 14587 : if (!body_is_inline(body, c.flat)) return 0;
984 14336 : usize w = flat_after(p, body, sub(c, EP_EXPR));
985 : // A body that opens a block of its own reserves nothing measurable: only
986 : // its FIRST line shares this one, and a reserve is a claim about this line.
987 : // Under-reserving can only leave a line over the width, which is reported;
988 : // over-reserving would wrap a head for an overflow no wrap can fix.
989 14336 : return w >= (usize)WOK_PRINT_UNBOUNDED ? 0 : 1 + w;
990 : }
991 :
992 : // Where a wrapped line starts. A capability row aligns under its own `with`
993 : // (decision 1); everything else takes the item's indent plus one level, which
994 : // is local and therefore stable under a rename.
995 2629 : static usize cont_col(Ctx c) {
996 2629 : return c.wrap != 0 ? c.wrap : (usize)c.ind * 2 + 2;
997 : }
998 :
999 : // A bracketed, comma-separated group. The separator LEADS its line, so every
1000 : // wrapped line begins with a continuation lead -- which is what keeps L7's
1001 : // bracket-abandonment rule from firing on a wrapped element and what lets
1002 : // wok_shape clamp the alignment instead of enforcing it.
1003 : typedef struct {
1004 : usize col; // the opener's column; separators and the closer align on it
1005 : bool brk;
1006 : } Group;
1007 :
1008 61450 : static Group group_open(Pr *p, const char *open, bool brk) {
1009 61450 : Group g = {.col = p->cols, .brk = brk};
1010 61450 : usize n = strlen(open);
1011 61450 : put_n(p, open, n);
1012 : // `[ a` rather than `[a`, so the first item clears the separator column.
1013 61450 : if (brk && open[n - 1] != ' ') put_c(p, ' ');
1014 61450 : return g;
1015 : }
1016 :
1017 : // The context for one item of `g`. Layout is suspended inside the brackets,
1018 : // so what this fixes is legibility, not legality.
1019 113666 : static Ctx group_item(Ctx c, u32 prec, Group g) {
1020 113666 : Ctx r = bracket(c, prec);
1021 113666 : r.wrap = g.col + 2;
1022 113666 : return r;
1023 : }
1024 :
1025 62008 : static void group_sep(Pr *p, Group g) {
1026 62008 : if (g.brk) nl_fill(p, g.col, true);
1027 62008 : put_z(p, ", ");
1028 62008 : }
1029 :
1030 61450 : static void group_close(Pr *p, Group g, const char *close) {
1031 61450 : if (!g.brk) {
1032 60441 : put_z(p, close);
1033 60441 : return;
1034 : }
1035 1009 : nl_fill(p, g.col, true);
1036 1009 : put_z(p, close[0] == ' ' ? close + 1 : close);
1037 : }
1038 :
1039 : // ------------------------------------------------------- arrow alignment
1040 :
1041 4723 : static bool is_arrow_headed(const WokNode *n) {
1042 4723 : if (n->tag == H_Alt) return true;
1043 3114 : return n->tag == H_Clause && H_Clause_kind(n) != WOK_CLAUSE_VAR;
1044 : }
1045 :
1046 : // Everything left of the `->` of an arrow-headed item. Factored out because
1047 : // a block must measure every head before it writes any of them.
1048 16820 : static void p_arrow_head(Pr *p, const WokNode *n, Ctx c) {
1049 16820 : if (n->tag == H_Alt) {
1050 8134 : p_node(p, H_Alt_pat(n), lead_rest(c, PP_PAT, 0));
1051 8134 : return;
1052 : }
1053 8686 : switch (H_Clause_kind(n)) {
1054 1899 : case WOK_CLAUSE_CONTROL:
1055 1899 : put_span(p, H_Clause_name(n));
1056 : // `, k` still follows every pattern on this line, and it is two columns
1057 : // wider than the name, so a binder run wraps at the right place.
1058 1899 : c.rest += 2 + H_Clause_k(n).len;
1059 1899 : p_args(p, H_Clause_pats(n), c, PP_ATOM, false);
1060 1899 : put_z(p, ", ");
1061 1899 : put_span(p, H_Clause_k(n));
1062 1899 : return;
1063 1725 : case WOK_CLAUSE_ABORT:
1064 1725 : put_z(p, "abort ");
1065 1725 : put_span(p, H_Clause_name(n));
1066 1725 : p_args(p, H_Clause_pats(n), c, PP_ATOM, false);
1067 1725 : return;
1068 2783 : case WOK_CLAUSE_RETURN:
1069 2783 : put_z(p, "return");
1070 2783 : p_args(p, H_Clause_pats(n), c, PP_ATOM, false);
1071 2783 : return;
1072 2279 : case WOK_CLAUSE_PLAIN:
1073 2279 : put_span(p, H_Clause_name(n));
1074 2279 : p_args(p, H_Clause_pats(n), c, PP_ATOM, false);
1075 2279 : return;
1076 : case WOK_CLAUSE_VAR:
1077 : break;
1078 : }
1079 0 : WOK_UNREACHABLE();
1080 : }
1081 :
1082 : // What an arrow head still has to share its line with: the alignment padding
1083 : // that follows it, the ` ->`, and the body when the body is written inline.
1084 : //
1085 : // Counting the body is the Wadler reading of "the columns remaining", and it
1086 : // is what makes a wide pattern wrap when a two-character body tips the line
1087 : // over. Its one wart: a head holding a breakable group, followed by a long
1088 : // UNBREAKABLE body, wraps for an overflow the wrap cannot fix. That is
1089 : // cosmetic, and the alternative -- ignoring the body -- leaves the far more
1090 : // common case silently over the width.
1091 : static usize head_cols(const Pr *p, const WokNode *n, Ctx c);
1092 :
1093 6256 : static Ctx head_ctx(const Pr *p, const WokNode *n, Ctx c, const WokNode *body) {
1094 6256 : usize head = head_cols(p, n, c);
1095 6256 : usize slack = c.pad > head ? c.pad - head : 0;
1096 6256 : c.rest += slack + 3 + body_cols(p, body, c);
1097 6256 : return c;
1098 : }
1099 :
1100 10564 : static usize head_cols(const Pr *p, const WokNode *n, Ctx c) {
1101 10564 : Pr m = *p;
1102 10564 : m.to_file = false;
1103 10564 : m.count_only = true;
1104 10564 : m.file = nullptr;
1105 10564 : m.buf = nullptr;
1106 10564 : m.len = 0;
1107 10564 : m.cap = 0;
1108 10564 : m.cols = 0;
1109 10564 : m.quiet = true;
1110 10564 : m.nofill = true; // a head never breaks a line, so measuring cannot either
1111 10564 : c.flat = true;
1112 10564 : c.pad = 0;
1113 10564 : p_arrow_head(&m, n, c);
1114 10564 : return m.cols;
1115 : }
1116 :
1117 : // ------------------------------------------------------------ block forms
1118 :
1119 : // A seq block, ALWAYS in the indented form at `ind` -- unless `flat`, where
1120 : // no indented form exists because layout is suspended inside brackets.
1121 : // `align` marks the two blocks whose arrows line up (decision 6).
1122 : //
1123 : // `owner` is the node whose trailing trivia closes the block (rule 3), or
1124 : // null for a `where`, which has no node of its own. Inside brackets no
1125 : // comment can be attached at all -- wok_trivia marks those items FLAT and
1126 : // gives them none -- so the flat arm has no trivia to write.
1127 36803 : static void p_seq_block(Pr *p, const WokNode *owner, WokSeq seq, u32 ind,
1128 : bool flat, bool align) {
1129 36803 : Ctx ic = {.prec = 0, .ind = ind, .flat = flat, .lead = true, .brk = false,
1130 : .pad = 0};
1131 :
1132 36803 : if (flat) {
1133 : // The parser can only have built a ONE-item block here, because L2
1134 : // suppresses every layout token between the brackets.
1135 2397 : if (seq.n != 1) unprintable(p, "a multi-item block inside brackets");
1136 4794 : for (u32 i = 0; i < seq.n; i++) {
1137 2397 : put_c(p, ' ');
1138 2397 : p_node(p, seq.items[i], (Ctx){
1139 : .prec = 0, .ind = ind, .flat = true,
1140 : .lead = false, .brk = false, .pad = 0});
1141 : }
1142 2397 : return;
1143 : }
1144 :
1145 : // This block's own column, which every item line of it must sit on and
1146 : // every filled line inside it must clear.
1147 34406 : if (p->nblk < WOK_PRINT_BLOCK_MAX) p->blk[p->nblk] = (u16)(ind * 2);
1148 34406 : p->nblk++;
1149 :
1150 34406 : if (align) {
1151 : usize widest = 0;
1152 7823 : for (u32 i = 0; i < seq.n; i++) {
1153 4723 : if (!is_arrow_headed(seq.items[i])) continue;
1154 4308 : usize w = head_cols(p, seq.items[i], ic);
1155 4308 : if (w > widest) widest = w;
1156 : }
1157 : // One long pattern must not push a whole block off the screen.
1158 3100 : if (widest <= WOK_PRINT_ALIGN_MAX) ic.pad = widest;
1159 : }
1160 :
1161 93743 : for (u32 i = 0; i < seq.n; i++) {
1162 59337 : const WokTriviaEntry *e = trivia_of(p, seq.items[i]);
1163 : // Nothing precedes the first item, so its paragraph break has nowhere to
1164 : // go; decision 4's "no blank line at the start of a block" still holds.
1165 59337 : p_lead(p, e, ind, i != 0);
1166 59337 : nl(p, ind, LN_ITEM);
1167 59337 : p_node(p, seq.items[i], ic);
1168 59337 : p_trail(p, seq.items[i], e);
1169 : }
1170 34406 : p_block_close(p, owner, ind);
1171 34406 : p->nblk--;
1172 : }
1173 :
1174 : // The right-hand side of `=` or `->`. A block body is the tree's own
1175 : // statement, so it is written indented; anything else follows on the line.
1176 : // `rest` is what the caller still writes after it on that line -- the ` in`
1177 : // of a `let`, the ` else` of an `if`.
1178 55064 : static void p_body(Pr *p, const WokNode *body, u32 ind, bool flat,
1179 : usize rest) {
1180 55064 : if (!body_is_inline(body, flat)) {
1181 1601 : p_seq_block(p, body, E_Block_stmts(body), ind + 1, false, false);
1182 1601 : return;
1183 : }
1184 53463 : put_c(p, ' ');
1185 53463 : p_node(p, body,
1186 53463 : (Ctx){.prec = EP_EXPR, .ind = ind, .flat = flat, .lead = false,
1187 : .brk = false, .pad = 0, .rest = rest});
1188 : }
1189 :
1190 : // `where` sits one level under its item, and its bindings one level under
1191 : // that. That column is exactly the body block's own column, which is what
1192 : // keeps `where` INSIDE the body's block region -- where parse_equation and
1193 : // parse_alt look for it -- instead of dedenting past it.
1194 30189 : static void p_wheres(Pr *p, WokSeq w, u32 ind, bool flat) {
1195 30189 : if (w.n == 0) return;
1196 16067 : if (flat) {
1197 0 : put_z(p, " where");
1198 0 : p_seq_block(p, nullptr, w, ind, true, false);
1199 0 : return;
1200 : }
1201 16067 : nl(p, ind + 1, LN_LEAD);
1202 16067 : put_z(p, "where");
1203 16067 : p_seq_block(p, nullptr, w, ind + 2, false, false);
1204 : }
1205 :
1206 : // An inline body that OPENS a block must be pushed one level deeper when a
1207 : // `where` follows, so the `where` line closes that block rather than landing
1208 : // level with it.
1209 30189 : static u32 body_indent(const WokNode *body, WokSeq wheres, u32 ind) {
1210 30189 : if (wheres.n == 0 || body->tag == E_Block) return ind;
1211 15426 : return ind + 1;
1212 : }
1213 :
1214 : // ------------------------------------------------------------- the switch
1215 :
1216 : // `if` needs its three parts written in one of two shapes; keeping it out of
1217 : // the giant switch keeps that decision readable.
1218 : static void p_if(Pr *p, const WokNode *n, Ctx c);
1219 :
1220 1088440 : static void p_node_inner(Pr *p, const WokNode *n, Ctx c) {
1221 1088440 : switch ((WokTag)n->tag) {
1222 : // ------------------------------------------------------------- names
1223 91640 : case N_Name:
1224 91640 : put_span(p, N_Name_text(n));
1225 91640 : return;
1226 84689 : case N_ModPath:
1227 84689 : p_list(p, N_ModPath_parts(n), sub(c, PREC_FIXED), ".");
1228 84689 : return;
1229 :
1230 : // ------------------------------------------------------ declarations
1231 2989 : case D_Module:
1232 2989 : put_z(p, "module ");
1233 2989 : p_node(p, D_Module_path(n), sub(c, PREC_FIXED));
1234 2989 : return;
1235 2636 : case D_Import: {
1236 2636 : put_z(p, "import ");
1237 2636 : p_node(p, D_Import_path(n), sub(c, PREC_FIXED));
1238 2636 : WokSeq names = D_Import_names(n);
1239 2636 : if (names.n != 0) {
1240 1442 : put_z(p, " (");
1241 1442 : p_list(p, names, bracket(c, PREC_FIXED), ", ");
1242 1442 : put_c(p, ')');
1243 : }
1244 2636 : if (D_Import_alias(n) != nullptr) {
1245 1535 : put_z(p, " as ");
1246 1535 : p_node(p, D_Import_alias(n), sub(c, PREC_FIXED));
1247 : }
1248 2636 : return;
1249 : }
1250 5380 : case D_Type: {
1251 5380 : put_z(p, "type ");
1252 5380 : put_span(p, D_Type_name(n));
1253 5380 : p_args(p, D_Type_params(n), c, PREC_FIXED, false);
1254 5380 : put_z(p, " = ");
1255 : // The FIRST alternative stays on the `=` line: a break before it would
1256 : // put an item lead at the head of the next line. Every later one is led
1257 : // by its `|`, which is a continuation lead and so may lead a line.
1258 5380 : WokSeq cons = D_Type_cons(n);
1259 17564 : for (u32 i = 0; i < cons.n; i++) {
1260 12184 : if (i != 0) {
1261 6804 : if (c.brk) {
1262 1780 : nl_fill(p, cont_col(c), c.flat);
1263 1780 : put_z(p, "| ");
1264 : } else {
1265 5024 : put_z(p, " | ");
1266 : }
1267 : }
1268 12184 : p_node(p, cons.items[i], sub(c, PREC_FIXED));
1269 : }
1270 5380 : return;
1271 : }
1272 2272 : case D_Alias:
1273 2272 : put_z(p, "alias ");
1274 2272 : put_span(p, D_Alias_name(n));
1275 2272 : p_args(p, D_Alias_params(n), c, PREC_FIXED, false);
1276 2272 : put_z(p, " = ");
1277 2272 : p_node(p, D_Alias_body(n), sub(c, TP_TYPE));
1278 2272 : return;
1279 3259 : case D_Effect:
1280 3259 : put_z(p, "effect ");
1281 3259 : put_span(p, D_Effect_name(n));
1282 3259 : p_args(p, D_Effect_params(n), c, PREC_FIXED, false);
1283 3259 : p_seq_block(p, n, D_Effect_ops(n), c.ind + 1, c.flat, false);
1284 3259 : return;
1285 3617 : case D_Class:
1286 3617 : put_z(p, "class ");
1287 3617 : put_span(p, D_Class_name(n));
1288 3617 : p_args(p, D_Class_params(n), c, PREC_FIXED, false);
1289 3617 : p_seq_block(p, n, D_Class_body(n), c.ind + 1, c.flat, false);
1290 3617 : return;
1291 3865 : case D_Instance:
1292 3865 : put_z(p, "instance ");
1293 : // The head's context is parenthesised by the GRAMMAR, not by
1294 : // precedence: parse_instance_decl demands the brackets.
1295 3865 : if (D_Instance_ctx(n) != nullptr) {
1296 2566 : put_c(p, '(');
1297 2566 : p_node(p, D_Instance_ctx(n), bracket(c, TP_TYPE));
1298 2566 : put_z(p, ") => ");
1299 : }
1300 3865 : put_span(p, D_Instance_name(n));
1301 3865 : p_args(p, D_Instance_args(n), c, TP_ATOM, false);
1302 3865 : p_seq_block(p, n, D_Instance_body(n), c.ind + 1, c.flat, false);
1303 3865 : return;
1304 2897 : case D_Foreign:
1305 2897 : put_z(p, "foreign module ");
1306 2897 : put_span(p, D_Foreign_name(n));
1307 2897 : put_c(p, ' ');
1308 2897 : put_span(p, D_Foreign_lib(n));
1309 2897 : p_seq_block(p, n, D_Foreign_members(n), c.ind + 1, c.flat, false);
1310 2897 : return;
1311 2768 : case D_ExternType:
1312 2768 : put_z(p, "extern type ");
1313 2768 : put_span(p, D_ExternType_name(n));
1314 2768 : p_args(p, D_ExternType_params(n), c, PREC_FIXED, false);
1315 2768 : return;
1316 21789 : case D_Sig:
1317 21789 : if (D_Sig_is_extern(n)) put_z(p, "extern ");
1318 21789 : p_list(p, D_Sig_names(n), sub(c, PREC_FIXED), ", ");
1319 21789 : put_z(p, " : ");
1320 21789 : p_node(p, D_Sig_type(n), sub(c, TP_TYPE));
1321 21789 : return;
1322 : // `fixity + left tighter than *`. One line, never filled: the relations
1323 : // are a sentence, and breaking `tighter than` across lines would put a
1324 : // continuation lead where the reader expects the rest of a phrase.
1325 5183 : case D_Fixity: {
1326 5183 : put_z(p, "fixity ");
1327 5183 : put_span(p, D_Fixity_name(n));
1328 8115 : put_z(p, D_Fixity_assoc(n) == WOK_ASSOC_RIGHT ? " right" : " left");
1329 5183 : WokSeq rels = D_Fixity_rels(n);
1330 10701 : for (u32 i = 0; i < rels.n; i++) {
1331 5518 : put_c(p, ' ');
1332 5518 : p_node(p, rels.items[i], sub(c, PREC_FIXED));
1333 : }
1334 5183 : return;
1335 : }
1336 5518 : case H_FixRel:
1337 8272 : put_z(p, H_FixRel_sense(n) == WOK_FIXREL_LOOSER ? "looser than "
1338 : : "tighter than ");
1339 5518 : put_span(p, H_FixRel_name(n));
1340 5518 : return;
1341 27134 : case D_Equation: {
1342 27134 : WokSeq wheres = D_Equation_wheres(n);
1343 27134 : const WokNode *body = D_Equation_body(n);
1344 27134 : p_node(p, D_Equation_lhs(n), lead_sub(c, PP_PAT));
1345 27134 : put_z(p, " =");
1346 27134 : p_body(p, body, body_indent(body, wheres, c.ind), c.flat, 0);
1347 27134 : p_wheres(p, wheres, c.ind, c.flat);
1348 27134 : return;
1349 : }
1350 0 : case D_Error:
1351 : // A damaged item has no canonical form; its source text is the only
1352 : // faithful rendering, and a file holding one is not fit to format.
1353 0 : unprintable(p, "a damaged declaration");
1354 0 : put_span(p, D_Error_text(n));
1355 0 : return;
1356 :
1357 : // ----------------------------------------------- declaration helpers
1358 22040 : case H_TyParam:
1359 22040 : if (H_TyParam_is_row(n)) {
1360 11027 : put_z(p, "(row ");
1361 11027 : put_span(p, H_TyParam_name(n));
1362 11027 : put_c(p, ')');
1363 : } else {
1364 11013 : put_span(p, H_TyParam_name(n));
1365 : }
1366 : return;
1367 14601 : case H_ConDef: {
1368 14601 : WokSpan name = H_ConDef_name(n);
1369 14601 : if (!wok_span_empty(name)) put_span(p, name);
1370 14601 : if (H_ConDef_is_record(n)) {
1371 9992 : WokSeq fields = H_ConDef_fields(n);
1372 9992 : if (!wok_span_empty(name)) put_c(p, ' ');
1373 9992 : if (fields.n == 0) {
1374 1109 : put_z(p, "{}");
1375 : } else {
1376 8883 : Group g = group_open(p, "{ ", c.brk);
1377 27964 : for (u32 i = 0; i < fields.n; i++) {
1378 19081 : if (i != 0) group_sep(p, g);
1379 19081 : p_node(p, fields.items[i], group_item(c, PREC_FIXED, g));
1380 : }
1381 8883 : group_close(p, g, " }");
1382 : }
1383 : } else {
1384 4609 : p_args(p, H_ConDef_args(n), c, TP_ATOM, false);
1385 : }
1386 14601 : return;
1387 : }
1388 19081 : case H_FieldType:
1389 19081 : put_span(p, H_FieldType_name(n));
1390 19081 : put_z(p, " : ");
1391 19081 : p_node(p, H_FieldType_type(n), sub(c, TP_TYPE));
1392 19081 : return;
1393 6545 : case H_OpSig:
1394 6545 : put_span(p, H_OpSig_name(n));
1395 6545 : put_z(p, " : ");
1396 6545 : p_node(p, H_OpSig_type(n), sub(c, TP_TYPE));
1397 6545 : return;
1398 6085 : case H_ForeignMember: {
1399 6085 : put_span(p, H_ForeignMember_name(n));
1400 6085 : WokSpan sym = H_ForeignMember_symbol(n);
1401 6085 : if (!wok_span_empty(sym)) {
1402 3994 : put_c(p, ' ');
1403 3994 : put_span(p, sym);
1404 : }
1405 6085 : put_z(p, " : ");
1406 6085 : p_node(p, H_ForeignMember_type(n), sub(c, TP_TYPE));
1407 6085 : return;
1408 : }
1409 29977 : case H_SigName:
1410 29977 : if (H_SigName_paren(n)) {
1411 7052 : put_c(p, '(');
1412 7052 : put_span(p, H_SigName_name(n));
1413 7052 : put_c(p, ')');
1414 : } else {
1415 22925 : put_span(p, H_SigName_name(n));
1416 : }
1417 : return;
1418 14891 : case L_Prefix:
1419 14891 : if (L_Prefix_paren(n)) {
1420 3443 : put_c(p, '(');
1421 3443 : put_span(p, L_Prefix_name(n));
1422 3443 : put_c(p, ')');
1423 : } else {
1424 11448 : put_span(p, L_Prefix_name(n));
1425 : }
1426 14891 : p_args(p, L_Prefix_args(n), c, PP_ATOM, !L_Prefix_paren(n));
1427 14891 : return;
1428 12883 : case L_Infix: {
1429 12883 : const WokNode *right = L_Infix_right(n);
1430 12883 : WokSpan op = L_Infix_op(n);
1431 12883 : p_node(p, L_Infix_left(n),
1432 : lead_rest(c, PP_ATOM,
1433 12883 : 2 + op_cols(p, op) +
1434 12883 : flat_after(p, right, sub(c, PP_ATOM))));
1435 12883 : put_c(p, ' ');
1436 12883 : p_op(p, op);
1437 12883 : put_c(p, ' ');
1438 12883 : p_node(p, right, sub(c, PP_ATOM));
1439 12883 : return;
1440 : }
1441 :
1442 : // ------------------------------------------------------------- types
1443 64355 : case T_Var:
1444 64355 : put_span(p, T_Var_name(n));
1445 64355 : return;
1446 67830 : case T_Con:
1447 67830 : p_node(p, T_Con_path(n), sub(c, PREC_FIXED));
1448 67830 : return;
1449 18529 : case T_App: {
1450 18529 : const WokNode *arg = T_App_arg(n);
1451 18529 : p_node(p, T_App_fn(n), sub_rest(c, TP_APP,
1452 18529 : 1 + flat_after(p, arg, sub(c, TP_ATOM))));
1453 18529 : put_c(p, ' ');
1454 18529 : p_node(p, arg, sub(c, TP_ATOM));
1455 18529 : return;
1456 : }
1457 26777 : case T_Fun: {
1458 26777 : if (!c.brk) {
1459 26413 : p_node(p, T_Fun_from(n), sub(c, TP_APP));
1460 26413 : put_z(p, " -> ");
1461 26413 : p_node(p, T_Fun_to(n), sub(c, TP_ARROW));
1462 26413 : return;
1463 : }
1464 : // The whole arrow SPINE is one group, so every `->` in it breaks --
1465 : // walked here rather than left to the nested T_Fun, which would measure
1466 : // itself against the width freed by the break and decide to stay flat.
1467 364 : const WokNode *cur = n;
1468 364 : p_node(p, T_Fun_from(cur), sub(c, TP_APP));
1469 179 : for (;;) {
1470 543 : const WokNode *to = T_Fun_to(cur);
1471 543 : nl_fill(p, cont_col(c), c.flat);
1472 543 : put_z(p, "-> ");
1473 543 : if (to->tag != T_Fun) {
1474 364 : p_node(p, to, sub(c, TP_ARROW));
1475 364 : return;
1476 : }
1477 179 : p_node(p, T_Fun_from(to), sub(c, TP_APP));
1478 179 : cur = to;
1479 : }
1480 : }
1481 16768 : case T_Qual: {
1482 16768 : const WokNode *body = T_Qual_body(n);
1483 16768 : p_node(p, T_Qual_ctx(n),
1484 16768 : sub_rest(c, TP_ARROW, 4 + flat_after(p, body, sub(c, TP_TYPE))));
1485 16768 : put_z(p, " => ");
1486 16768 : p_node(p, body, sub(c, TP_TYPE));
1487 16768 : return;
1488 : }
1489 25944 : case T_With: {
1490 : // Decision 1: a wrapped capability row ALIGNS under its own `with`, and
1491 : // the `+` entries align under each other. `with` is a fixed anchor --
1492 : // renaming a row entry does not move it -- so the reflow is bounded,
1493 : // and wok_shape clamps the indentation of a continuation line anyway.
1494 25944 : usize start = p->cols; // the column the whole type begins on
1495 25944 : p_node(p, T_With_body(n), sub(c, TP_ARROW));
1496 25944 : WokSeq row = T_With_row(n);
1497 25944 : if (!c.brk) {
1498 25416 : put_z(p, " with ");
1499 25416 : p_list(p, row, sub(c, PREC_FIXED), " + ");
1500 25416 : return;
1501 : }
1502 : // The alignment guard, for the same reason decision 6 has one: one long
1503 : // NAME must not push a whole row off the screen. The `with` column is
1504 : // set by what precedes the type, so a long function name would carry
1505 : // every row entry right with it. When alignment does not leave the
1506 : // widest entry room, the row falls back to the fixed item indent.
1507 : usize widest = 0;
1508 1867 : for (u32 i = 0; i < row.n; i++) {
1509 1339 : usize w = flat_cols(p, row.items[i], sub(c, PREC_FIXED));
1510 1339 : if (w > widest) widest = w;
1511 : }
1512 : // `with ` is five columns, and every entry begins in that column.
1513 528 : usize wcol = start;
1514 528 : if (!c.flat && (wcol <= block_col(p) ||
1515 44 : wcol + 5 + widest > WOK_PRINT_WIDTH))
1516 0 : wcol = cont_col(c);
1517 528 : nl_fill(p, wcol, c.flat);
1518 528 : put_z(p, "with ");
1519 1867 : for (u32 i = 0; i < row.n; i++) {
1520 1339 : if (i != 0) {
1521 : // `+ ` is two columns, and `with ` is five: three deeper than the
1522 : // `with` puts the entries in one column.
1523 811 : nl_fill(p, wcol + 3, c.flat);
1524 811 : put_z(p, "+ ");
1525 : }
1526 1339 : p_node(p, row.items[i], sub(c, PREC_FIXED));
1527 : }
1528 : return;
1529 : }
1530 15248 : case T_List:
1531 15248 : put_c(p, '[');
1532 15248 : p_node(p, T_List_elem(n), bracket(c, TP_TYPE));
1533 15248 : put_c(p, ']');
1534 15248 : return;
1535 24010 : case T_Tuple: {
1536 24010 : WokSeq items = T_Tuple_items(n);
1537 24010 : Group g = group_open(p, "(", c.brk);
1538 76279 : for (u32 i = 0; i < items.n; i++) {
1539 52269 : if (i != 0) group_sep(p, g);
1540 52269 : p_node(p, items.items[i], group_item(c, TP_TYPE, g));
1541 : }
1542 24010 : group_close(p, g, ")");
1543 24010 : return;
1544 : }
1545 55360 : case T_Unit:
1546 55360 : put_z(p, "()");
1547 55360 : return;
1548 51296 : case T_RowArg:
1549 51296 : put_z(p, "(row ");
1550 51296 : put_span(p, T_RowArg_name(n));
1551 51296 : put_c(p, ')');
1552 51296 : return;
1553 656 : case T_Transfer:
1554 656 : switch (T_Transfer_mode(n)) {
1555 297 : case WOK_TRANSFER_OWN:
1556 297 : put_z(p, "own ");
1557 297 : break;
1558 211 : case WOK_TRANSFER_LEND:
1559 211 : put_z(p, "lend ");
1560 211 : break;
1561 148 : case WOK_TRANSFER_COPY:
1562 148 : put_z(p, "copy ");
1563 148 : break;
1564 0 : default:
1565 0 : unprintable(p, "an unknown FFI transfer mode");
1566 0 : break;
1567 : }
1568 656 : p_node(p, T_Transfer_body(n), sub_rest(c, TP_APP, 0));
1569 656 : return;
1570 38179 : case H_RowEntry:
1571 38179 : switch (H_RowEntry_kind(n)) {
1572 7021 : case WOK_ROW_ROLE:
1573 7021 : put_c(p, '(');
1574 7021 : put_span(p, H_RowEntry_label(n));
1575 7021 : put_z(p, " : ");
1576 7021 : p_node(p, H_RowEntry_type(n), bracket(c, TP_APP));
1577 7021 : put_c(p, ')');
1578 7021 : return;
1579 8368 : case WOK_ROW_VAR:
1580 8368 : put_z(p, "eff ");
1581 8368 : put_span(p, H_RowEntry_label(n));
1582 8368 : return;
1583 22790 : case WOK_ROW_SLOT:
1584 : // parse_rowentry reads a SLOT at typeapp level, so anything
1585 : // looser has to be bracketed to survive the round trip.
1586 22790 : p_node(p, H_RowEntry_type(n), sub(c, TP_APP));
1587 22790 : return;
1588 0 : default:
1589 0 : unprintable(p, "an unknown row entry kind");
1590 0 : return;
1591 : }
1592 :
1593 : // ---------------------------------------------------------- patterns
1594 15069 : case P_Var:
1595 15069 : put_span(p, P_Var_name(n));
1596 15069 : return;
1597 12804 : case P_Wild:
1598 12804 : put_c(p, '_');
1599 12804 : return;
1600 10385 : case P_Int:
1601 10385 : if (P_Int_negative(n)) put_c(p, '-');
1602 10385 : put_uint(p, P_Int_value(n));
1603 10385 : return;
1604 9854 : case P_Str:
1605 9854 : put_span(p, P_Str_text(n));
1606 9854 : return;
1607 10884 : case P_Char:
1608 10884 : put_span(p, P_Char_text(n));
1609 10884 : return;
1610 3592 : case P_Con:
1611 3592 : p_node(p, P_Con_path(n), lead_sub(c, PREC_FIXED));
1612 3592 : p_args(p, P_Con_args(n), c, PP_ATOM, false);
1613 3592 : return;
1614 3448 : case P_Cons: {
1615 3448 : const WokNode *tail = P_Cons_tail(n);
1616 3448 : p_node(p, P_Cons_head(n),
1617 3448 : lead_rest(c, PP_APP, 4 + flat_after(p, tail, sub(c, PP_PAT))));
1618 3448 : put_z(p, " :: ");
1619 3448 : p_node(p, tail, sub(c, PP_PAT));
1620 3448 : return;
1621 : }
1622 7578 : case P_Tuple: {
1623 7578 : WokSeq items = P_Tuple_items(n);
1624 7578 : Group g = group_open(p, "(", c.brk);
1625 23702 : for (u32 i = 0; i < items.n; i++) {
1626 16124 : if (i != 0) group_sep(p, g);
1627 16124 : p_node(p, items.items[i], group_item(c, PP_PAT, g));
1628 : }
1629 7578 : group_close(p, g, ")");
1630 7578 : return;
1631 : }
1632 3899 : case P_List: {
1633 3899 : WokSeq items = P_List_items(n);
1634 3899 : Group g = group_open(p, "[", c.brk);
1635 6002 : for (u32 i = 0; i < items.n; i++) {
1636 2103 : if (i != 0) group_sep(p, g);
1637 2103 : p_node(p, items.items[i], group_item(c, PP_PAT, g));
1638 : }
1639 3899 : group_close(p, g, "]");
1640 3899 : return;
1641 : }
1642 12257 : case P_Unit:
1643 12257 : put_z(p, "()");
1644 12257 : return;
1645 3544 : case P_As:
1646 3544 : p_node(p, P_As_pat(n),
1647 3544 : lead_rest(c, PP_ATOM, 4 + P_As_name(n).len));
1648 3544 : put_z(p, " as ");
1649 3544 : put_span(p, P_As_name(n));
1650 3544 : return;
1651 7642 : case P_Record: {
1652 7642 : WokSeq fields = P_Record_fields(n);
1653 7642 : bool open = P_Record_is_open(n);
1654 7642 : p_node(p, P_Record_path(n), lead_sub(c, PREC_FIXED));
1655 7642 : put_c(p, ' ');
1656 7642 : if (!open && fields.n == 0) {
1657 2609 : put_z(p, "{}");
1658 2609 : return;
1659 : }
1660 5033 : Group g = group_open(p, "{ ", c.brk);
1661 5033 : bool first = true;
1662 5033 : if (open) {
1663 4455 : put_z(p, "..");
1664 4455 : WokSpan rest = P_Record_rest(n);
1665 4455 : if (!wok_span_empty(rest)) {
1666 2942 : put_c(p, ' ');
1667 2942 : put_span(p, rest);
1668 : }
1669 4455 : first = false;
1670 : }
1671 7368 : for (u32 i = 0; i < fields.n; i++) {
1672 2335 : if (!first) group_sep(p, g);
1673 2335 : first = false;
1674 2335 : p_node(p, fields.items[i], group_item(c, PREC_FIXED, g));
1675 : }
1676 5033 : group_close(p, g, " }");
1677 5033 : return;
1678 : }
1679 2335 : case H_FieldPat: {
1680 2335 : WokSpan name = H_FieldPat_name(n);
1681 2335 : const WokNode *pat = H_FieldPat_pat(n);
1682 : // `{ x }` and `{ x = x }` are one tree; the punned spelling is the
1683 : // canonical one.
1684 2335 : if (pat->tag == P_Var && span_eq(p, P_Var_name(pat), name)) {
1685 117 : put_span(p, name);
1686 117 : return;
1687 : }
1688 2218 : put_span(p, name);
1689 2218 : put_z(p, " = ");
1690 2218 : p_node(p, pat, bracket(c, PP_PAT));
1691 2218 : return;
1692 : }
1693 :
1694 : // ------------------------------------------------------- expressions
1695 18745 : case E_Var:
1696 18745 : put_span(p, E_Var_name(n));
1697 18745 : return;
1698 15530 : case E_Con:
1699 15530 : put_span(p, E_Con_name(n));
1700 15530 : return;
1701 15158 : case E_Int:
1702 15158 : put_uint(p, E_Int_value(n));
1703 15158 : return;
1704 11766 : case E_Str:
1705 11766 : put_span(p, E_Str_text(n));
1706 11766 : return;
1707 9430 : case E_Char:
1708 9430 : put_span(p, E_Char_text(n));
1709 9430 : return;
1710 11131 : case E_Unit:
1711 11131 : put_z(p, "()");
1712 11131 : return;
1713 11981 : case E_OpRef:
1714 11981 : put_c(p, '(');
1715 11981 : put_span(p, E_OpRef_name(n));
1716 11981 : put_c(p, ')');
1717 11981 : return;
1718 5326 : case E_App: {
1719 5326 : const WokNode *arg = E_App_arg(n);
1720 5326 : p_node(p, E_App_fn(n),
1721 5326 : lead_rest(c, EP_APP, 1 + flat_after(p, arg, sub(c, EP_ATOM))));
1722 5326 : put_c(p, ' ');
1723 5326 : p_node(p, arg, sub(c, EP_ATOM));
1724 5326 : return;
1725 : }
1726 5649 : case E_Chain: {
1727 : // EP_NEG, not EP_APP: `-a + b` is one chain whose head is a negation,
1728 : // and a spine head is exactly where negation is legal bare.
1729 5649 : p_node(p, E_Chain_head(n), lead_sub(c, EP_NEG));
1730 5649 : WokSeq ops = E_Chain_ops(n);
1731 13990 : for (u32 i = 0; i < ops.n; i++) {
1732 : // Every operand is separated by an OPERATOR, which is the one token
1733 : // class the layout filter reads as continuing the line.
1734 8341 : if (c.brk)
1735 306 : nl_fill(p, cont_col(c), c.flat);
1736 : else
1737 8035 : put_c(p, ' ');
1738 8341 : p_node(p, ops.items[i], sub(c, PREC_FIXED));
1739 : }
1740 5649 : return;
1741 : }
1742 8341 : case H_ChainOp:
1743 8341 : p_op(p, H_ChainOp_op(n));
1744 8341 : put_c(p, ' ');
1745 8341 : p_node(p, H_ChainOp_rhs(n), sub(c, EP_NEG)); // `a + -b`
1746 8341 : return;
1747 3693 : case E_Dot:
1748 3693 : p_node(p, E_Dot_recv(n), lead_rest(c, EP_ATOM, 1 + E_Dot_name(n).len));
1749 3693 : put_c(p, '.');
1750 3693 : put_span(p, E_Dot_name(n));
1751 3693 : return;
1752 2404 : case E_Neg:
1753 2404 : put_c(p, '-');
1754 : // Negation takes the whole application to its right, so its body is an
1755 : // application spine. A nested negation is looser than that and brackets
1756 : // itself, which is also what keeps `- -x` from lexing as the comment
1757 : // opener `--`; the ladder states it, so no case here has to.
1758 2404 : p_node(p, E_Neg_body(n), sub_rest(c, EP_APP, 0));
1759 2404 : return;
1760 4316 : case E_List: {
1761 4316 : WokSeq items = E_List_items(n);
1762 4316 : Group g = group_open(p, "[", c.brk);
1763 8620 : for (u32 i = 0; i < items.n; i++) {
1764 4304 : if (i != 0) group_sep(p, g);
1765 4304 : p_node(p, items.items[i], group_item(c, EP_EXPR, g));
1766 : }
1767 4316 : group_close(p, g, "]");
1768 4316 : return;
1769 : }
1770 4647 : case E_Tuple: {
1771 4647 : WokSeq items = E_Tuple_items(n);
1772 4647 : Group g = group_open(p, "(", c.brk);
1773 14859 : for (u32 i = 0; i < items.n; i++) {
1774 10212 : if (i != 0) group_sep(p, g);
1775 10212 : p_node(p, items.items[i], group_item(c, EP_EXPR, g));
1776 : }
1777 4647 : group_close(p, g, ")");
1778 4647 : return;
1779 : }
1780 2205 : case E_Lambda: {
1781 2205 : put_c(p, '\\');
1782 2205 : WokSeq params = E_Lambda_params(n);
1783 2637 : for (u32 i = 0; i < params.n; i++) {
1784 432 : if (i != 0) put_c(p, ' ');
1785 432 : p_node(p, params.items[i], sub(c, PP_ATOM));
1786 : }
1787 2205 : put_z(p, " ->");
1788 2205 : p_body(p, E_Lambda_body(n), c.ind, c.flat, c.rest);
1789 2205 : return;
1790 : }
1791 1660 : case E_LetIn: {
1792 1660 : const WokNode *bind = E_LetIn_bind(n);
1793 1660 : const WokNode *lbody = E_LetIn_body(n);
1794 1660 : bool hang = !c.flat && opens_block(H_Bind_body(bind));
1795 1660 : put_z(p, "let ");
1796 3320 : p_node(p, bind,
1797 31 : hang ? sub(c, PREC_FIXED)
1798 1629 : : sub_rest(c, PREC_FIXED, 3 + body_cols(p, lbody, c)));
1799 : // `in` closes the binding's block, so it must start a line SHALLOWER
1800 : // than that block when there is one.
1801 1660 : if (hang) {
1802 31 : nl(p, c.ind, LN_LEAD);
1803 31 : put_z(p, "in");
1804 : } else {
1805 1629 : put_z(p, " in");
1806 : }
1807 1660 : p_body(p, lbody, c.ind, c.flat, c.rest);
1808 1660 : return;
1809 : }
1810 2331 : case E_HandleIn: {
1811 2331 : const WokNode *handler = E_HandleIn_handler(n);
1812 2331 : WokSpan label = E_HandleIn_label(n);
1813 2331 : put_z(p, "handle ");
1814 2331 : if (!wok_span_empty(label)) {
1815 1357 : put_span(p, label);
1816 1357 : put_z(p, " = ");
1817 : }
1818 2331 : p_node(p, handler, sub(c, EP_EXPR));
1819 2331 : if (!c.flat && opens_block(handler)) {
1820 36 : nl(p, c.ind, LN_LEAD);
1821 36 : put_z(p, "in");
1822 : } else {
1823 2295 : put_z(p, " in");
1824 : }
1825 2331 : p_body(p, E_HandleIn_body(n), c.ind, c.flat, c.rest);
1826 2331 : return;
1827 : }
1828 2719 : case E_UseIn:
1829 2719 : put_z(p, "use ");
1830 2719 : p_list(p, E_UseIn_binds(n), sub(c, PREC_FIXED), ", ");
1831 2719 : put_z(p, " in");
1832 2719 : p_body(p, E_UseIn_body(n), c.ind, c.flat, c.rest);
1833 2719 : return;
1834 3477 : case E_If:
1835 3477 : p_if(p, n, c);
1836 3477 : return;
1837 2317 : case E_Case: {
1838 2317 : const WokNode *scrut = E_Case_scrut(n);
1839 2317 : bool split = !c.flat && opens_block(scrut);
1840 2317 : put_z(p, "case ");
1841 : {
1842 2317 : Ctx sc = sub(c, EP_EXPR);
1843 2317 : if (split) sc.ind = c.ind + 1;
1844 2317 : p_node(p, scrut, sc);
1845 : }
1846 2317 : if (split) {
1847 84 : nl(p, c.ind + 1, LN_LEAD);
1848 84 : put_z(p, "of");
1849 : } else {
1850 2233 : put_z(p, " of");
1851 : }
1852 2317 : p_seq_block(p, n, E_Case_alts(n), c.ind + 1, c.flat, true);
1853 2317 : return;
1854 : }
1855 3180 : case E_Handler:
1856 3180 : put_z(p, "handler ");
1857 3180 : put_span(p, E_Handler_effect(n));
1858 3180 : p_seq_block(p, n, E_Handler_clauses(n), c.ind + 1, c.flat, true);
1859 3180 : return;
1860 2745 : case E_Assign:
1861 2745 : p_node(p, E_Assign_target(n), lead_sub(c, EP_CHAIN));
1862 2745 : put_z(p, " :=");
1863 2745 : p_body(p, E_Assign_value(n), c.ind, c.flat, c.rest);
1864 2745 : return;
1865 3276 : case E_Record: {
1866 3276 : WokSeq fields = E_Record_fields(n);
1867 3276 : const WokNode *spread = E_Record_spread(n);
1868 3276 : p_node(p, E_Record_path(n), lead_sub(c, EP_ATOM));
1869 3276 : put_c(p, ' ');
1870 3276 : if (spread == nullptr && fields.n == 0) {
1871 192 : put_z(p, "{}");
1872 192 : return;
1873 : }
1874 3084 : Group g = group_open(p, "{ ", c.brk);
1875 3084 : bool first = true;
1876 3084 : if (spread != nullptr) {
1877 2208 : put_z(p, ".. ");
1878 2208 : p_node(p, spread, group_item(c, EP_EXPR, g));
1879 2208 : first = false;
1880 : }
1881 8114 : for (u32 i = 0; i < fields.n; i++) {
1882 5030 : if (!first) group_sep(p, g);
1883 5030 : first = false;
1884 5030 : p_node(p, fields.items[i], group_item(c, PREC_FIXED, g));
1885 : }
1886 3084 : group_close(p, g, " }");
1887 3084 : return;
1888 : }
1889 0 : case E_Block:
1890 : // Reached only when a block sits somewhere p_body did not place it.
1891 0 : p_seq_block(p, n, E_Block_stmts(n), c.ind + 1, c.flat, false);
1892 0 : return;
1893 0 : case E_Error:
1894 0 : unprintable(p, "a damaged expression");
1895 0 : put_span(p, E_Error_text(n));
1896 0 : return;
1897 :
1898 : // -------------------------------------------------------- statements
1899 400 : case S_Let:
1900 400 : put_z(p, "let ");
1901 400 : p_node(p, S_Let_bind(n), sub(c, PREC_FIXED));
1902 400 : return;
1903 408 : case S_Handle:
1904 408 : put_z(p, "handle ");
1905 408 : put_span(p, S_Handle_label(n));
1906 408 : put_z(p, " = ");
1907 408 : p_node(p, S_Handle_handler(n), sub(c, EP_EXPR));
1908 408 : return;
1909 88 : case S_Use:
1910 88 : put_z(p, "use ");
1911 88 : p_list(p, S_Use_binds(n), sub(c, PREC_FIXED), ", ");
1912 88 : return;
1913 136 : case S_Discard:
1914 136 : put_z(p, "_ =");
1915 136 : p_body(p, S_Discard_body(n), c.ind, c.flat, c.rest);
1916 136 : return;
1917 :
1918 : // ------------------------------------------------ expression helpers
1919 2060 : case H_Bind:
1920 2060 : p_node(p, H_Bind_lhs(n), lead_sub(c, PP_PAT));
1921 2060 : put_z(p, " =");
1922 2060 : p_body(p, H_Bind_body(n), c.ind, c.flat, c.rest);
1923 2060 : return;
1924 3226 : case H_UseBind:
1925 3226 : put_span(p, H_UseBind_from(n));
1926 3226 : put_z(p, " as ");
1927 3226 : put_span(p, H_UseBind_to(n));
1928 3226 : return;
1929 3055 : case H_Alt: {
1930 3055 : WokSeq wheres = H_Alt_wheres(n);
1931 3055 : const WokNode *body = H_Alt_body(n);
1932 3055 : usize start = p->cols;
1933 3055 : p_arrow_head(p, n, head_ctx(p, n, c, body));
1934 3055 : if (c.pad > p->cols - start) put_spaces(p, c.pad - (p->cols - start));
1935 3055 : put_z(p, " ->");
1936 3055 : p_body(p, body, body_indent(body, wheres, c.ind), c.flat, 0);
1937 3055 : p_wheres(p, wheres, c.ind, c.flat);
1938 3055 : return;
1939 : }
1940 4065 : case H_Clause: {
1941 4065 : if (H_Clause_kind(n) == WOK_CLAUSE_VAR) {
1942 : // A baton is headed by `=`, so it joins no alignment group.
1943 864 : put_z(p, "var ");
1944 864 : put_span(p, H_Clause_name(n));
1945 864 : put_z(p, " =");
1946 864 : p_body(p, H_Clause_body(n), c.ind, c.flat, c.rest);
1947 864 : return;
1948 : }
1949 3201 : usize start = p->cols;
1950 3201 : p_arrow_head(p, n, head_ctx(p, n, c, H_Clause_body(n)));
1951 3201 : if (c.pad > p->cols - start) put_spaces(p, c.pad - (p->cols - start));
1952 3201 : put_z(p, " ->");
1953 3201 : p_body(p, H_Clause_body(n), c.ind, c.flat, c.rest);
1954 3201 : return;
1955 : }
1956 5030 : case H_Field: {
1957 5030 : WokSpan name = H_Field_name(n);
1958 5030 : const WokNode *value = H_Field_value(n);
1959 5030 : if (value->tag == E_Var && span_eq(p, E_Var_name(value), name)) {
1960 76 : put_span(p, name);
1961 76 : return;
1962 : }
1963 4954 : put_span(p, name);
1964 4954 : put_z(p, " = ");
1965 4954 : p_node(p, value, bracket(c, EP_EXPR));
1966 4954 : return;
1967 : }
1968 :
1969 : // -------------------------------------------------------------- file
1970 17963 : case W_File: {
1971 17963 : WokSeq decls = W_File_decls(n);
1972 60639 : for (u32 i = 0; i < decls.n; i++) {
1973 : // One blank line between top-level items, except where two of them
1974 : // are really one unit (a signature and its equation, a run of imports).
1975 : // Decision 4 owns the blank line here, so a declaration's recorded
1976 : // paragraph break is not consulted -- honouring both would print two.
1977 42676 : if (i != 0 && !decls_are_one_unit(p, decls.items[i - 1], decls.items[i]))
1978 22038 : put_c(p, '\n');
1979 42676 : const WokTriviaEntry *e = trivia_of(p, decls.items[i]);
1980 42676 : if (e != nullptr)
1981 4993 : for (u32 k = 0; k < e->lead_n; k++) {
1982 3908 : put_comment(p, e->lead_first + k);
1983 3908 : put_c(p, '\n');
1984 : }
1985 42676 : p_node(p, decls.items[i],
1986 42676 : (Ctx){.prec = 0, .ind = 0, .flat = false, .lead = true,
1987 : .brk = false, .pad = 0});
1988 42676 : p_trail(p, decls.items[i], e);
1989 42676 : put_c(p, '\n');
1990 : }
1991 : // Rule 3 at the top level: a comment with no declaration after it.
1992 17963 : const WokTriviaEntry *fe = trivia_of(p, n);
1993 17963 : if (fe != nullptr)
1994 6056 : for (u32 k = 0; k < fe->trail_n; k++) {
1995 441 : put_comment(p, fe->trail_first + k);
1996 441 : put_c(p, '\n');
1997 : }
1998 17963 : return;
1999 : }
2000 :
2001 : case WOK_TAG_COUNT:
2002 : break;
2003 : }
2004 0 : WOK_UNREACHABLE();
2005 : }
2006 :
2007 3477 : static void p_if(Pr *p, const WokNode *n, Ctx c) {
2008 3477 : const WokNode *cond = E_If_cond(n);
2009 3477 : const WokNode *then_ = E_If_then_(n);
2010 3477 : const WokNode *else_ = E_If_else_(n);
2011 3477 : bool split = !c.flat && (opens_block(cond) || opens_block(then_));
2012 :
2013 3477 : put_z(p, "if ");
2014 : {
2015 126 : Ctx sc = split ? sub(c, EP_EXPR)
2016 3477 : : sub_rest(c, EP_EXPR, 5 + body_cols(p, then_, c));
2017 3477 : if (split) sc.ind = c.ind + 1;
2018 3477 : p_node(p, cond, sc);
2019 : }
2020 3477 : if (!split) {
2021 3351 : put_z(p, " then");
2022 3351 : p_body(p, then_, c.ind, c.flat, 5 + body_cols(p, else_, c) + c.rest);
2023 3351 : put_z(p, " else");
2024 3351 : p_body(p, else_, c.ind, c.flat, c.rest);
2025 3351 : return;
2026 : }
2027 126 : nl(p, c.ind + 1, LN_LEAD);
2028 126 : put_z(p, "then");
2029 126 : p_body(p, then_, c.ind + 1, c.flat, 0);
2030 126 : nl(p, c.ind + 1, LN_LEAD);
2031 126 : put_z(p, "else");
2032 126 : p_body(p, else_, c.ind + 1, c.flat, c.rest);
2033 : }
2034 :
2035 1088440 : static void p_node(Pr *p, const WokNode *n, Ctx c) {
2036 1088440 : if (p->depth >= WOK_PRINT_MAX_DEPTH) {
2037 0 : unprintable(p, "a tree nested past the printer's depth cap");
2038 0 : put_z(p, "!DEPTH");
2039 0 : return;
2040 : }
2041 : // Measured BEFORE the depth is charged, so the measuring pass re-enters at
2042 : // the same depth this one is at and cannot exhaust the cap by halving it.
2043 1088440 : bool brk = decide_break(p, n, c);
2044 1088440 : p->depth++;
2045 1088440 : p->tstack[p->tn++] = n->tag;
2046 :
2047 1088440 : bool paren = node_prec(n) < c.prec || (c.lead && needs_lead_paren(n));
2048 33986 : if (paren) {
2049 33986 : put_c(p, '(');
2050 33986 : c.prec = 0;
2051 33986 : c.flat = true; // a bracket group suspends layout: rule L2
2052 33986 : c.lead = false;
2053 : }
2054 1088440 : c.brk = brk;
2055 1088440 : p_node_inner(p, n, c);
2056 1088440 : if (paren) put_c(p, ')');
2057 :
2058 1088440 : p->tn--;
2059 1088440 : p->depth--;
2060 : }
2061 :
2062 : // ---------------------------------------------------------------- public
2063 :
2064 : static const Ctx TOP = {.prec = 0, .ind = 0, .flat = false, .lead = true,
2065 : .brk = false, .pad = 0};
2066 :
2067 0 : void wok_print(const WokNode *file, const char *src, FILE *out) {
2068 0 : Pr p = {.to_file = true, .file = out, .src = src, .line = 1,
2069 0 : .tv = wok_trivia_of(file)};
2070 0 : p_node(&p, file, TOP);
2071 : // A W_File closes every declaration with a newline of its own.
2072 0 : if (file->tag != W_File) put_c(&p, '\n');
2073 0 : }
2074 :
2075 281 : char *wok_print_string(const WokNode *file, const char *src, WokArena *arena) {
2076 281 : return wok_print_string_named(file, src, arena, nullptr);
2077 : }
2078 :
2079 17963 : char *wok_print_string_named(const WokNode *file, const char *src,
2080 : WokArena *arena, const char *path) {
2081 35926 : Pr p = {.arena = arena, .src = src, .path = path, .line = 1,
2082 17963 : .tv = wok_trivia_of(file)};
2083 17963 : p_node(&p, file, TOP);
2084 17963 : if (file->tag != W_File) put_c(&p, '\n');
2085 17963 : sink_reserve(&p, 1);
2086 17963 : p.buf[p.len] = '\0';
2087 17963 : return p.buf;
2088 : }
|