strip trailing whitespace
[efmt.git] / main.myr
blobe58307398cad934e9b6ce7ff56505e24319b64ff
1 use std
3 const main = {
4         match std.fslurp(0)
5         | `std.Err e:
6                 std.fput(2, "Error: {}\n", e)
7         | `std.Ok s:
8                 regurgitate(digest(ingest(s)), 72)
9         ;;
12 type paragraph = struct
13         first_line_prefix : char[:]
14         gen_line_prefix : char[:]
15         content : char[:]
16         empty : bool
17         merged : bool
20 type state = union
21         `Reading_prefix
22         `Reading_line
23         `Reading_line_last_was_ws
27    The algorithm:
29    Read each line, and strip off the prefix (the whitespace, markers
30    like -/•/·/#/45:/_1_/) from the content. Each line becomes its own
31    paragraph. If the content is all whitespace, the paragraph is
32    considered empty.
34    Now collapse all paragraphs. Empty paragraphs with equivalent
35    prefixes (that is, up to whitespace) collapse together. Non-empty
36    paragraphs with equivalent prefixes collapse together. If two
37    adjacent, non-empty paragraphs A, B have non-equivalent prefixes, A
38    is preceded by an empty paragraph (or nothing), B is anteceded by an
39    empty paragraph (or nothing), AND A itself was not collapsed, then
40    merge A and B, with A governing the first_line_prefix and B the
41    gen_line_prefix.
43    Now output. That's easy, you made Ori take your stupid Unicode tables
44    so you know what the cell width is.
45  */
47 /* Turn input slop into paragraphs */
48 const ingest = { str : byte[:]
49         var s : state = `Reading_prefix
50         var p : paragraph[:] = [][:]
51         var p_cur : paragraph = [
52                 .first_line_prefix = [][:],
53                 .gen_line_prefix = [][:],
54                 .content = [][:],
55                 .empty = false,
56                 .merged = false,
57         ]
58         for c : std.bychar(str)
59                 if c == ('\r' : char)
60                         continue /* dorks */
61                 ;;
62                 if c == ('\n' : char)
63                         std.slpush(&p, p_cur)
64                         p_cur = [
65                                 .first_line_prefix = [][:],
66                                 .gen_line_prefix = [][:],
67                                 .content = [][:],
68                                 .empty = false,
69                                 .merged = false,
70                         ]
71                         s = `Reading_prefix
72                         continue
73                 ;;
75                 match s
76                 | `Reading_prefix:
77                         if std.isalpha(c)
78                                 s = `Reading_line
79                                 std.slpush(&p_cur.content, c)
80                         else
81                                 std.slpush(&p_cur.first_line_prefix, c)
82                         ;;
83                 | `Reading_line:
84                         std.slpush(&p_cur.content, c)
85                         if std.isblank(c)
86                                 s = `Reading_line_last_was_ws
87                         ;;
88                 | `Reading_line_last_was_ws:
89                         if !std.isblank(c)
90                                 std.slpush(&p_cur.content, c)
91                                 s = `Reading_line
92                         ;;
93                 ;;
94         ;;
96         if p_cur.first_line_prefix.len > 0 || p_cur.content.len > 0
97                 std.slpush(&p, p_cur)
98         ;;
100         -> p
103 /* Do the paragraph joining thing */
104 const digest = {p
105         /* Mark as empty */
106         for var j = 0; j < p.len; ++j
107                 p[j].empty = (p[j].content.len == 0)
108         ;;
110         /* Easy merges */
111         for var j = 0; j + 1 < p.len; ++j
112                 if p[j].empty == p[j + 1].empty && equiv_prefixes(p[j].first_line_prefix, p[j + 1].first_line_prefix)
113                         if !p[j].merged
114                                 p[j].gen_line_prefix = std.sldup(p[j + 1].first_line_prefix)
115                         ;;
116                         merge_para(&p, j, j + 1)
117                         j--
118                 ;;
119         ;;
122         /* Hard merges */
123         for var j = 0; j + 1 < p.len; ++j
124                 if j > 0 && !p[j - 1].empty
125                         continue
126                 ;;
128                 if j + 2 < p.len && !p[j + 2].empty
129                         continue
130                 ;;
132                 if p[j].empty || p[j + 1].empty || p[j].merged
133                         continue
134                 ;;
136                 p[j].gen_line_prefix = std.sldup(p[j + 1].first_line_prefix)
137                 merge_para(&p, j, j + 1)
138         ;;
140         /* The unmerged give no distinction to the first */
141         for var j = 0; j < p.len; ++j
142                 if !p[j].merged
143                         p[j].gen_line_prefix = std.sldup(p[j].first_line_prefix)
144                 ;;
145         ;;
146         
147         /* Finally, strip whitespace from the end of content */
148         for var j = 0; j < p.len; ++j
149                 var c = &p[j].content
150                 while c#.len > 0 && std.isblank(c#[c#.len - 1])
151                         std.sldel(c, c#.len - 1)
152                 ;;
153         ;;
155         -> p
158 const regurgitate = {p, max
159         var sb : std.strbuf# = std.mksb()
160         for a : p
161                 var cur_pos = 0
162                 if a.empty
163                         /* maybe we can get away with dropping the prefix? */
164                         var need_prefix = false
165                         for c : a.first_line_prefix
166                                 if !std.isblank(c)
167                                         need_prefix = true
168                                         break
169                                 ;;
170                         ;;
171                         if !need_prefix
172                                 std.sbputc(sb, '\n')
173                                 continue
174                         ;;
176                         /* Oh well, just handle it normally */
177                 ;;
179                 /* initial prefix */
180                 for c : a.first_line_prefix
181                         std.sbputc(sb, c)
182                         cur_pos += std.cellwidth(c)
183                 ;;
185                 /* precalculate this */
186                 var gen_prefix_len = 0
187                 for c : a.gen_line_prefix
188                         gen_prefix_len += std.cellwidth(c)
189                 ;;
191                 var st, sn, e, wt, wn
192                 var j = 0
193                 while j < a.content.len
194                         (st, sn, e, wt, wn) = hypothetical_forward(a.content, j)
195                         if cur_pos + wt > max && gen_prefix_len + wn <= max
196                                 std.sbputc(sb, '\n')
197                                 for c : a.gen_line_prefix
198                                         std.sbputc(sb, c)
199                                 ;;
200                                 for var k = sn; k < e; ++k
201                                         std.sbputc(sb, a.content[k])
202                                 ;;
203                                 cur_pos = gen_prefix_len + wn
204                         else
205                                 for var k = st; k < e; ++k
206                                         std.sbputc(sb, a.content[k])
207                                 ;;
208                                 cur_pos+= wt
209                         ;;
211                         j = e
212                 ;;
214                 std.sbputc(sb, ('\n' : char))
215         ;;
217         std.writeall(1, std.sbfin(sb))
220 const equiv_prefixes = {a, b
221         var ak = 0
222         var bk = 0
223         while true
224                 while ak < a.len && std.isblank(a[ak])
225                         ak++
226                 ;;
228                 while bk < b.len && std.isblank(b[bk])
229                         bk++
230                 ;;
232                 if (ak < a.len) != (bk < b.len)
233                         -> false
234                 elif ak < a.len
235                         if a[ak] != b[bk]
236                                 -> false
237                         ;;
238                 else
239                         break
240                 ;;
242                 ak++
243                 bk++
244         ;;
246         -> true
249 const clean = {p
250         std.slfree(p.first_line_prefix)
251         std.slfree(p.gen_line_prefix)
252         std.slfree(p.content)
255 const merge_para = {p, j, k
256         if (p#[j].content.len > 0 && !std.isblank(p#[j].content[p#[j].content.len - 1]))
257                 /* TODO: what if you use U+3000 instead of ' '? Huh? */
258                 std.slpush(&(p#[j].content), (' ' : char))
259         ;;
260         std.sljoin(&(p#[j].content), p#[k].content)
261         clean(p#[k])
262         std.sldel(p, k)
263         p#[j].merged = true
266 const hypothetical_forward = {c, j
267         var start_if_this_line = j
268         var start_if_next_line = j
269         var end = j
270         var width_if_this_line = 0
271         var width_if_next_line = 0
272         var past_first_blanks = false
274         while end < c.len
275                 /*
276                    By the normalization in ingest() we should only have
277                    one blank separating non-blanks. Still, let's be damn
278                    sure.
279                  */
280                 if !past_first_blanks
281                         if!std.isblank(c[end])
282                                 past_first_blanks = true
283                                 start_if_next_line = end
284                         else
285                                 width_if_this_line += std.cellwidth(c[end])
286                         ;;
287                 ;;
289                 if past_first_blanks
290                         if std.isblank(c[end])
291                                 break
292                         ;;
293                         width_if_this_line += std.cellwidth(c[end])
294                         width_if_next_line += std.cellwidth(c[end])
295                 ;;
297                 end++
298         ;;
300         -> (start_if_this_line, start_if_next_line, end, width_if_this_line, width_if_next_line)