settings.bend
cswap-bend-cli@0.1.0.0 · 355 lines · 56 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/fs/write.bend as FWrite
3import cswap-bend-cli@0.1.0.0/json.bend as Json
4import cswap-bend-cli@0.1.0.0/fs.bend as Fs
5import cswap-bend-cli@0.1.0.0/act.bend as Act
6import cswap-bend-cli@0.1.0.0/cmd.bend as Cmd
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39type Dec is Data:
40 Dec{neg: Bool, i: String, f: String}
41
42type Exp is Data:
43 Exp{neg: Bool, n: Nat}
44
45def sel.s(c: Bool, x: String, y: String) -> String:
46 match c:
47 case True{}:
48 x
49 case False{}:
50 y
51
52def all_digits(s: String) -> Bool:
53 match s:
54 case SNil{}:
55 True{}
56 case SCon{c, t}:
57 Char.is_digit(c) && all_digits(t)
58
59def drop0.pick(z: Bool, c: Char, t: String, rest: String) -> String:
60 match z:
61 case True{}:
62 rest
63 case False{}:
64 SCon{c, t}
65
66
67def drop0(s: String) -> String:
68 match s:
69 case SNil{}:
70 SNil{}
71 case SCon{+c, +t}:
72 drop0.pick(Char.is_eq(c, '0'), c, t, drop0(t))
73
74def canon(neg: Bool, +i: String, f: String) -> Dec:
75 Dec{neg, sel.s(String.is_empty(drop0(i)), "0", drop0(i)), String.reverse(drop0(String.reverse(f)))}
76
77
78def shr(n: Nat, neg: Bool, i: String, f: String) -> Dec:
79 match n f:
80 case 0n _:
81 canon(neg, i, f)
82 case 1n+p SNil{}:
83 shr(p, neg, i ++ "0", "")
84 case 1n+p SCon{c, t}:
85 shr(p, neg, i ++ SCon{c, SNil{}}, t)
86
87
88def shl(n: Nat, neg: Bool, ri: String, f: String) -> Dec:
89 match n ri:
90 case 0n _:
91 canon(neg, String.reverse(ri), f)
92 case 1n+p SNil{}:
93 shl(p, neg, "", "0" ++ f)
94 case 1n+p SCon{c, t}:
95 shl(p, neg, t, SCon{c, f})
96
97def shift.pick(eneg: Bool, n: Nat, neg: Bool, i: String, f: String) -> Dec:
98 match eneg:
99 case True{}:
100 shl(n, neg, String.reverse(i), f)
101 case False{}:
102 shr(n, neg, i, f)
103
104def shift(d: Dec, e: Exp) -> Dec:
105 match d e:
106 case Dec{neg, i, f} Exp{eneg, n}:
107 shift.pick(eneg, n, neg, i, f)
108
109def exp.cap2(+d: String) -> Nat:
110 U32.to_nat(U32.min(Maybe.default(&2, U32, U32.read(d), 400), 400))
111
112
113
114def exp.cap(+d: String) -> Nat:
115 exp.cap2(sel.s(Nat.is_gt(String.length(d), 3n), "400", d))
116
117def exp.pick(ok: Bool, neg: Bool, d: String) -> Maybe<&2, Exp>:
118 match ok:
119 case True{}:
120 Some{Exp{neg, exp.cap(d)}}
121 case False{}:
122 None{}
123
124def exp.of3(neg: Bool, +d: String) -> Maybe<&2, Exp>:
125 exp.pick(all_digits(d) && Bool.not(String.is_empty(d)), neg, d)
126
127def exp.of2(+neg: Bool, plus: Bool, +x: String) -> Maybe<&2, Exp>:
128 exp.of3(neg, sel.s(neg || plus, String.drop(x, 1n), x))
129
130
131def exp.of(+x: String) -> Maybe<&2, Exp>:
132 exp.of2(String.starts_with(x, "-"), String.starts_with(x, "+"), x)
133
134def mant.pick(ok: Bool, neg: Bool, i: String, f: String) -> Maybe<&2, Dec>:
135 match ok:
136 case True{}:
137 Some{canon(neg, i, f)}
138 case False{}:
139 None{}
140
141def mant.mk(neg: Bool, +i: String, +f: String) -> Maybe<&2, Dec>:
142 mant.pick(all_digits(i) && all_digits(f) && Bool.not(String.is_empty(i) && String.is_empty(f)), neg, i, f)
143
144def mant.parts(neg: Bool, ps: List<&2, String>) -> Maybe<&2, Dec>:
145 match ps:
146 case Con{i, Nil{}}:
147 mant.mk(neg, i, "")
148 case Con{i, Con{f, Nil{}}}:
149 mant.mk(neg, i, f)
150 case _:
151 None{}
152
153def num.join(m: Maybe<&2, Dec>, e: Maybe<&2, Exp>) -> Maybe<&2, Dec>:
154 match m e:
155 case Some{d} Some{x}:
156 Some{shift(d, x)}
157 case _ _:
158 None{}
159
160def num.parts(neg: Bool, ps: List<&2, String>) -> Maybe<&2, Dec>:
161 match ps:
162 case Con{m, Nil{}}:
163 num.join(mant.parts(neg, String.split(m, '.')), Some{Exp{False{}, 0n}})
164 case Con{m, Con{x, Nil{}}}:
165 num.join(mant.parts(neg, String.split(m, '.')), exp.of(x))
166 case _:
167 None{}
168
169def num.sign(+neg: Bool, plus: Bool, +s: String) -> Maybe<&2, Dec>:
170 num.parts(neg, String.split(String.to_lower(sel.s(neg || plus, String.drop(s, 1n), s)), 'e'))
171
172def num.trimmed(+s: String) -> Maybe<&2, Dec>:
173 num.sign(String.starts_with(s, "-"), String.starts_with(s, "+"), s)
174
175
176
177
178def num(+s: String) -> Maybe<&2, Dec>:
179 num.trimmed(String.trim(s))
180
181def lit(+s: String) -> Dec:
182 Maybe.default(&2, Dec, num(s), Dec{False{}, "0", ""})
183
184def is_zero(d: Dec) -> Bool:
185 match d:
186 case Dec{neg, i, f}:
187 String.eq(i, "0") && String.is_empty(f)
188
189
190def mag_lt(a: Dec, b: Dec) -> Bool:
191 match a b:
192 case Dec{na, +ia, +fa} Dec{nb, +ib, +fb}:
193 Nat.is_lt(String.length(ia), String.length(ib))
194 || (Nat.is_eq(String.length(ia), String.length(ib))
195 && (String.is_lt(ia, ib) || (String.eq(ia, ib) && String.is_lt(fa, fb))))
196
197def neg_nonzero(+d: Dec) -> Bool:
198 match d:
199 case Dec{neg, i, f}:
200 neg && Bool.not(is_zero(Dec{neg, i, f}))
201
202def below(+v: Dec, +lo: Dec) -> Bool:
203 neg_nonzero(v) || mag_lt(v, lo)
204
205def above(+v: Dec, +hi: Dec) -> Bool:
206 Bool.not(neg_nonzero(v)) && mag_lt(hi, v)
207
208def clamp.pick(b: Bool, a: Bool, v: Dec, lo: Dec, hi: Dec) -> Dec:
209 match b a:
210 case True{} _:
211 lo
212 case False{} True{}:
213 hi
214 case False{} False{}:
215 v
216
217
218def clamp(+v: Dec, +lo: Dec, +hi: Dec) -> Dec:
219 clamp.pick(below(v, lo), above(v, hi), v, lo, hi)
220
221def lead0.pick(z: Bool, rest: Nat) -> Nat:
222 match z:
223 case True{}:
224 1n+rest
225 case False{}:
226 0n
227
228def lead0(s: String) -> Nat:
229 match s:
230 case SNil{}:
231 0n
232 case SCon{+c, +t}:
233 lead0.pick(Char.is_eq(c, '0'), lead0(t))
234
235def pad2(+n: Nat) -> String:
236 sel.s(Nat.is_lt(n, 10n), "0", "") ++ U32.show(U32.from_nat(n))
237
238def sci.tail(t: String) -> String:
239 match t:
240 case SNil{}:
241 ""
242 case SCon{h, r}:
243 "." ++ SCon{h, r}
244
245def sci.mant(sig: String) -> String:
246 match sig:
247 case SNil{}:
248 "0"
249 case SCon{h, r}:
250 SCon{h, sci.tail(r)}
251
252
253def sci(neg: Bool, +f: String) -> String:
254 sel.s(neg, "-", "") ++ sci.mant(drop0(f)) ++ "e-" ++ pad2(1n+lead0(f))
255
256def repr.pick(tiny: Bool, neg: Bool, i: String, +f: String) -> String:
257 match tiny:
258 case True{}:
259 sci(neg, f)
260 case False{}:
261 sel.s(neg, "-", "") ++ i ++ "." ++ sel.s(String.is_empty(f), "0", f)
262
263
264def repr(d: Dec) -> String:
265 match d:
266 case Dec{neg, +i, +f}:
267 repr.pick(String.eq(i, "0") && String.starts_with(f, "0000"), neg, i, f)
268
269def fmt.pick(whole: Bool, d: Dec) -> String:
270 match d:
271 case Dec{neg, +i, +f}:
272 sel.s(whole, i, repr(Dec{neg, i, f}))
273
274
275def fmt(d: Dec) -> String:
276 match d:
277 case Dec{neg, i, +f}:
278 fmt.pick(String.is_empty(f), Dec{neg, i, f})
279
280
281def tenths.fin(i: String, f: String) -> U32:
282 (Maybe.default(&2, U32, U32.read(i), 0) * 10 + Maybe.default(&2, U32, U32.read(String.take(f, 1n)), 0) : U32)
283
284def tenths.dec(d: Dec) -> U32:
285 match d:
286 case Dec{neg, i, f}:
287 tenths.fin(i, f)
288
289
290def tenths(s: String) -> U32:
291 tenths.dec(lit(s))
292
293type Sort is Data:
294 KFloat{}
295 KInt{}
296 KBool{}
297 KChoice{}
298 KString{}
299
300
301
302type Spec is Data:
303 Spec{section: String, key: String, kind: Sort, lo: String, hi: String, choices: String, dflt: Json.Json}
304
305def spec.num(key: String, kind: Sort, lo: String, hi: String, dflt: String) -> Spec:
306 Spec{"autoswitch", key, kind, lo, hi, "", Json.JNum{dflt}}
307
308
309def specs() -> List<&2, Spec>:
310 [spec.num("threshold", KFloat{}, "50", "99.9", "90.0"),
311 spec.num("intervalSeconds", KFloat{}, "15", "3600", "60.0"),
312 spec.num("cooldownSeconds", KFloat{}, "0", "86400", "300.0"),
313 spec.num("hysteresisPct", KFloat{}, "0", "50", "10.0"),
314 Spec{"autoswitch", "strategy", KChoice{}, "", "", "best,consume-first", Json.JStr{"best"}},
315 Spec{"autoswitch", "includeApiKeyAccounts", KBool{}, "", "", "", Json.JBool{False{}}},
316 spec.num("unhealthyTicks", KInt{}, "1", "100", "3"),
317 Spec{"autoswitch", "model", KString{}, "", "", "", Json.JNull{}},
318 Spec{"ui", "theme", KChoice{}, "", "", "dark,light,auto", Json.JStr{"auto"}}]
319
320def dotted(+sp: Spec) -> String:
321 match sp:
322 case Spec{section, key, kind, lo, hi, choices, dflt}:
323 section ++ "." ++ key
324
325def find.pick(hit: Bool, sp: Spec, rest: Maybe<&2, Spec>) -> Maybe<&2, Spec>:
326 match hit:
327 case True{}:
328 Some{sp}
329 case False{}:
330 rest
331
332def find(+key: String, xs: List<&2, Spec>) -> Maybe<&2, Spec>:
333 match xs:
334 case Nil{}:
335 None{}
336 case Con{+h, t}:
337 find.pick(String.eq(dotted(h), key), h, find(key, t))
338
339def dotted_all(xs: List<&2, Spec>) -> List<&2, String>:
340 match xs:
341 case Nil{}:
342 []
343 case Con{+h, t}:
344 dotted(h) <> dotted_all(t)
345
346
347def spec_of.pick(m: Maybe<&2, Spec>, key: String) -> Result<&2, &2, String, Spec>:
348 match m:
349 case Some{sp}:
350 Done{sp}
351 case None{}:
352 Fail{"unknown setting '" ++ key ++ "'\nValid keys: " ++ String.join(dotted_all(specs()), ", ")}
353
354def spec_of(+key: String) -> Result<&2, &2, String, Spec>:
355 spec_of.pick(find(key, specs()), key)