fmt/csv.bend
cswap-bend-cli@0.1.0.0 · 182 lines · 30 definitions
1import Base
2
3
4
5
6
7
8
9def special(+c: U32) -> Bool:
10 U32.is_eq(c, 44) || U32.is_eq(c, 34) || U32.is_eq(c, 10) || U32.is_eq(c, 13)
11
12def needs(s: String) -> Bool:
13 match s:
14 case SNil{}:
15 False{}
16 case SCon{Chr{c}, t}:
17 special(c) || needs(t)
18
19def dq(s: String) -> String:
20 match s:
21 case SNil{}:
22 ""
23 case SCon{Chr{+c}, t}:
24 Bool.pick(String, U32.is_eq(c, 34), "\"\"", SCon{Chr{c}, SNil{}}) ++ dq(t)
25
26def field.q(c: Bool, +s: String) -> String:
27 Bool.pick(String, c, "\"" ++ dq(s) ++ "\"", s)
28
29
30def field(+s: String) -> String:
31 field.q(needs(s), s)
32
33
34def row(fs: List<&2, String>) -> String:
35 match fs:
36 case Nil{}:
37 ""
38 case Con{h, Nil{}}:
39 field(h)
40 case Con{h, Con{h2, t2}}:
41 field(h) ++ "," ++ row(h2 <> t2)
42
43
44def table(rs: List<&2, List<&2, String>>) -> String:
45 match rs:
46 case Nil{}:
47 ""
48 case Con{r, t}:
49 row(r) ++ "\n" ++ table(t)
50
51
52
53type Mode is Data:
54 MStart{}
55 MPlain{}
56 MQuoted{}
57 MClosed{}
58
59
60type St is Data:
61 St{rows: List<&2, List<&2, String>>, row: List<&2, String>, cur: String, mode: Mode, err: String}
62
63def start() -> St:
64 St{[], [], "", MStart{}, ""}
65
66def with_mode(+st: St, m: Mode) -> St:
67 match st:
68 case St{rows, row, cur, mode, err}:
69 St{rows, row, cur, m, err}
70
71def push(+c: U32, +st: St) -> St:
72 match st:
73 case St{rows, row, cur, mode, err}:
74 St{rows, row, SCon{Chr{c}, cur}, mode, err}
75
76
77def end_field(+st: St, m: Mode) -> St:
78 match st:
79 case St{rows, row, cur, mode, err}:
80 St{rows, String.reverse(cur) <> row, "", m, err}
81
82def end_row(+st: St) -> St:
83 match st:
84 case St{rows, row, cur, mode, err}:
85 St{List.reverse(&2, String, row) <> rows, [], cur, MStart{}, err}
86
87def end_both(+st: St) -> St:
88 end_row(end_field(st, MStart{}))
89
90def fail(+st: St, msg: String) -> St:
91 match st:
92 case St{rows, row, cur, mode, err}:
93 St{rows, row, cur, mode, Bool.pick(String, String.is_empty(err), msg, err)}
94
95def row_empty(+st: St) -> Bool:
96 match st:
97 case St{rows, row, cur, mode, err}:
98 List.is_empty(&2, String, row)
99
100def blank.pick(empty: Bool, +st: St) -> St:
101 match empty:
102 case True{}:
103 st
104 case False{}:
105 end_both(st)
106
107def sel.st(c: Bool, +a: St, +b: St) -> St:
108 match c:
109 case True{}:
110 a
111 case False{}:
112 b
113
114def at_start(+c: U32, +st: St) -> St:
115 sel.st(U32.is_eq(c, 34), with_mode(st, MQuoted{}),
116 sel.st(U32.is_eq(c, 44), end_field(st, MStart{}),
117 sel.st(U32.is_eq(c, 10), blank.pick(row_empty(st), st),
118 sel.st(U32.is_eq(c, 13), st,
119 with_mode(push(c, st), MPlain{})))))
120
121def in_plain(+c: U32, +st: St) -> St:
122 sel.st(U32.is_eq(c, 44), end_field(st, MStart{}),
123 sel.st(U32.is_eq(c, 10), end_both(st),
124 sel.st(U32.is_eq(c, 13), st,
125 push(c, st))))
126
127def in_quoted(+c: U32, +st: St) -> St:
128 sel.st(U32.is_eq(c, 34), with_mode(st, MClosed{}), push(c, st))
129
130def after_quote(+c: U32, +st: St) -> St:
131 sel.st(U32.is_eq(c, 34), with_mode(push(34, st), MQuoted{}),
132 sel.st(U32.is_eq(c, 44), end_field(st, MStart{}),
133 sel.st(U32.is_eq(c, 10), end_both(st),
134 sel.st(U32.is_eq(c, 13), st,
135 fail(st, "text after a closing quote")))))
136
137def step(m: Mode, +c: U32, +st: St) -> St:
138 match m:
139 case MStart{}:
140 at_start(c, st)
141 case MPlain{}:
142 in_plain(c, st)
143 case MQuoted{}:
144 in_quoted(c, st)
145 case MClosed{}:
146 after_quote(c, st)
147
148def mode_of(+st: St) -> Mode:
149 match st:
150 case St{rows, row, cur, mode, err}:
151 mode
152
153def go(s: String, +st: St) -> St:
154 match s:
155 case SNil{}:
156 st
157 case SCon{Chr{c}, t}:
158 go(t, step(mode_of(st), c, st))
159
160
161def close(m: Mode, +st: St) -> St:
162 match m:
163 case MStart{}:
164 blank.pick(row_empty(st), st)
165 case MQuoted{}:
166 fail(st, "unterminated quoted field")
167 case MPlain{}:
168 end_both(st)
169 case MClosed{}:
170 end_both(st)
171
172def result(+st: St) -> Result<&2, &2, String, List<&2, List<&2, String>>>:
173 match st:
174 case St{rows, row, cur, mode, err}:
175 Bool.pick(Result<&2, &2, String, List<&2, List<&2, String>>>, String.is_empty(err), Done{List.reverse(&2, List<&2, String>, rows)}, Fail{err})
176
177def parse.fin(+st: St) -> Result<&2, &2, String, List<&2, List<&2, String>>>:
178 result(close(mode_of(st), st))
179
180
181def parse(s: String) -> Result<&2, &2, String, List<&2, List<&2, String>>>:
182 parse.fin(go(s, start()))