lib/text/main.bend
bend-lint-rules@0.2.0.0 · 398 lines · 45 definitions
1
2
3import Base
4import bend-lint-api@0.2.0.0/shared/lists.bend as Lists
5import bend-lint-api@0.2.0.0/shared/names.bend as Names
6import bend-lint-api@0.2.0.0/shared/strings.bend as Strings
7
8
9
10
11
12type Edit is Data:
13 Edit{path: String, beg: U32, end: U32, text: String}
14
15def Edit.path(edit: Edit) -> String:
16 match edit:
17 case Edit{path, beg, end, text}:
18 path
19
20def Edit.is_le(a: Edit, b: Edit) -> Bool:
21 match a b:
22 case Edit{p, x, e, t} Edit{q, y, f, u}:
23 U32.is_le(x, y)
24
25
26
27def apply.go(edits: List<&2, Edit>, +rest: String, +at: U32, out: List<&2, String>) -> String:
28 match edits:
29 case Nil{}:
30 Strings.onto(rest <> out, "")
31 case Con{Edit{p, beg, +end, text}, more}:
32 apply.go(
33 more,
34 String.drop(rest, U32.to_nat((end - at: U32))),
35 end,
36 text <> Strings.take(rest, U32.to_nat((beg - at: U32))) <> out
37 )
38
39
40def apply(edits: List<&2, Edit>, text: String) -> String:
41 apply.go(Lists.sort(~Edit, ~Edit.is_le, edits), text, 0, [])
42
43
44
45
46
47
48type Spans is Data:
49 SLeaf{}
50 SNode{height: U32, left: Spans, beg: U32, end: U32, right: Spans}
51
52def Spans.height(t: Spans) -> U32:
53 match t:
54 case SLeaf{}:
55 0
56 case SNode{h, l, b, e, r}:
57 h
58
59
60def Spans.node(+l: Spans, b: U32, e: U32, +r: Spans) -> Spans:
61 +hl = Spans.height(l)
62 +hr = Spans.height(r)
63 SNode{(Bool.pick(U32, U32.is_ge(hl, hr), hl, hr) + 1: U32), l, b, e, r}
64
65def Spans.lean_left.if(
66 outer: Bool,
67 ll: Spans,
68 lb: U32,
69 le: U32,
70 lr: Spans,
71 b: U32,
72 e: U32,
73 r: Spans
74) -> Spans:
75 match outer lr:
76 case False{} SNode{h, lrl, lrb, lre, lrr}:
77 Spans.node(Spans.node(ll, lb, le, lrl), lrb, lre, Spans.node(lrr, b, e, r))
78 case _ _:
79 Spans.node(ll, lb, le, Spans.node(lr, b, e, r))
80
81
82
83def Spans.lean_left(l: Spans, b: U32, e: U32, r: Spans) -> Spans:
84 match l:
85 case SNode{h, +ll, lb, le, +lr}:
86 Spans.lean_left.if(U32.is_ge(Spans.height(ll), Spans.height(lr)), ll, lb, le, lr, b, e, r)
87 case SLeaf{}:
88 Spans.node(SLeaf{}, b, e, r)
89
90def Spans.lean_right.if(
91 outer: Bool,
92 l: Spans,
93 b: U32,
94 e: U32,
95 rl: Spans,
96 rb: U32,
97 re: U32,
98 rr: Spans
99) -> Spans:
100 match outer rl:
101 case False{} SNode{h, rll, rlb, rle, rlr}:
102 Spans.node(Spans.node(l, b, e, rll), rlb, rle, Spans.node(rlr, rb, re, rr))
103 case _ _:
104 Spans.node(Spans.node(l, b, e, rl), rb, re, rr)
105
106
107def Spans.lean_right(l: Spans, b: U32, e: U32, r: Spans) -> Spans:
108 match r:
109 case SNode{h, +rl, rb, re, +rr}:
110 Spans.lean_right.if(U32.is_ge(Spans.height(rr), Spans.height(rl)), l, b, e, rl, rb, re, rr)
111 case SLeaf{}:
112 Spans.node(l, b, e, SLeaf{})
113
114def Spans.balance.if(left: Bool, right: Bool, l: Spans, b: U32, e: U32, r: Spans) -> Spans:
115 match left right:
116 case True{} _:
117 Spans.lean_left(l, b, e, r)
118 case _ True{}:
119 Spans.lean_right(l, b, e, r)
120 case _ _:
121 Spans.node(l, b, e, r)
122
123
124def Spans.balance(+l: Spans, b: U32, e: U32, +r: Spans) -> Spans:
125 +hl = Spans.height(l)
126 +hr = Spans.height(r)
127 Spans.balance.if(U32.is_gt(hl, (hr + 1: U32)), U32.is_gt(hr, (hl + 1: U32)), l, b, e, r)
128
129def Spans.cmp.of(starts: Cmp, e: U32, y: U32) -> Cmp:
130 match starts:
131 case EQ{}:
132 U32.cmp(e, y)
133 case LT{}:
134 LT{}
135 case GT{}:
136 GT{}
137
138
139def Spans.cmp(t: Spans, b: U32, e: U32) -> Cmp:
140 match t:
141 case SNode{h, l, x, y, r}:
142 Spans.cmp.of(U32.cmp(b, x), e, y)
143 case SLeaf{}:
144 GT{}
145
146
147def Spans.add(t: Spans, +b: U32, +e: U32, side: Cmp) -> Spans:
148 match t side:
149 case SLeaf{} _:
150 SNode{1, SLeaf{}, b, e, SLeaf{}}
151 case SNode{h, +l, x, y, r} LT{}:
152 Spans.balance(Spans.add(l, b, e, Spans.cmp(l, b, e)), x, y, r)
153 case SNode{h, l, x, y, +r} _:
154 Spans.balance(l, x, y, Spans.add(r, b, e, Spans.cmp(r, b, e)))
155
156
157def Spans.has(t: Spans, +b: U32, +e: U32, side: Cmp) -> Bool:
158 match t side:
159 case SLeaf{} _:
160 False{}
161 case SNode{h, l, x, y, r} EQ{}:
162 True{}
163 case SNode{h, +l, x, y, r} LT{}:
164 Spans.has(l, b, e, Spans.cmp(l, b, e))
165 case SNode{h, l, x, y, +r} GT{}:
166 Spans.has(r, b, e, Spans.cmp(r, b, e))
167
168def Spans.starts_before(t: Spans, e: U32) -> Bool:
169 match t:
170 case SNode{h, l, x, y, r}:
171 U32.is_lt(x, e)
172 case SLeaf{}:
173 False{}
174
175
176
177def Spans.last(t: Spans, +e: U32, found: Maybe<&2, U32>, before: Bool) -> Maybe<&2, U32>:
178 match t before:
179 case SLeaf{} _:
180 found
181 case SNode{h, l, x, y, +r} True{}:
182 Spans.last(r, e, Some{y}, Spans.starts_before(r, e))
183 case SNode{h, +l, x, y, r} False{}:
184 Spans.last(l, e, found, Spans.starts_before(l, e))
185
186def Spans.ends_after(last: Maybe<&2, U32>, b: U32) -> Bool:
187 match last:
188 case Some{y}:
189 U32.is_lt(b, y)
190 case None{}:
191 False{}
192
193
194def Spans.touches.insertion(insertion: Bool, +t: Spans, +b: U32, +e: U32) -> Bool:
195 match insertion:
196 case True{}:
197 Spans.has(t, b, e, Spans.cmp(t, b, e))
198 case False{}:
199 False{}
200
201def Spans.touches.of(shares: Bool, t: Spans, +b: U32, +e: U32) -> Bool:
202 match shares:
203 case True{}:
204 True{}
205 case False{}:
206 Spans.touches.insertion(U32.is_eq(b, e), t, b, e)
207
208def Spans.touches(+t: Spans, +b: U32, +e: U32) -> Bool:
209 Spans.touches.of(
210 Spans.ends_after(Spans.last(t, e, None{}, Spans.starts_before(t, e)), b),
211 t,
212 b,
213 e
214 )
215
216
217
218
219
220type Position is Data:
221 Position{line: U32, column: U32}
222
223
224type Place is Data:
225 Place{path: String, offset: U32}
226
227
228type Placed is Data:
229 Placed{index: U32, offset: U32}
230
231type Located is Data:
232 Located{index: U32, at: Position}
233
234
235type Walk is Data:
236 Walk{pending: List<&2, Placed>, out: List<&2, Located>}
237
238def Placed.is_le(a: Placed, b: Placed) -> Bool:
239 match a b:
240 case Placed{i, x} Placed{j, y}:
241 U32.is_le(x, y)
242
243def Located.is_le(a: Located, b: Located) -> Bool:
244 match a b:
245 case Located{i, x} Located{j, y}:
246 U32.is_le(i, j)
247
248def Placed.of(offsets: List<&2, U32>, +index: U32, out: List<&2, Placed>) -> List<&2, Placed>:
249 match offsets:
250 case Nil{}:
251 out
252 case Con{o, rest}:
253 Placed.of(rest, (index + 1: U32), Placed{index, o} <> out)
254
255def Walk.is_due(pending: List<&2, Placed>, at: U32) -> Bool:
256 match pending:
257 case Con{Placed{i, offset}, rest}:
258 U32.is_le(offset, at)
259 case Nil{}:
260 False{}
261
262
263def Walk.emit(
264 pending: List<&2, Placed>,
265 due: Bool,
266 +at: U32,
267 +line: U32,
268 +column: U32,
269 out: List<&2, Located>
270) -> Walk:
271 match pending due:
272 case Con{Placed{i, offset}, +rest} True{}:
273 Walk.emit(
274 rest,
275 Walk.is_due(rest, at),
276 at,
277 line,
278 column,
279 Located{i, Position{line, column}} <> out
280 )
281 case _ _:
282 Walk{pending, out}
283
284def Walk.done(w: Walk) -> List<&2, Located>:
285 match w:
286 case Walk{pending, out}:
287 out
288
289
290def Walk.go(text: String, +at: U32, +line: U32, column: U32, w: Walk) -> List<&2, Located>:
291 match text w:
292 case _ Walk{Nil{}, out}:
293 out
294 case SNil{} Walk{pending, out}:
295 Walk.done(Walk.emit(pending, True{}, 4294967295, line, column, out))
296 case SCon{+c, t} Walk{+pending, out}:
297 +ends = Char.is_eq(c, '\n')
298 +l = Bool.pick(U32, ends, (line + 1: U32), line)
299 +col = Bool.pick(
300 U32,
301 ends,
302 1,
303 (column + Bool.pick(U32, U32.is_ge(Char.to_u32(c), 65536), 2, 1): U32)
304 )
305 Walk.go(
306 t,
307 (at + 1: U32),
308 l,
309 col,
310 Walk.emit(pending, Walk.is_due(pending, (at + 1: U32)), (at + 1: U32), l, col, out)
311 )
312
313def Walk.positions(located: List<&2, Located>, out: List<&2, Position>) -> List<&2, Position>:
314 match located:
315 case Nil{}:
316 List.reverse(&2, Position, out)
317 case Con{Located{i, at}, rest}:
318 Walk.positions(rest, at <> out)
319
320
321def Walk.locate(+sorted: List<&2, Placed>, text: String) -> List<&2, Located>:
322 Walk.go(text, 0, 1, 1, Walk.emit(sorted, Walk.is_due(sorted, 0), 0, 1, 1, []))
323
324
325def positions(text: String, offsets: List<&2, U32>) -> List<&2, Position>:
326 Walk.positions(
327 Lists.sort(
328 ~Located,
329 ~Located.is_le,
330 Walk.locate(Lists.sort(~Placed, ~Placed.is_le, Placed.of(offsets, 0, [])), text)
331 ),
332 []
333 )
334
335def Place.key(path: String, offset: U32) -> String:
336 path ++ "\t" ++ U32.show(offset)
337
338
339def Place.at(places: Map<&2, Position>, path: String, offset: U32) -> Position:
340 Pair.snd(
341 Map<&2, Position>,
342 Position,
343 Map.get(Position, Position{1, 1}, places, Place.key(path, offset))
344 )
345
346
347def places.group(
348 requests: List<&2, Place>,
349 groups: Map<&2, List<&2, Placed>>
350) -> Map<&2, List<&2, Placed>>:
351 match requests:
352 case Nil{}:
353 groups
354 case Con{Place{path, +offset}, rest}:
355 places.group(rest, Names.Group.add(Placed, groups, path, Placed{offset, offset}))
356
357
358def places.keep(
359 located: List<&2, Located>,
360 +path: String,
361 out: Map<&2, Position>
362) -> Map<&2, Position>:
363 match located:
364 case Nil{}:
365 out
366 case Con{Located{offset, at}, rest}:
367 places.keep(rest, path, Map.set(&2, Position, out, Place.key(path, offset), at))
368
369
370def places.walk(
371 paths: List<&2, String>,
372 +groups: Map<&2, List<&2, Placed>>,
373 +texts: Map<&2, String>,
374 out: Map<&2, Position>
375) -> Map<&2, Position>:
376 match paths:
377 case Nil{}:
378 out
379 case Con{+path, rest}:
380 places.walk(
381 rest,
382 groups,
383 texts,
384 places.keep(
385 Walk.locate(
386 Lists.sort(~Placed, ~Placed.is_le, Names.Group.get(Placed, groups, path)),
387 Pair.snd(Map<&2, String>, String, Map.get(String, "", texts, path))
388 ),
389 path,
390 out
391 )
392 )
393
394
395
396def places(texts: Map<&2, String>, requests: List<&2, Place>) -> Map<&2, Position>:
397 +groups = places.group(requests, Map.new(&2, List<&2, Placed>))
398 places.walk(Map.keys(&2, List<&2, Placed>, groups), groups, texts, Map.new(&2, Position))