Line data Source code
1 : #include "wok_shape.h"
2 :
3 : #include <string.h>
4 :
5 : #include "wok_token.h"
6 :
7 : typedef struct {
8 : u32 off, len;
9 : bool first_on_line;
10 : bool cont_lead; // this item is a token the layout filter reads as a
11 : // CONTINUATION lead, so its line is a wrapped one
12 : } Item;
13 :
14 : typedef struct {
15 : char *buf;
16 : usize len, cap;
17 : } Out;
18 :
19 33645 : static void put(Out *o, const char *p, usize n) {
20 33645 : if (o->len + n >= o->cap) return; // cap is a proved bound; see wok_shape
21 33645 : memcpy(o->buf + o->len, p, n);
22 33645 : o->len += n;
23 : }
24 :
25 16892 : static void put_c(Out *o, char c) { put(o, &c, 1); }
26 :
27 : // A comment's column, and whether it opens its line. WokComment carries only a
28 : // span, so both are recovered from the source. Tabs cannot appear in
29 : // indentation (the scanner rejects them), so counting bytes back to the
30 : // newline gives the exact column.
31 3027 : static u32 line_col(const char *src, u32 off, bool *first) {
32 3027 : u32 col = 1;
33 3027 : u32 i = off;
34 3027 : bool only_space = true;
35 3079 : while (i > 0 && src[i - 1] != '\n') {
36 52 : i--;
37 52 : col++;
38 52 : if (src[i] != ' ' && src[i] != '\t') only_space = false;
39 : }
40 3027 : *first = only_space;
41 3027 : return col;
42 : }
43 :
44 258 : char *wok_shape(const char *restrict src, usize src_len, WokArena *arena,
45 : WokDiagSink *diag) {
46 258 : WokScanResult sr = wok_scan(src, src_len, arena, diag);
47 :
48 : // Every emitted byte is either a source byte, one clamped space, or one
49 : // newline per line, so twice the source plus a small constant is a bound.
50 258 : Out o = {.cap = src_len * 2 + 64, .len = 0};
51 258 : o.buf = WOK_NEW_N(arena, char, o.cap);
52 :
53 258 : usize ti = 0, ci = 0;
54 258 : const WokToken *prev = nullptr;
55 258 : u32 prev_end = 0;
56 258 : bool wrote_anything = false;
57 :
58 33764 : for (;;) {
59 : // Merge the token stream and the comment list by source position. A
60 : // comment is a first-class item here: a formatter that dropped one must
61 : // not be reported as canonical.
62 17011 : bool have_tok = ti < sr.tokens.n &&
63 17011 : (WokKind)sr.tokens.tok[ti].kind != WT_EOF;
64 17011 : bool have_com = ci < sr.ncomments;
65 17011 : if (!have_tok && !have_com) break;
66 :
67 16753 : Item it;
68 16753 : if (have_tok && (!have_com || sr.tokens.tok[ti].off < sr.comments[ci].off)) {
69 15237 : const WokToken *t = &sr.tokens.tok[ti++];
70 15237 : it = (Item){.off = t->off,
71 15237 : .len = t->len,
72 15237 : .first_on_line = (t->flags & WOK_TF_FIRST_ON_LINE) != 0,
73 15237 : .cont_lead = wok_token_is_continuation_lead(t)};
74 15237 : prev = t;
75 : } else {
76 1516 : const WokComment *c = &sr.comments[ci++];
77 1516 : bool first = false;
78 1516 : (void)line_col(src, c->off, &first);
79 1516 : it = (Item){.off = c->off, .len = c->len, .first_on_line = first,
80 : .cont_lead = false};
81 1516 : prev = nullptr;
82 : }
83 :
84 16753 : if (it.first_on_line) {
85 4375 : if (wrote_anything) put_c(&o, '\n');
86 4375 : if (it.cont_lead) {
87 : // A WRAPPED line, and the one place indentation is not local: the
88 : // filler aligns a capability row under its `with`, so renaming the
89 : // function moves that column and reflows every continuation line
90 : // under it. Comparing those columns exactly would fail --check on a
91 : // rename, which is the churn arrow alignment was already exempted to
92 : // avoid. WHERE the break falls is still compared -- that is line
93 : // structure, and this line begins where it began.
94 210 : put_c(&o, ' ');
95 : } else {
96 4165 : bool ignored = false;
97 2654 : u32 col = prev && prev->off == it.off
98 : ? prev->col
99 4165 : : line_col(src, it.off, &ignored);
100 6623 : for (u32 k = 1; k < col; k++) put_c(&o, ' '); // exactly
101 : }
102 12378 : } else if (wrote_anything) {
103 : // Interior gap, clamped: this is the whole trick.
104 12378 : if (it.off > prev_end) put_c(&o, ' ');
105 : }
106 :
107 : // A block comment may span lines; its interior is copied verbatim, since
108 : // reflowing it would be a change the formatter is not allowed to make.
109 16753 : put(&o, src + it.off, it.len);
110 16753 : prev_end = it.off + it.len;
111 16753 : wrote_anything = true;
112 : }
113 :
114 258 : o.buf[o.len < o.cap ? o.len : o.cap - 1] = '\0';
115 258 : return o.buf;
116 : }
|