yaml11.bend
yaml-for-bend@0.1.0.0 · 229 lines · 0 definitions
1# The YAML 1.1 types (yaml.org/type), for YAML written for 1.1 parsers.
2#
3# An untagged plain scalar is null (~, null, Null, NULL or nothing), a
4# boolean (yes, no, true, false, on, off, each lowercase, capitalized or
5# uppercase), an integer (decimal, 0b binary, 0 octal, 0x hex, or base 60
6# as in 1:30:00, with "_" anywhere among the digits), a float (decimal,
7# base 60 with a fraction, .inf, -.inf, .nan), a timestamp, or a string.
8# A timestamp is Tagged{"tag:yaml.org,2002:timestamp", Str{text}}, the text
9# as written. "<<" is the merge key, which the composer handles.
10#
11# y, Y, n and N are strings, as in most 1.1 parsers, though the 1.1 type
12# list has them as booleans.
13
14import Base
15
16import yaml-for-bend@0.1.0.0/value.bend as V
17
18import yaml-for-bend@0.1.0.0/re.bend as R
19
20import yaml-for-bend@0.1.0.0/num.bend as N
21
22import yaml-for-bend@0.1.0.0/core.bend as C
23
24def when(-A: Type, hit: Bool, yes: Unit -> A, no: Unit -> A) -> A:
25 match hit:
26 case True{}:
27 yes(Unit{})
28 case False{}:
29 no(Unit{})
30
31def timestamp() -> String:
32 C.yaml.tag("timestamp")
33
34def is_true(+s: String) -> Bool:
35 List.contains(~String, ~String.eq, ["yes", "Yes", "YES", "true", "True", "TRUE", "on", "On", "ON"], s)
36
37def is_false(+s: String) -> Bool:
38 List.contains(~String, ~String.eq, ["no", "No", "NO", "false", "False", "FALSE", "off", "Off", "OFF"], s)
39
40# Patterns
41# --------
42
43def d() -> R.Pat:
44 R.digit()
45
46def digits_() -> R.Pat:
47 R.Star{R.of("0123456789_")}
48
49def bin.pat() -> List<&2, R.Pat>:
50 [R.sign(), R.c('0'), R.c('b'), R.Plus{R.of("01_")}]
51
52def oct.pat() -> List<&2, R.Pat>:
53 [R.sign(), R.c('0'), R.Plus{R.of("01234567_")}]
54
55def dec.pat() -> List<&2, R.Pat>:
56 [R.sign(), R.Alt{[[R.c('0')], [R.One{R.Range{'1', '9'}}, digits_()]]}]
57
58def hex.pat() -> List<&2, R.Pat>:
59 [R.sign(), R.c('0'), R.c('x'), R.Plus{R.Alt{[[R.One{R.Hex{}}], [R.c('_')]]}}]
60
61def sexa.part() -> R.Pat:
62 R.seq([R.c(':'), R.Opt{R.One{R.Range{'0', '5'}}}, d()])
63
64def sexa.int.pat() -> List<&2, R.Pat>:
65 [R.sign(), R.One{R.Range{'1', '9'}}, digits_(), R.Plus{sexa.part()}]
66
67def float.pat() -> List<&2, R.Pat>:
68 [R.sign(), R.Opt{R.seq([d(), digits_()])}, R.c('.'), digits_(), R.Opt{R.seq([R.of("eE"), R.of("-+"), R.digits()])}]
69
70def sexa.float.pat() -> List<&2, R.Pat>:
71 [R.sign(), d(), digits_(), R.Plus{sexa.part()}, R.c('.'), digits_()]
72
73def ymd.pat() -> List<&2, R.Pat>:
74 [d(), d(), d(), d(), R.c('-'), d(), d(), R.c('-'), d(), d()]
75
76def blank() -> R.Pat:
77 R.of(" \t")
78
79def time.pat() -> List<&2, R.Pat>:
80 [d(), d(), d(), d(), R.c('-'), d(), R.Opt{d()}, R.c('-'), d(), R.Opt{d()},
81 R.Alt{[[R.of("Tt")], [R.Plus{blank()}]]}, d(), R.Opt{d()}, R.c(':'), d(), d(), R.c(':'), d(), d(),
82 R.Opt{R.seq([R.c('.'), R.Star{d()}])},
83 R.Opt{R.seq([R.Star{blank()}, R.Alt{[[R.c('Z')], [R.of("-+"), d(), R.Opt{d()}, R.Opt{R.seq([R.c(':'), d(), d()])}]]}])}]
84
85def is_timestamp(+s: String) -> Bool:
86 Bool.or(R.matches(ymd.pat(), s), R.matches(time.pat(), s))
87
88# Values
89# ------
90
91def no.seps(s: String) -> String:
92 match s:
93 case SNil{}:
94 ""
95 case SCon{c, t}:
96 match c:
97 case '_':
98 no.seps(t)
99 case _:
100 SCon{c, no.seps(t)}
101
102def has_digit(s: String) -> Bool:
103 match s:
104 case SNil{}:
105 False{}
106 case SCon{+c, t}:
107 Bool.or(Char.is_digit(c), has_digit(t))
108
109# Base 60: each part is the next digit, "1:30" is 90.
110def sexa.go(parts: List<&2, String>, acc: List<&2, Nat>) -> List<&2, Nat>:
111 match parts:
112 case Nil{}:
113 acc
114 case Con{p, rest}:
115 sexa.go(rest, N.big.mul.add(acc, 60n, N.big.small(p, 0n)))
116
117def sexa.big(parts: List<&2, String>) -> List<&2, Nat>:
118 match parts:
119 case Nil{}:
120 []
121 case Con{p, rest}:
122 sexa.go(rest, N.big.read(p, 10n, []))
123
124def int.of.digits(+ds: String) -> String:
125 when(String, String.starts_with(ds, "0b"), u => N.big.show(N.big.read(String.drop(ds, 2n), 2n, [])),
126 u => when(String, String.starts_with(ds, "0x"), u => N.big.show(N.big.read(String.drop(ds, 2n), 16n, [])),
127 u => when(String, String.contains(ds, ":"), u => N.big.show(sexa.big(String.split(ds, ':'))),
128 u => when(String, String.starts_with(ds, "0"), u => N.big.show(N.big.read(ds, 8n, [])), u => N.big.show(N.big.read(ds, 10n, []))))))
129
130# An integer's decimal text; +s+ already matched one of the integer forms.
131def int.text(+s: String) -> String:
132 +t = no.seps(s)
133 N.signed(C.is_neg(t), int.of.digits(C.unsigned(t)))
134
135def is_int(+s: String) -> Bool:
136 Bool.or(Bool.or(R.matches(dec.pat(), s), R.matches(oct.pat(), s)),
137 Bool.or(Bool.or(R.matches(hex.pat(), s), R.matches(bin.pat(), s)), R.matches(sexa.int.pat(), s)))
138
139def before.dot(s: String) -> String:
140 match s:
141 case SNil{}:
142 ""
143 case SCon{c, t}:
144 match c:
145 case '.':
146 ""
147 case _:
148 SCon{c, before.dot(t)}
149
150def after.dot(s: String) -> String:
151 match s:
152 case SNil{}:
153 ""
154 case SCon{c, t}:
155 match c:
156 case '.':
157 t
158 case _:
159 after.dot(t)
160
161# A base-60 float: the whole part in base 60, then the fraction.
162def sexa.float(+s: String) -> String:
163 +t = no.seps(s)
164 +body = C.unsigned(t)
165 N.dec.exact(Bool.pick(String, C.is_neg(t), "-", "") ++ N.big.show(sexa.big(String.split(before.dot(body), ':'))) ++ "." ++ after.dot(body))
166
167def float.of(+s: String) -> Maybe<&2, String>:
168 +body = C.unsigned(s)
169 when(Maybe<&2, String>, C.is_inf(body), u => Some{Bool.pick(String, C.is_neg(s), "-.inf", ".inf")},
170 u => when(Maybe<&2, String>, C.is_nan(s), u => Some{".nan"},
171 u => when(Maybe<&2, String>, Bool.and(R.matches(float.pat(), s), has_digit(s)), u => Some{N.dec.exact(no.seps(s))},
172 u => when(Maybe<&2, String>, R.matches(sexa.float.pat(), s), u => Some{sexa.float(s)}, u => None{}))))
173
174def int.of(+s: String) -> Maybe<&2, String>:
175 when(Maybe<&2, String>, is_int(s), u => Some{int.text(s)}, u => None{})
176
177def or.int(m: Maybe<&2, String>, rest: Unit -> V.Yaml) -> V.Yaml:
178 match m:
179 case Some{t}:
180 V.Int{t}
181 case None{}:
182 rest(Unit{})
183
184def or.float(m: Maybe<&2, String>, rest: Unit -> V.Yaml) -> V.Yaml:
185 match m:
186 case Some{t}:
187 V.Float{t}
188 case None{}:
189 rest(Unit{})
190
191# An untagged plain scalar.
192def plain(+s: String) -> V.Yaml:
193 when(V.Yaml, C.is_null(s), u => V.Null{},
194 u => when(V.Yaml, is_true(s), u => V.Boolean{True{}},
195 u => when(V.Yaml, is_false(s), u => V.Boolean{False{}},
196 u => or.int(int.of(s), u => or.float(float.of(s),
197 u => when(V.Yaml, is_timestamp(s), u => V.Tagged{timestamp(), V.Str{s}}, u => V.Str{s}))))))
198
199def int.yaml(m: Maybe<&2, String>) -> Maybe<&2, V.Yaml>:
200 match m:
201 case Some{t}:
202 Some{V.Int{t}}
203 case None{}:
204 None{}
205
206# !!float: a float, or an integer written as one ("3" is 3.0).
207def float.yaml(m: Maybe<&2, String>, i: Maybe<&2, String>) -> Maybe<&2, V.Yaml>:
208 match m:
209 case Some{t}:
210 Some{V.Float{t}}
211 case None{}:
212 match i:
213 case Some{t}:
214 Some{V.Float{N.dec.exact(t)}}
215 case None{}:
216 None{}
217
218def bool.yaml(+s: String) -> Maybe<&2, V.Yaml>:
219 Bool.pick(Maybe<&2, V.Yaml>, is_true(s), Some{V.Boolean{True{}}}, Bool.pick(Maybe<&2, V.Yaml>, is_false(s), Some{V.Boolean{False{}}}, None{}))
220
221# A scalar with a tag: the standard types read the text in their 1.1 forms,
222# !!merge is the merge key, "!" is a string; another tag is kept.
223def tagged(+tag: String, +s: String) -> Result<&2, &2, String, V.Yaml>:
224 when(Result<&2, &2, String, V.Yaml>, Bool.or(Bool.or(String.eq(tag, "!"), String.eq(tag, C.yaml.tag("str"))), String.eq(tag, C.yaml.tag("merge"))), u => Done{V.Str{s}},
225 u => when(Result<&2, &2, String, V.Yaml>, String.eq(tag, C.yaml.tag("null")), u => C.must(Bool.pick(Maybe<&2, V.Yaml>, C.is_null(s), Some{V.Null{}}, None{}), "!!null", s),
226 u => when(Result<&2, &2, String, V.Yaml>, String.eq(tag, C.yaml.tag("bool")), u => C.must(bool.yaml(s), "!!bool", s),
227 u => when(Result<&2, &2, String, V.Yaml>, String.eq(tag, C.yaml.tag("int")), u => C.must(int.yaml(int.of(s)), "!!int", s),
228 u => when(Result<&2, &2, String, V.Yaml>, String.eq(tag, C.yaml.tag("float")), u => C.must(float.yaml(float.of(s), int.of(s)), "!!float", s),
229 u => Done{V.Tagged{tag, V.Str{s}}})))))