1 import Base 2 import ./head.bend as Hd 3 4 # Source text to numbered lines with their depth (spec section 12). 5 6 type DL is Data: 7 DL{no: U32, depth: U32, text: String, blank: Bool} 8 9 # Leading spaces and tabs counted, and what follows them. 10 type Ld is Data: 11 Ld{sp: U32, tabs: U32, rest: String} 12 13 def 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 20 def 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. 30 def 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 38 def at(+no: U32, +m: String) -> String: 39 "line " ++ U32.show(no) ++ ": " ++ m 40 41 def 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. 47 def 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 56 def 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 62 def 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 69 def 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 74 def 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 77 def 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 86 def 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 93 def size.of(+n: U32) -> U32: 94 U32.max(n, 1) 95 96 # Split on LF and number the lines from 1. 97 def 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)