src/dline.bend

bend-toon-codec@0.1.0.0 · 98 lines · 16 definitions

1import Base
2import bend-toon-codec@0.1.0.0/src/head.bend as Hd
3
4# Source text to numbered lines with their depth (spec section 12).
5
6type DL is Data:
7  DL{no: U32, depth: U32, text: String, blank: Bool}
8
9# Leading spaces and tabs counted, and what follows them.
10type Ld is Data:
11  Ld{sp: U32, tabs: U32, rest: String}
12
13def lead.pick(+go: Bool, +s: String, +sp: U32, +tabs: U32, +r: Ld) -> Ld:
14  match go:
15    case True{}:
16      r
17    case False{}:
18      Ld{sp, tabs, s}
19
20def inc(+b: Bool) -> U32:
21  match b:
22    case True{}:
23      1
24    case False{}:
25      0
26
27# Count the leading spaces and tabs. `live` is true until the first other
28# character; the recursion runs to the end of the line but only counts
29# while live.
30def lead(+s: String, +sp: U32, +tabs: U32, +live: Bool) -> Ld:
31  match s:
32    case SNil{}:
33      Ld{sp, tabs, s}
34    case SCon{Chr{c}, t}:
35      lead.pick(live && (U32.is_eq(c, 32) || U32.is_eq(c, 9)), s, sp, tabs,
36        lead(t, (sp + inc(live && U32.is_eq(c, 32)) : U32), (tabs + inc(live && U32.is_eq(c, 9)) : U32), live && (U32.is_eq(c, 32) || U32.is_eq(c, 9))))
37
38def at(+no: U32, +m: String) -> String:
39  "line " ++ U32.show(no) ++ ": " ++ m
40
41def blank(+no: U32) -> Result<&2, &2, String, DL>:
42  Done{DL{no, 0, SNil{}, True{}}}
43
44# Depth is spaces divided by the indent size. Strict mode refuses tabs in the
45# indentation and a count that is not a multiple. Lenient mode floors the
46# count and reads each leading tab as one indent level.
47def depth.pick(+bad_tab: Bool, +bad_mult: Bool, +no: U32, +depth: U32, +text: String) -> Result<&2, &2, String, DL>:
48  match bad_tab bad_mult:
49    case True{} _:
50      Fail{at(no, "tabs are not allowed in indentation")}
51    case False{} True{}:
52      Fail{at(no, "indentation is not a multiple of the indent size")}
53    case False{} False{}:
54      Done{DL{no, depth, text, False{}}}
55
56def make.nonblank(+no: U32, +l: Ld, +size: U32, +strict: Bool) -> Result<&2, &2, String, DL>:
57  match l:
58    case Ld{sp, tabs, rest}:
59      depth.pick(strict && Bool.not(U32.is_zero(tabs)), strict && Bool.not(U32.is_zero(U32.mod(sp, size)) ), no,
60        (U32.div(sp, size) + tabs : U32), String.trim_end(rest))
61
62def make.pick(+isblank: Bool, +no: U32, +l: Ld, +size: U32, +strict: Bool) -> Result<&2, &2, String, DL>:
63  match isblank:
64    case True{}:
65      blank(no)
66    case False{}:
67      make.nonblank(no, l, size, strict)
68
69def make.of(+no: U32, +l: Ld, +size: U32, +strict: Bool) -> Result<&2, &2, String, DL>:
70  match l:
71    case Ld{sp, tabs, rest}:
72      make.pick(String.is_empty(String.trim(rest)), no, Ld{sp, tabs, rest}, size, strict)
73
74def make(+no: U32, +line: String, +size: U32, +strict: Bool) -> Result<&2, &2, String, DL>:
75  make.of(no, lead(line, 0, 0, True{}), size, strict)
76
77def prep.cons(+r: Result<&2, &2, String, DL>, +rest: Result<&2, &2, String, List<&2, DL>>) -> Result<&2, &2, String, List<&2, DL>>:
78  match r rest:
79    case Done{l} Done{ls}:
80      Done{Con{l, ls}}
81    case Fail{e} _:
82      Fail{e}
83    case _ Fail{e}:
84      Fail{e}
85
86def prep.go(+lines: List<&2, String>, +no: U32, +size: U32, +strict: Bool) -> Result<&2, &2, String, List<&2, DL>>:
87  match lines:
88    case Nil{}:
89      Done{Nil{}}
90    case Con{l, rest}:
91      prep.cons(make(no, l, size, strict), prep.go(rest, (no + 1 : U32), size, strict))
92
93def size.of(+n: U32) -> U32:
94  U32.max(n, 1)
95
96# Split on LF and number the lines from 1.
97def prep(+src: String, +size: U32, +strict: Bool) -> Result<&2, &2, String, List<&2, DL>>:
98  prep.go(String.lines(src), 1, size.of(size), strict)