1 # A time series from text with one value per line, as ts2vg's command line 2 # reads it. Blank lines are skipped. Each value is rounded once, from its 3 # decimal text to F32. Its x is its index, or the number in the same place 4 # in a file of x coordinates, which `xs` checks. 5 import Base 6 7 type Parsed is Data: 8 Values{rev: List<&2, F32>, n: U32} 9 Bad{line: U32} 10 11 def add(blank: Bool, m: Maybe<&2, F32>, +line: U32, rev: List<&2, F32>, +n: U32) -> Parsed: 12 match blank m: 13 case True{} _: 14 Values{rev, n} 15 case False{} Some{v}: 16 Values{v <> rev, (n + 1 : U32)} 17 case False{} None{}: 18 Bad{line} 19 20 def step(s: String, +line: U32, st: Parsed) -> Parsed: 21 match st: 22 case Values{rev, +n}: 23 +t = String.trim(s) 24 add(String.is_empty(t), F32.read(t), line, rev, n) 25 case Bad{l}: 26 Bad{l} 27 28 # Base's String.lines recurses once per character, too deep for the JS 29 # lane's stack, so this walks the text with tail calls. `h` is the character 30 # before `s`, `nl` whether it is a newline, and `cur` the line before `h`, 31 # last character first. 32 def scan(s: String, nl: Bool, +h: Char, cur: String, +line: U32, st: Parsed) -> Parsed: 33 match s nl: 34 case SNil{} True{}: 35 step(String.reverse(cur), line, st) 36 case SNil{} False{}: 37 step(String.reverse(SCon{h, cur}), line, st) 38 case SCon{+h2, t} True{}: 39 scan(t, Char.is_eq(h2, '\n'), h2, SNil{}, (line + 1 : U32), step(String.reverse(cur), line, st)) 40 case SCon{+h2, t} False{}: 41 scan(t, Char.is_eq(h2, '\n'), h2, SCon{h, cur}, line, st) 42 43 # The values of `text`, last first, and how many there are; or the first 44 # line, counted from 1, that holds no number. 45 def parse(text: String) -> Parsed: 46 match text: 47 case SNil{}: 48 Values{Nil{}, 0} 49 case SCon{+h, t}: 50 scan(t, Char.is_eq(h, '\n'), h, SNil{}, 1, Values{Nil{}, 0}) 51 52 def column(rev: List<&2, F32>, +i: U32, ys: Array) -> Array: 53 match rev: 54 case Nil{}: 55 ys 56 case Con{y, rest}: 57 column(rest, (i - 1 : U32), Array.set(F32, ys, i, y)) 58 59 # The `n` values `rev`, last first, as an array indexed from 0. 60 def ys(rev: List<&2, F32>, +n: U32) -> Array: 61 column(rev, (n - 1 : U32), Array.new(F32, Nat.add(1n, U32.log2(n)), 0.0)) 62 63 def read_text.done(m: File & Result<&1, &1, U32 & String, String>) -> IO(String): 64 (f, r) = m 65 do IO: 66 text : String <- IO.pass(String, r) 67 File.close(f) 68 return text 69 70 def read_text.sized(m: File & Result<&1, &1, U32 & String, U32>) -> IO(String): 71 (f, r) = m 72 do IO: 73 size : U32 <- IO.pass(U32, r) 74 got : File & Result<&1, &1, U32 & String, String> <- File.read(f, size) 75 read_text.done(got) 76 77 def read_text(path: String) -> IO(String): 78 do IO: 79 f : File <- IO.try(File, File.open(path, "r")) 80 sized : File & Result<&1, &1, U32 & String, U32> <- File.size(f) 81 read_text.sized(sized) 82 83 # The x coordinates of a file, last first, if they suit a series; or why 84 # they do not. 85 type XsCheck is Data: 86 XsOk{rev: List<&2, F32>} 87 XsBad{msg: String} 88 89 # Whether each value of `rest`, last first, is below `a`, as ts2vg checks 90 # that x increases. 91 def increasing(rest: List<&2, F32>, +a: F32, ok: Bool) -> Bool: 92 match rest: 93 case Nil{}: 94 ok 95 case Con{+b, t}: 96 increasing(t, b, ok && Bool.not(F32.is_le(F32.sub(a, b), 0.0))) 97 98 def increases(xrev: List<&2, F32>) -> Bool: 99 match xrev: 100 case Nil{}: 101 True{} 102 case Con{+a, t}: 103 increasing(t, a, True{}) 104 105 def xs_check(same: Bool, up: Bool, rev: List<&2, F32>, +n: U32, +m: U32) -> XsCheck: 106 match same up: 107 case False{} _: 108 XsBad{"the x coordinates hold " ++ U32.show(m) ++ " values and the series " ++ U32.show(n)} 109 case True{} False{}: 110 XsBad{"the x coordinates must increase"} 111 case True{} True{}: 112 XsOk{rev} 113 114 # The x coordinates `p` of a series of `n` values: ts2vg takes one x per 115 # value, each above the one before. 116 def xs(p: Parsed, +n: U32) -> XsCheck: 117 match p: 118 case Bad{line}: 119 XsBad{"line " ++ U32.show(line) ++ " of the x coordinates holds no number"} 120 case Values{+rev, +m}: 121 xs_check(U32.is_eq(n, m), increases(rev), rev, n, m)