Line data Source code
1 : // wok_trivia -- see wok_trivia.h for what this is and why comments are not
2 : // slots.
3 : //
4 : // THE SHAPE OF THE PASS
5 : //
6 : // 1. index the source's lines, because every attachment rule is stated in
7 : // lines and columns and the token stream has already thrown the blank
8 : // ones away;
9 : // 2. walk the tree once, in PRINT order, recording every block ITEM and
10 : // every block SCOPE with an Euler timestamp;
11 : // 3. give each comment a target, in source order;
12 : // 4. lay the comments out in one pooled array so each item's leading and
13 : // trailing runs are contiguous.
14 : //
15 : // STEP 2 IS THE ONE THAT PAYS FOR THE PROPERTY. The Euler tour gives every
16 : // possible target a POSITION IN THE PRINTED TEXT, and step 3 refuses to let
17 : // that position go backwards. That is what makes
18 : //
19 : // comments(Format(t)) == comments(t) -- same texts, SAME ORDER
20 : //
21 : // true by construction rather than by inspection of cases: a rule that would
22 : // move a comment behind one that preceded it in the source is overridden and
23 : // the comment joins its predecessor instead.
24 :
25 : // The prelude comes FIRST: it carries the POSIX feature-test macros, which
26 : // have no effect once a system header has been read. wok_base.h hard-errors
27 : // if it is reached too late.
28 : #include "wok_base.h"
29 :
30 : #include "wok_trivia.h"
31 :
32 : #include <string.h>
33 :
34 : // The tree is bounded by the parser's own depth cap; this is only a guard so
35 : // a hostile or damaged tree cannot walk off the C stack.
36 : enum { TRIVIA_MAX_DEPTH = 512 };
37 :
38 : #define NO_SCOPE UINT32_MAX
39 :
40 : // ------------------------------------------------------------------ lines
41 :
42 : typedef struct {
43 : const char *src;
44 : usize n;
45 : u32 *start; // start[i] is the offset of line i, 0-based
46 : u32 nlines;
47 : } Lines;
48 :
49 20179 : static void lines_build(Lines *l, const char *src, usize n, WokArena *a) {
50 20179 : u32 count = 1;
51 5891823 : for (usize i = 0; i < n; i++)
52 5871644 : if (src[i] == '\n') count++;
53 20179 : l->src = src;
54 20179 : l->n = n;
55 20179 : l->nlines = count;
56 20179 : l->start = WOK_NEW_N(a, u32, count);
57 20179 : u32 k = 0;
58 20179 : l->start[k++] = 0;
59 5891823 : for (usize i = 0; i < n; i++)
60 5871644 : if (src[i] == '\n' && k < count) l->start[k++] = (u32)i + 1;
61 20179 : }
62 :
63 524120 : static u32 line_of(const Lines *l, u32 off) {
64 524120 : u32 lo = 0, hi = l->nlines - 1;
65 3252958 : while (lo < hi) {
66 2204718 : u32 mid = lo + (hi - lo + 1) / 2;
67 2204718 : if (l->start[mid] <= off)
68 : lo = mid;
69 : else
70 1077280 : hi = mid - 1;
71 : }
72 524120 : return lo;
73 : }
74 :
75 216760 : static u32 col_of(const Lines *l, u32 off) {
76 216760 : return off - l->start[line_of(l, off)] + 1;
77 : }
78 :
79 73914 : static bool is_blank_byte(char c) { return c == ' ' || c == '\t' || c == '\r'; }
80 :
81 : // A line with nothing but whitespace on it -- the author's paragraph break.
82 17586 : static bool line_is_blank(const Lines *l, u32 line) {
83 17586 : u32 from = l->start[line];
84 17586 : u32 to = line + 1 < l->nlines ? l->start[line + 1] : (u32)l->n;
85 73465 : for (u32 i = from; i < to; i++) {
86 73465 : char c = l->src[i];
87 73465 : if (c == '\n') break;
88 73417 : if (!is_blank_byte(c)) return false;
89 : }
90 : return true;
91 : }
92 :
93 : // Rule 1's test: is this the first non-whitespace on its line?
94 49152 : static bool starts_its_line(const Lines *l, u32 off) {
95 49436 : for (u32 i = l->start[line_of(l, off)]; i < off; i++)
96 497 : if (!is_blank_byte(l->src[i])) return false;
97 : return true;
98 : }
99 :
100 : // ------------------------------------------------------- the layout table
101 : //
102 : // ONE switch decides two things the walk needs, so a new node cannot be added
103 : // without answering both. -Wswitch with no `default:` is what enforces that.
104 : //
105 : // block_slot -- the SEQ that wok_print writes with p_seq_block, whose
106 : // members are therefore block ITEMS. Every other SEQ (tuple
107 : // elements, constructor arguments) holds no items.
108 : // open_mask -- the slots wok_print enters WITHOUT brackets, so an indented
109 : // block can still exist under them. Inside brackets layout is
110 : // suspended (rule L2) and no comment can be written on a line
111 : // of its own, so items reached through any other slot are
112 : // marked FLAT and never receive trivia. Forgetting a slot
113 : // here is safe -- it only pushes a comment outward to an
114 : // enclosing item; inventing one is not.
115 :
116 : typedef struct {
117 : i32 block_slot;
118 : u32 open_mask;
119 : } TagLayout;
120 :
121 : #define SL(x) (1u << (x))
122 :
123 597908 : static TagLayout tag_layout(WokTag t) {
124 597908 : switch (t) {
125 515127 : case N_Name:
126 : case N_ModPath:
127 : case D_Module:
128 : case D_Import:
129 : case D_Type:
130 : case D_Alias:
131 : case D_ExternType:
132 : case D_Sig:
133 : case D_Fixity:
134 : case H_FixRel:
135 : case D_Error:
136 : case H_TyParam:
137 : case H_ConDef:
138 : case H_FieldType:
139 : case H_OpSig:
140 : case H_ForeignMember:
141 : case H_SigName:
142 : case L_Prefix:
143 : case L_Infix:
144 : case T_Var:
145 : case T_Con:
146 : case T_App:
147 : case T_Fun:
148 : case T_Qual:
149 : case T_With:
150 : case T_List:
151 : case T_Tuple:
152 : case T_Unit:
153 : case T_RowArg:
154 : case T_Transfer:
155 : case H_RowEntry:
156 : case P_Var:
157 : case P_Wild:
158 : case P_Int:
159 : case P_Str:
160 : case P_Char:
161 : case P_Con:
162 : case P_Cons:
163 : case P_Tuple:
164 : case P_List:
165 : case P_Unit:
166 : case P_As:
167 : case P_Record:
168 : case H_FieldPat:
169 : case E_Var:
170 : case E_Con:
171 : case E_Int:
172 : case E_Str:
173 : case E_Char:
174 : case E_Unit:
175 : case E_OpRef:
176 : case E_App:
177 : case E_Chain:
178 : case E_Dot:
179 : case E_Neg:
180 : case E_List:
181 : case E_Tuple:
182 : case E_Record:
183 : case E_Error:
184 : case S_Use:
185 : case H_ChainOp:
186 : case H_UseBind:
187 : case H_Field:
188 : case WOK_TAG_COUNT:
189 515127 : return (TagLayout){-1, 0};
190 :
191 4500 : case D_Effect:
192 4500 : return (TagLayout){D_Effect__ops, SL(D_Effect__ops)};
193 909 : case D_Class:
194 909 : return (TagLayout){D_Class__body, SL(D_Class__body)};
195 1004 : case D_Instance:
196 1004 : return (TagLayout){D_Instance__body, SL(D_Instance__body)};
197 883 : case D_Foreign:
198 883 : return (TagLayout){D_Foreign__members, SL(D_Foreign__members)};
199 20058 : case D_Equation:
200 20058 : return (TagLayout){D_Equation__wheres,
201 : SL(D_Equation__body) | SL(D_Equation__wheres)};
202 1206 : case E_Lambda:
203 1206 : return (TagLayout){-1, SL(E_Lambda__body)};
204 548 : case E_LetIn:
205 548 : return (TagLayout){-1, SL(E_LetIn__bind) | SL(E_LetIn__body)};
206 1967 : case E_HandleIn:
207 1967 : return (TagLayout){-1, SL(E_HandleIn__handler) | SL(E_HandleIn__body)};
208 592 : case E_UseIn:
209 592 : return (TagLayout){-1, SL(E_UseIn__body)};
210 563 : case E_If:
211 563 : return (TagLayout){
212 : -1, SL(E_If__cond) | SL(E_If__then_) | SL(E_If__else_)};
213 3076 : case E_Case:
214 3076 : return (TagLayout){E_Case__alts, SL(E_Case__scrut) | SL(E_Case__alts)};
215 3415 : case E_Handler:
216 3415 : return (TagLayout){E_Handler__clauses, SL(E_Handler__clauses)};
217 1312 : case E_Assign:
218 1312 : return (TagLayout){-1, SL(E_Assign__value)};
219 3934 : case E_Block:
220 3934 : return (TagLayout){E_Block__stmts, SL(E_Block__stmts)};
221 2521 : case S_Let:
222 2521 : return (TagLayout){-1, SL(S_Let__bind)};
223 1160 : case S_Handle:
224 1160 : return (TagLayout){-1, SL(S_Handle__handler)};
225 216 : case S_Discard:
226 216 : return (TagLayout){-1, SL(S_Discard__body)};
227 3056 : case H_Bind:
228 3056 : return (TagLayout){-1, SL(H_Bind__body)};
229 5269 : case H_Alt:
230 5269 : return (TagLayout){H_Alt__wheres, SL(H_Alt__body) | SL(H_Alt__wheres)};
231 6413 : case H_Clause:
232 6413 : return (TagLayout){-1, SL(H_Clause__body)};
233 20179 : case W_File:
234 20179 : return (TagLayout){W_File__decls, SL(W_File__decls)};
235 : }
236 0 : WOK_UNREACHABLE();
237 : }
238 :
239 : // A scope whose owner node can hold the comments that CLOSE it (rule 3) in
240 : // its own trailing range. A `where` block is the exception: it has no node of
241 : // its own -- its owner is the equation or alternative it hangs off, and that
242 : // node's trailing range already belongs to the item itself. Its closing
243 : // comments go to an enclosing block instead.
244 63227 : static bool scope_is_closable(WokTag t) {
245 63227 : return t != D_Equation && t != H_Alt;
246 : }
247 :
248 : // The mirror of the above: an item may take a TRAILING comment only if its
249 : // own trailing range is not already spoken for by a block it closes. An
250 : // equation or alternative is safe even though it owns a `where`, because that
251 : // block's closing comments were sent elsewhere.
252 1352 : static bool item_takes_trail(WokTag t) {
253 1352 : return tag_layout(t).block_slot < 0 || !scope_is_closable(t);
254 : }
255 :
256 1164 : bool wok_trivia_trail_closes_block(const WokNode *n) {
257 1164 : return !item_takes_trail((WokTag)n->tag);
258 : }
259 :
260 : // ------------------------------------------------------- items and scopes
261 :
262 : typedef struct {
263 : WokNode *node;
264 : u32 scope;
265 : u32 enter, exit; // Euler timestamps: the item's place in the output
266 : u32 line, end_line, col;
267 : bool flat;
268 : bool first_in_scope;
269 : } Item;
270 :
271 : typedef struct {
272 : WokNode *owner;
273 : u32 parent;
274 : u32 item_col;
275 : u32 close; // Euler timestamp of the point after the last item
276 : bool flat;
277 : bool closable;
278 : } Scope;
279 :
280 : typedef struct {
281 : WokArena *a;
282 : Lines lines;
283 : Item *item;
284 : u32 nitem, capitem;
285 : Scope *scope;
286 : u32 nscope, capscope;
287 : u32 clock;
288 : } Builder;
289 :
290 104506 : static u32 push_item(Builder *b, WokNode *n, u32 scope, bool flat,
291 : bool first) {
292 104506 : if (b->nitem == b->capitem) {
293 15928 : u32 next = b->capitem ? b->capitem * 2 : 32;
294 15928 : Item *grown = WOK_NEW_N(b->a, Item, next);
295 15928 : if (b->nitem) memcpy(grown, b->item, (usize)b->nitem * sizeof *grown);
296 15928 : b->item = grown;
297 15928 : b->capitem = next;
298 : }
299 104506 : u32 last = n->len != 0 ? n->off + n->len - 1 : n->off;
300 104506 : if (last >= b->lines.n) last = b->lines.n != 0 ? (u32)b->lines.n - 1 : 0;
301 209012 : b->item[b->nitem] = (Item){.node = n,
302 : .scope = scope,
303 : .enter = 0,
304 : .exit = 0,
305 104506 : .line = line_of(&b->lines, n->off),
306 104506 : .end_line = line_of(&b->lines, last),
307 104506 : .col = col_of(&b->lines, n->off),
308 : .flat = flat,
309 : .first_in_scope = first};
310 104506 : return b->nitem++;
311 : }
312 :
313 63102 : static u32 push_scope(Builder *b, WokNode *owner, u32 parent,
314 : bool flat, u32 item_col) {
315 63102 : if (b->nscope == b->capscope) {
316 20659 : u32 next = b->capscope ? b->capscope * 2 : 16;
317 20659 : Scope *grown = WOK_NEW_N(b->a, Scope, next);
318 20659 : if (b->nscope) memcpy(grown, b->scope, (usize)b->nscope * sizeof *grown);
319 20659 : b->scope = grown;
320 20659 : b->capscope = next;
321 : }
322 63102 : b->scope[b->nscope] = (Scope){.owner = owner,
323 : .parent = parent,
324 : .item_col = item_col,
325 : .close = 0,
326 : .flat = flat,
327 : .closable =
328 63102 : scope_is_closable((WokTag)owner->tag)};
329 63102 : return b->nscope++;
330 : }
331 :
332 : static void walk_node(Builder *b, WokNode *n, u32 scope, bool flat,
333 : u32 depth);
334 :
335 63102 : static void walk_scope(Builder *b, WokNode *owner, WokSeq seq, u32 parent,
336 : bool flat, u32 depth) {
337 35923 : u32 col = seq.n != 0 ? col_of(&b->lines, seq.items[0]->off)
338 63102 : : col_of(&b->lines, owner->off);
339 63102 : u32 s = push_scope(b, owner, parent, flat, col);
340 167608 : for (u32 i = 0; i < seq.n; i++) {
341 104506 : u32 idx = push_item(b, seq.items[i], s, flat, i == 0);
342 104506 : b->item[idx].enter = b->clock++;
343 104506 : walk_node(b, seq.items[i], s, flat, depth + 1);
344 104506 : b->item[idx].exit = b->clock++;
345 : }
346 63102 : b->scope[s].close = b->clock++;
347 63102 : }
348 :
349 596556 : static void walk_node(Builder *b, WokNode *n, u32 scope, bool flat,
350 : u32 depth) {
351 596556 : if (depth >= TRIVIA_MAX_DEPTH) return;
352 596556 : TagLayout layout = tag_layout((WokTag)n->tag);
353 596556 : const WokNodeDesc *desc = &wok_node_desc[n->tag];
354 1634369 : for (u16 i = 0; i < desc->nfields; i++) {
355 1037813 : bool open = ((layout.open_mask >> i) & 1u) != 0;
356 1037813 : bool child_flat = flat || !open;
357 1037813 : switch (desc->fields[i].cls) {
358 317981 : case WFC_NODE:
359 : case WFC_OPT: {
360 317981 : WokNode *child = n->slot[i].node;
361 317981 : if (child != nullptr) walk_node(b, child, scope, child_flat, depth + 1);
362 : break;
363 : }
364 233201 : case WFC_SEQ: {
365 233201 : WokSeq seq = wok_seq_unpack(n->slot[i].seq);
366 233201 : if (layout.block_slot == (i32)i) {
367 63102 : walk_scope(b, n, seq, scope, child_flat, depth);
368 : } else {
369 330747 : for (u32 k = 0; k < seq.n; k++)
370 160648 : walk_node(b, seq.items[k], scope, child_flat, depth + 1);
371 : }
372 233201 : break;
373 : }
374 : case WFC_NAME:
375 : case WFC_TEXT:
376 : case WFC_INT:
377 : case WFC_FLAG:
378 : case WOK_FIELD_CLASS_COUNT:
379 : break;
380 : }
381 : }
382 : }
383 :
384 : // ------------------------------------------------------------- attachment
385 :
386 : typedef enum { T_LEAD, T_TRAIL, T_CLOSE } TargetKind;
387 :
388 : typedef struct {
389 : TargetKind kind;
390 : u32 idx; // an item for T_LEAD/T_TRAIL, a scope for T_CLOSE
391 : u32 rank; // where this lands in the printed text
392 : } Target;
393 :
394 49152 : static u32 target_rank(const Builder *b, TargetKind k, u32 idx) {
395 49152 : switch (k) {
396 35620 : case T_LEAD:
397 35620 : return b->item[idx].enter;
398 188 : case T_TRAIL:
399 188 : return b->item[idx].exit;
400 13344 : case T_CLOSE:
401 13344 : return b->scope[idx].close;
402 : }
403 0 : WOK_UNREACHABLE();
404 : }
405 :
406 49152 : static WokNode *target_node(const Builder *b, const Target *t) {
407 49152 : switch (t->kind) {
408 35810 : case T_LEAD:
409 : case T_TRAIL:
410 35810 : return b->item[t->idx].node;
411 13342 : case T_CLOSE:
412 13342 : return b->scope[t->idx].owner;
413 : }
414 0 : WOK_UNREACHABLE();
415 : }
416 :
417 : // Rule 2. The item this line's trailing comment belongs to is the last one
418 : // that both begins and ENDS on the line, before the comment. An item that
419 : // runs on -- because it opens a block whose own items follow -- is refused,
420 : // because its trailing comment would be printed after that block and so after
421 : // every comment written inside it.
422 : // `before` is the count of items that begin left of the comment, which the
423 : // caller advances monotonically; scanning back from it visits only the items
424 : // on this one line.
425 213 : static bool find_line_item(const Builder *b, u32 before, u32 line,
426 : u32 coff, u32 *out) {
427 221 : for (u32 i = before; i-- > 0;) {
428 213 : const Item *it = &b->item[i];
429 213 : if (it->line < line) break;
430 196 : if (it->flat || it->end_line != line) continue;
431 188 : if (!item_takes_trail((WokTag)it->node->tag)) continue;
432 188 : if (it->node->off + it->node->len > coff) continue;
433 188 : *out = i; // scanning backward, the first hit is the LAST on the line
434 188 : return true;
435 : }
436 : return false;
437 : }
438 :
439 : // Rule 3. The block a comment that follows no item belongs to is the
440 : // innermost enclosing one whose ITEMS sit at or left of the comment. Column
441 : // is the only evidence there is: the comment is inside whichever block its
442 : // author indented it to.
443 13361 : static u32 choose_close_scope(const Builder *b, u32 before,
444 : u32 ccol) {
445 13361 : u32 s = before != 0 ? b->item[before - 1].scope : NO_SCOPE;
446 17129 : while (s != NO_SCOPE) {
447 8177 : const Scope *sc = &b->scope[s];
448 8177 : if (sc->closable && !sc->flat && sc->item_col <= ccol) return s;
449 3768 : s = sc->parent;
450 : }
451 : return 0; // scope 0 is the file's own: closable, never flat, column 1
452 : }
453 :
454 : // Is `outer` `inner` itself, or one of the blocks it nests inside?
455 37 : static bool scope_encloses(const Builder *b, u32 outer, u32 inner) {
456 61 : while (inner != NO_SCOPE) {
457 41 : if (inner == outer) return true;
458 24 : inner = b->scope[inner].parent;
459 : }
460 : return false;
461 : }
462 :
463 : // ------------------------------------------------------- the entry table
464 : //
465 : // One entry per node that carries anything. Open addressing on the node
466 : // pointer, sized so the table can never fill.
467 :
468 : typedef struct {
469 : WokNode *key;
470 : u32 entry;
471 : } MapSlot;
472 :
473 : typedef struct {
474 : MapSlot *slot;
475 : u32 mask;
476 : WokTriviaEntry *entry;
477 : u32 nentry, capentry;
478 : } EntryMap;
479 :
480 69379 : static u32 ptr_hash(const void *p) {
481 69379 : u64 x = (u64)(uptr)p;
482 69379 : x ^= x >> 33;
483 69379 : x *= UINT64_C(0xFF51AFD7ED558CCD);
484 69379 : x ^= x >> 29;
485 69379 : return (u32)x;
486 : }
487 :
488 69379 : static u32 entry_for(EntryMap *m, WokNode *n) {
489 69379 : u32 i = ptr_hash(n) & m->mask;
490 70460 : while (m->slot[i].key != nullptr) {
491 37965 : if (m->slot[i].key == n) return m->slot[i].entry;
492 1081 : i = (i + 1) & m->mask;
493 : }
494 32495 : assert(m->nentry < m->capentry); // sized for every possible target
495 32495 : u32 e = m->nentry++;
496 32495 : m->entry[e] = (WokTriviaEntry){0};
497 32495 : m->slot[i] = (MapSlot){.key = n, .entry = e};
498 32495 : n->trivia = e;
499 32495 : return e;
500 : }
501 :
502 : // --------------------------------------------------------------- bindings
503 : //
504 : // wok_print is handed a tree and the buffer its spans point into, and must
505 : // find this table from those alone. The binding is keyed on the file node AND
506 : // gated on `trivia != 0`, which only this pass ever sets: a node the arena
507 : // later hands out at the same address is freshly zeroed, so a stale binding
508 : // can never be mistaken for a live one. The tool is one-shot and
509 : // single-threaded by design (design section 1), exactly as the coverage
510 : // instrument in wok_ast.c already assumes.
511 :
512 : enum { TRIVIA_BINDINGS = 8 };
513 :
514 : static struct {
515 : const WokNode *file;
516 : const WokTrivia *table;
517 : } bindings[TRIVIA_BINDINGS];
518 : static u32 binding_next;
519 :
520 20179 : static void bind_table(const WokNode *file, const WokTrivia *table) {
521 179504 : for (u32 i = 0; i < TRIVIA_BINDINGS; i++)
522 159788 : if (bindings[i].file == file) {
523 463 : bindings[i].table = table;
524 463 : return;
525 : }
526 19716 : u32 i = binding_next++ % TRIVIA_BINDINGS;
527 19716 : bindings[i].file = file;
528 19716 : bindings[i].table = table;
529 : }
530 :
531 17963 : const WokTrivia *wok_trivia_of(const WokNode *file) {
532 17963 : if (file == nullptr || file->trivia == 0) return nullptr;
533 24077 : for (u32 i = 0; i < TRIVIA_BINDINGS; i++)
534 24077 : if (bindings[i].file == file) return bindings[i].table;
535 : return nullptr;
536 : }
537 :
538 : // ------------------------------------------------------------------ attach
539 :
540 20179 : const WokTrivia *wok_trivia_attach(WokNode *file, const char *src,
541 : usize src_len, const WokComment *comments,
542 : usize ncomments, WokArena *arena) {
543 20179 : WokTrivia *table = WOK_NEW(arena, WokTrivia);
544 20179 : *table = (WokTrivia){
545 : .entry = nullptr, .nentries = 0, .comment = nullptr, .ncomments = 0};
546 :
547 20179 : Builder b = {.a = arena,
548 : .item = nullptr,
549 : .nitem = 0,
550 : .capitem = 0,
551 : .scope = nullptr,
552 : .nscope = 0,
553 : .capscope = 0,
554 : .clock = 0};
555 20179 : lines_build(&b.lines, src, src_len, arena);
556 : // Scope 0 is the file's own block, and rule 3's last resort. A tree that is
557 : // not a whole file has no such block, so it takes no trivia at all.
558 20179 : if (file->tag == W_File) walk_node(&b, file, NO_SCOPE, false, 0);
559 20179 : if (b.nscope == 0) ncomments = 0;
560 :
561 : // Every node that can be a target, plus one so the file node always gets a
562 : // non-zero index -- that is what makes the binding check above sound.
563 20179 : u32 cap = (u32)ncomments + b.nitem + 2;
564 20179 : u32 size = 16;
565 31188 : while (size < cap * 2) size *= 2;
566 20179 : EntryMap map = {.slot = WOK_NEW_N(arena, MapSlot, size),
567 20179 : .mask = size - 1,
568 20179 : .entry = WOK_NEW_N(arena, WokTriviaEntry, cap),
569 : .nentry = 0,
570 : .capentry = cap};
571 20179 : memset(map.slot, 0, (usize)size * sizeof *map.slot);
572 20179 : map.entry[map.nentry++] = (WokTriviaEntry){0}; // the reserved sentinel
573 20179 : (void)entry_for(&map, file);
574 :
575 20179 : Target *target = WOK_NEW_N(arena, Target, ncomments + 1);
576 20179 : bool have_prev = false;
577 20179 : Target prev = {.kind = T_CLOSE, .idx = 0, .rank = 0};
578 :
579 : // Comments arrive in source order, so both cursors only ever move forward
580 : // and the whole pass is linear in items plus comments.
581 20179 : u32 before = 0; // items that begin left of the comment
582 20179 : u32 next = 0; // the first non-flat item that begins after it
583 :
584 69331 : for (usize ci = 0; ci < ncomments; ci++) {
585 49152 : const WokComment *c = &comments[ci];
586 49152 : u32 coff = c->off;
587 49152 : u32 cend = c->off + c->len;
588 49152 : u32 cline = line_of(&b.lines, coff);
589 49152 : u32 ccol = col_of(&b.lines, coff);
590 105006 : while (before < b.nitem && b.item[before].node->off < coff) before++;
591 105006 : while (next < b.nitem &&
592 91578 : (b.item[next].flat || b.item[next].node->off < cend))
593 55854 : next++;
594 49152 : bool have_next = next < b.nitem;
595 :
596 49152 : Target t = {.kind = T_CLOSE, .idx = 0, .rank = 0};
597 49152 : u32 idx = 0;
598 49152 : bool decided = false;
599 :
600 49365 : if (!starts_its_line(&b.lines, coff) &&
601 213 : find_line_item(&b, before, cline, coff, &idx)) {
602 188 : t.kind = T_TRAIL;
603 188 : t.idx = idx;
604 188 : decided = true;
605 : }
606 49152 : if (!decided && have_next && ccol <= b.item[next].col) {
607 : // Deeper than the item it precedes means the author left it inside the
608 : // block that just ended, not attached to what comes next.
609 : t.kind = T_LEAD;
610 : t.idx = next;
611 : decided = true;
612 : }
613 13549 : if (!decided) {
614 13361 : u32 s = choose_close_scope(&b, before, ccol);
615 : // A block only CLOSES if what follows is outside it. Between two items
616 : // of the same block, an over-indented comment is still that block's --
617 : // it leads the item it was written above.
618 13361 : if (have_next && scope_encloses(&b, s, b.item[next].scope)) {
619 : t.kind = T_LEAD;
620 : t.idx = next;
621 : } else {
622 : t.kind = T_CLOSE;
623 : t.idx = s;
624 : }
625 : }
626 49152 : t.rank = target_rank(&b, t.kind, t.idx);
627 :
628 : // The order guarantee. A rule that would print this comment before one
629 : // that preceded it in the source loses; the comment joins its predecessor.
630 49152 : if (have_prev && t.rank < prev.rank) t = prev;
631 49152 : target[ci] = t;
632 49152 : prev = t;
633 49152 : have_prev = true;
634 : }
635 :
636 : // blank_before, read off the source: the paragraph break above the item,
637 : // counting a leading comment block as part of the item.
638 20179 : u32 *first_lead = WOK_NEW_N(arena, u32, b.nitem + 1);
639 144864 : for (u32 i = 0; i <= b.nitem; i++) first_lead[i] = UINT32_MAX;
640 69331 : for (usize ci = ncomments; ci-- > 0;)
641 49152 : if (target[ci].kind == T_LEAD) first_lead[target[ci].idx] = (u32)ci;
642 :
643 124685 : for (u32 i = 0; i < b.nitem; i++) {
644 104506 : const Item *it = &b.item[i];
645 : // Only a break BETWEEN two items of one block is printable: the top level
646 : // already writes its own blank lines, and nothing precedes a block's
647 : // first item.
648 104506 : if (it->flat || it->first_in_scope || it->scope == 0) continue;
649 17542 : u32 line = first_lead[i] != UINT32_MAX
650 44 : ? line_of(&b.lines, comments[first_lead[i]].off)
651 17586 : : it->line;
652 17586 : if (line == 0 || !line_is_blank(&b.lines, line - 1)) continue;
653 48 : map.entry[entry_for(&map, it->node)].blank_before = 1;
654 : }
655 :
656 : // The pool. Ranks never decrease, so equal-ranked comments are adjacent and
657 : // every run below is contiguous without a second pass to prove it.
658 20179 : WokComment *pool = WOK_NEW_N(arena, WokComment, ncomments + 1);
659 69331 : for (usize ci = 0; ci < ncomments; ci++) {
660 49152 : WokTriviaEntry *e = &map.entry[entry_for(&map, target_node(&b,
661 49152 : &target[ci]))];
662 49152 : if (target[ci].kind == T_LEAD) {
663 35622 : if (e->lead_n == 0) e->lead_first = (u32)ci;
664 35622 : e->lead_n++;
665 : } else {
666 13530 : if (e->trail_n == 0) e->trail_first = (u32)ci;
667 13530 : e->trail_n++;
668 : }
669 49152 : pool[ci] = comments[ci];
670 : }
671 :
672 20179 : table->entry = map.entry;
673 20179 : table->nentries = map.nentry;
674 20179 : table->comment = pool;
675 20179 : table->ncomments = (u32)ncomments;
676 20179 : bind_table(file, table);
677 20179 : return table;
678 : }
|