6 std.fput(2, "Error: {}\n", e)
8 regurgitate(digest(ingest(s)), 72)
12 type paragraph = struct
13 first_line_prefix : char[:]
14 gen_line_prefix : char[:]
23 `Reading_line_last_was_ws
29 Read each line, and strip off the prefix (whitespace and markers like
30 -/•/·/#/45:/_1_/) from the content. Each line becomes its own
31 paragraph. If the content is all whitespace, the paragraph is
34 Now collapse all paragraphs:
36 - Empty paragraphs with equivalent prefixes collapse together.
38 - Non-empty paragraphs with equivalent prefixes collapse together.
40 - If two adjacent, non-empty paragraphs A, B have non-equivalent
41 prefixes, but the prefix of A is a prefix of the prefix of B, then
42 we probably made an unfortunate mistake: they're just using an
43 exotic character in the middle of a paragraph, and by bad luck
44 that character starts the line. So transfer the suffix of B's
45 prefix to B's content and collapse A and B together.
47 - If two adjacent, non-empty paragraphs A, B have non-equivalent
48 prefixes, A is preceded by an empty paragraph (or nothing), B is
49 succeded by an empty paragraph (or nothing), AND A itself was not
50 collapsed, then merge A and B, with A governing the
51 first_line_prefix and B the gen_line_prefix.
53 Now output. That's easy, you made Ori take your stupid Unicode tables
54 so you know what the cell width is.
57 /* Turn input slop into paragraphs */
58 const ingest = { str : byte[:]
59 var s : state = `Reading_prefix
60 var p : paragraph[:] = [][:]
61 var p_cur : paragraph = [
62 .first_line_prefix = [][:],
63 .gen_line_prefix = [][:],
68 for c : std.bychar(str)
75 .first_line_prefix = [][:],
76 .gen_line_prefix = [][:],
87 if is_textual_content(c)
89 std.slpush(&p_cur.content, c)
91 std.slpush(&p_cur.first_line_prefix, c)
94 std.slpush(&p_cur.content, c)
96 s = `Reading_line_last_was_ws
98 | `Reading_line_last_was_ws:
100 std.slpush(&p_cur.content, c)
106 if p_cur.first_line_prefix.len > 0 || p_cur.content.len > 0
107 std.slpush(&p, p_cur)
114 I don't typically denote lists with ", ', or (. They should really be
115 considered alphanumeric. TODO: exotic unicode should go here as well.
117 const is_textual_content = { c : char
137 /* Do the paragraph joining thing */
140 for var j = 0; j < p.len; ++j
141 p[j].empty = (p[j].content.len == 0)
144 /* Easy merges: first two cases in "the algorithm" */
145 for var j = 0; j + 1 < p.len; ++j
146 if p[j].empty == p[j + 1].empty && equiv_prefixes(p[j].first_line_prefix, p[j + 1].first_line_prefix)
148 p[j].gen_line_prefix = std.sldup(p[j + 1].first_line_prefix)
150 merge_para(&p, j, j + 1)
157 /* third case of "the algorithm" */
158 for var j = 0; j + 1 < p.len; ++j
159 if !p[j].empty && !p[j + 1].empty && is_prefix(p[j].first_line_prefix, p[j + 1].first_line_prefix)
160 var l = p[j].first_line_prefix.len
161 var new_content = std.sldup(p[j + 1].first_line_prefix[l:])
162 std.sljoin(&new_content, p[j + 1].content)
165 .first_line_prefix = [][:],
166 .gen_line_prefix = [][:],
167 .content = new_content,
169 merge_para(&p, j, j + 1)
174 /* fourth case of "the algorithm" */
175 for var j = 0; j + 1 < p.len; ++j
176 if j > 0 && !p[j - 1].empty
180 if j + 2 < p.len && !p[j + 2].empty
184 if p[j].empty || p[j + 1].empty || p[j].merged
188 p[j].gen_line_prefix = std.sldup(p[j + 1].first_line_prefix)
189 merge_para(&p, j, j + 1)
192 /* The unmerged give no distinction to the first */
193 for var j = 0; j < p.len; ++j
195 p[j].gen_line_prefix = std.sldup(p[j].first_line_prefix)
199 /* Finally, strip whitespace from the end of content */
200 for var j = 0; j < p.len; ++j
201 var c = &p[j].content
202 while c#.len > 0 && std.isblank(c#[c#.len - 1])
203 std.sldel(c, c#.len - 1)
210 const regurgitate = {p, max
211 var sb : std.strbuf# = std.mksb()
215 /* maybe we can get away with dropping the prefix? */
216 var need_prefix = false
217 for c : a.first_line_prefix
228 /* Oh well, just handle it normally */
232 for c : a.first_line_prefix
234 cur_pos += std.cellwidth(c)
237 /* precalculate this */
238 var gen_prefix_len = 0
239 for c : a.gen_line_prefix
240 gen_prefix_len += std.cellwidth(c)
243 var st, sn, e, wt, wn
245 while j < a.content.len
246 (st, sn, e, wt, wn) = hypothetical_forward(a.content, j)
247 if cur_pos + wt > max && gen_prefix_len + wn <= max
249 for c : a.gen_line_prefix
252 for var k = sn; k < e; ++k
253 std.sbputc(sb, a.content[k])
255 cur_pos = gen_prefix_len + wn
257 for var k = st; k < e; ++k
258 std.sbputc(sb, a.content[k])
266 std.sbputc(sb, ('\n' : char))
269 std.writeall(1, std.sbfin(sb))
272 const equiv_prefixes = {a, b
276 while ak < a.len && std.isblank(a[ak])
280 while bk < b.len && std.isblank(b[bk])
284 if (ak < a.len) != (bk < b.len)
301 const is_prefix = {pre, s
306 for var j = 0; j < pre.len; ++j
317 std.slfree(p.first_line_prefix)
318 std.slfree(p.gen_line_prefix)
319 std.slfree(p.content)
322 const merge_para = {p, j, k
323 if (p#[j].content.len > 0 && !std.isblank(p#[j].content[p#[j].content.len - 1]))
324 /* TODO: what if you use U+3000 instead of ' '? Huh? */
325 std.slpush(&(p#[j].content), (' ' : char))
327 std.sljoin(&(p#[j].content), p#[k].content)
333 const hypothetical_forward = {c, j
334 var start_if_this_line = j
335 var start_if_next_line = j
337 var width_if_this_line = 0
338 var width_if_next_line = 0
339 var past_first_blanks = false
343 By the normalization in ingest() we should only have
344 one blank separating non-blanks. Still, let's be damn
347 if !past_first_blanks
348 if!std.isblank(c[end])
349 past_first_blanks = true
350 start_if_next_line = end
352 width_if_this_line += std.cellwidth(c[end])
357 if std.isblank(c[end])
360 width_if_this_line += std.cellwidth(c[end])
361 width_if_next_line += std.cellwidth(c[end])
367 -> (start_if_this_line, start_if_next_line, end, width_if_this_line, width_if_next_line)