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}}})))))