emit.bend
yaml-for-bend@0.1.1.0 · 237 lines · 28 definitions
1
2
3
4
5
6
7
8
9
10import Base
11
12import yaml-for-bend@0.1.1.0/value.bend as V
13
14import yaml-for-bend@0.1.1.0/core.bend as C
15
16
17
18
19type Pos is Data:
20 Line{}
21 Dash{}
22 Colon{}
23 More{}
24
25def when(-A: Type, hit: Bool, yes: Unit -> A, no: Unit -> A) -> A:
26 match hit:
27 case True{}:
28 yes(Unit{})
29 case False{}:
30 no(Unit{})
31
32def code(c: Char) -> U32:
33 match c:
34 case Chr{x}:
35 x
36
37def is_in(c: Char, s: String) -> Bool:
38 String.contains(s, SCon{c, SNil{}})
39
40
41
42
43def hex.digit(+n: Nat) -> Char:
44 Chr{U32.from_nat(Bool.pick(Nat, Nat.is_lt(n, 10n), Nat.add(n, 48n), Nat.add(n, 55n)))}
45
46
47def hex.text(width: Nat, +n: Nat) -> String:
48 match width:
49 case 0n:
50 ""
51 case 1n+k:
52 hex.text(k, Nat.div(n, 16n)) ++ SCon{hex.digit(Nat.mod(n, 16n)), SNil{}}
53
54
55
56def is_plain.char(+x: U32) -> Bool:
57 Bool.or(Bool.and(U32.is_ge(x, 32), U32.is_le(x, 126)),
58 Bool.or(Bool.and(U32.is_ge(x, 160), U32.is_le(x, 55295)), Bool.or(Bool.and(U32.is_ge(x, 57344), U32.is_le(x, 65533)), U32.is_ge(x, 65536))))
59
60def escape.other(c: Char) -> String:
61 match c:
62 case Chr{+x}:
63 +n = U32.to_nat(x)
64 Bool.pick(String, U32.is_eq(x, 133), "\\N",
65 Bool.pick(String, U32.is_eq(x, 8232), "\\L",
66 Bool.pick(String, U32.is_eq(x, 8233), "\\P",
67 Bool.pick(String, is_plain.char(x), SCon{Chr{x}, SNil{}},
68 Bool.pick(String, Nat.is_lt(n, 256n), "\\x" ++ hex.text(2n, n), "\\u" ++ hex.text(4n, n))))))
69
70def escape(c: Char) -> String:
71 match c:
72 case '"':
73 "\\\""
74 case '\\':
75 "\\\\"
76 case '\n':
77 "\\n"
78 case '\t':
79 "\\t"
80 case '\r':
81 "\\r"
82 case _:
83 escape.other(c)
84
85def escaped(s: String) -> String:
86 match s:
87 case SNil{}:
88 ""
89 case SCon{c, t}:
90 escape(c) ++ escaped(t)
91
92def quoted(s: String) -> String:
93 "\"" ++ escaped(s) ++ "\""
94
95def all.plain(s: String) -> Bool:
96 match s:
97 case SNil{}:
98 True{}
99 case SCon{c, t}:
100 Bool.and(is_plain.char(code(c)), all.plain(t))
101
102def last.char(s: String) -> Char:
103 Maybe.default(&2, Char, String.get(String.reverse(s), 0n), ' ')
104
105def first.char(s: String) -> Char:
106 Maybe.default(&2, Char, String.get(s, 0n), ' ')
107
108def is_str(v: V.Yaml) -> Bool:
109 match v:
110 case V.Str{t}:
111 True{}
112 case _:
113 False{}
114
115
116
117
118def is_safe.plain(+s: String) -> Bool:
119 Bool.and(Bool.and(Bool.not(String.is_empty(s)), all.plain(s)),
120 Bool.and(Bool.and(Bool.not(is_in(first.char(s), " -?:,[]{}#&*!|>'\"%@`")), Bool.not(Char.is_eq(last.char(s), ' '))),
121 Bool.and(Bool.and(Bool.not(String.contains(s, ": ")), Bool.not(String.contains(s, " #"))),
122 Bool.and(Bool.not(String.ends_with(s, ":")), is_str(C.plain(s))))))
123
124def str.text(+s: String) -> String:
125 when(String, is_safe.plain(s), u => s, u => quoted(s))
126
127def tag.text(+tag: String) -> String:
128 +core = {"tag:yaml.org,2002:" : String}
129 Bool.pick(String, String.starts_with(tag, core), "!!" ++ String.drop(tag, String.length(core)),
130 Bool.pick(String, Bool.and(String.starts_with(tag, "!"), Bool.not(String.contains(tag, " "))), tag, "!<" ++ tag ++ ">"))
131
132def spaces(n: Nat) -> String:
133 String.repeat(" ", n)
134
135
136
137def lead(p: Pos, +ind: Nat) -> String:
138 match p:
139 case Line{}:
140 spaces(ind)
141 case Dash{}:
142 ""
143 case Colon{}:
144 "\n" ++ spaces(ind)
145 case More{}:
146 spaces(ind)
147
148
149def scalar.lead(p: Pos, +ind: Nat) -> String:
150 match p:
151 case Line{}:
152 spaces(ind)
153 case Dash{}:
154 ""
155 case Colon{}:
156 " "
157 case More{}:
158 spaces(ind)
159
160def is_more(p: Pos) -> Bool:
161 match p:
162 case More{}:
163 True{}
164 case _:
165 False{}
166
167def is_collection(v: V.Yaml) -> Bool:
168 match v:
169 case V.Seq{xs}:
170 Bool.not(List.is_empty(&2, V.Yaml, xs))
171 case V.Mapping{es}:
172 Bool.not(List.is_empty(&2, V.Entry, es))
173 case _:
174 False{}
175
176
177def scalar.text(v: V.Yaml) -> String:
178 match v:
179 case V.Null{}:
180 "null"
181 case V.Boolean{on}:
182 Bool.pick(String, on, "true", "false")
183 case V.Int{t}:
184 t
185 case V.Float{t}:
186 t
187 case V.Str{t}:
188 str.text(t)
189 case V.Seq{xs}:
190 "[]"
191 case V.Mapping{es}:
192 "{}"
193 case V.Tagged{t, x}:
194 ""
195
196def is_tagged(v: V.Yaml) -> Bool:
197 match v:
198 case V.Tagged{t, x}:
199 True{}
200 case _:
201 False{}
202
203
204def emit(v: V.Yaml, +ind: Nat, +p: Pos) -> String:
205 match v:
206 case V.Seq{xs}:
207 match xs:
208 case Nil{}:
209 Bool.pick(String, is_more(p), "", scalar.lead(p, ind) ++ "[]\n")
210 case Con{x, rest}:
211 lead(p, ind) ++ "- " ++ emit(x, Nat.add(ind, 2n), Dash{}) ++ emit(V.Seq{rest}, ind, More{})
212 case V.Mapping{es}:
213 match es:
214 case Nil{}:
215 Bool.pick(String, is_more(p), "", scalar.lead(p, ind) ++ "{}\n")
216 case Con{V.Entry{+k, x}, rest}:
217 when(String, is_collection(k) || is_tagged(k),
218 u => lead(p, ind) ++ "? " ++ emit(k, Nat.add(ind, 2n), Dash{}) ++ spaces(ind) ++ ":",
219 u => lead(p, ind) ++ scalar.text(k) ++ ":") ++
220 emit(x, Nat.add(ind, 2n), Colon{}) ++ emit(V.Mapping{rest}, ind, More{})
221 case V.Tagged{+tag, +x}:
222 scalar.lead(p, ind) ++ tag.text(tag) ++ Bool.pick(String, is_collection(x), "", " ") ++
223 emit(x, ind, Bool.pick(Pos, is_collection(x), Colon{}, Dash{}))
224 case _:
225 scalar.lead(p, ind) ++ scalar.text(v) ++ "\n"
226
227
228def dump(v: V.Yaml) -> String:
229 emit(v, 0n, Line{})
230
231
232def dump.all(vs: List<&2, V.Yaml>) -> String:
233 match vs:
234 case Nil{}:
235 ""
236 case Con{v, rest}:
237 "---\n" ++ dump(v) ++ dump.all(rest)