yaml.bend

yaml-for-bend@0.1.1.0 · 121 lines · 16 definitions

1# YAML for Bend: read YAML text into values and write values back as YAML.
2#
3#   import ./yaml.bend as Y
4#   import ./value.bend as V
5#
6#   Y.load("a: [1, 2]")    # Done{V.Mapping{[V.Entry{V.Str{"a"}, V.Seq{...}}]}}
7#
8# load reads under the YAML 1.2 Core schema; load.v11 reads the YAML 1.1
9# types (yes/no booleans, 0 octal, base 60, timestamps, "<<" merges).
10# Errors say what went wrong and where: T.error.show(e) is "mapping values
11# are not allowed in this context at line 1 column 5".
12
13import Base
14
15import yaml-for-bend@0.1.1.0/token.bend as T
16
17import yaml-for-bend@0.1.1.0/parse.bend as P
18
19import yaml-for-bend@0.1.1.0/value.bend as V
20
21import yaml-for-bend@0.1.1.0/compose.bend as C
22
23import yaml-for-bend@0.1.1.0/emit.bend as E
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 doc.vals(ds: List<&2, C.Doc>) -> List<&2, V.Yaml>:
33  match ds:
34    case Nil{}:
35      []
36    case Con{d, rest}:
37      match d:
38        case C.Doc{v, at}:
39          v <> doc.vals(rest)
40
41def all.of(r: Result<&2, &2, T.Error, List<&2, C.Doc>>) -> Result<&2, &2, T.Error, List<&2, V.Yaml>>:
42  match r:
43    case Fail{e}:
44      Fail{e}
45    case Done{ds}:
46      Done{doc.vals(ds)}
47
48def one.of(r: Result<&2, &2, T.Error, List<&2, C.Doc>>) -> Result<&2, &2, T.Error, V.Yaml>:
49  match r:
50    case Fail{e}:
51      Fail{e}
52    case Done{ds}:
53      match ds:
54        case Nil{}:
55          Done{V.Null{}}
56        case Con{d, rest}:
57          match d:
58            case C.Doc{v, at}:
59              match rest:
60                case Nil{}:
61                  Done{v}
62                case Con{other, more}:
63                  match other:
64                    case C.Doc{w, second}:
65                      Fail{T.Error{"found a second document where load expects one; use load.all", second, T.Content{}}}
66
67# A text past the size limit is refused before it is read.
68def sized(+lim: C.Limits, +text: String, read: Unit -> Result<&2, &2, T.Error, List<&2, C.Doc>>) -> Result<&2, &2, T.Error, List<&2, C.Doc>>:
69  when(Result<&2, &2, T.Error, List<&2, C.Doc>>, C.longer(text, C.limits.size(lim)),
70    u => Fail{C.limit.error("found a text longer than the size limit of " ++ Nat.show(C.limits.size(lim)) ++ " characters", T.Mark{0n, 0n, 0n})},
71    read)
72
73def docs(+s: C.Schema, +lim: C.Limits, +text: String) -> Result<&2, &2, T.Error, List<&2, C.Doc>>:
74  sized(lim, text, u => C.compose(P.events(text, C.limits.depth(lim)), s, lim))
75
76# Every document in +text+ under schema +s+, within limits +lim+.
77def load.all.with(s: C.Schema, lim: C.Limits, text: String) -> Result<&2, &2, T.Error, List<&2, V.Yaml>>:
78  all.of(docs(s, lim, text))
79
80# The one document in +text+ under schema +s+, within limits +lim+: null
81# when there is none, an error when there are more.
82def load.with(s: C.Schema, lim: C.Limits, text: String) -> Result<&2, &2, T.Error, V.Yaml>:
83  one.of(docs(s, lim, text))
84
85# The one document in +text+ under the YAML 1.2 Core schema, within the
86# default limits: null when there is none, an error when there are more.
87# A repeated key is an error.
88def load(text: String) -> Result<&2, &2, T.Error, V.Yaml>:
89  load.with(C.Core{}, C.limits(), text)
90
91# Every document in +text+, under the Core schema.
92def load.all(text: String) -> Result<&2, &2, T.Error, List<&2, V.Yaml>>:
93  load.all.with(C.Core{}, C.limits(), text)
94
95# load, reading the YAML 1.1 types.
96def load.v11(text: String) -> Result<&2, &2, T.Error, V.Yaml>:
97  load.with(C.Yaml11{}, C.limits(), text)
98
99# load.all, reading the YAML 1.1 types.
100def load.all.v11(text: String) -> Result<&2, &2, T.Error, List<&2, V.Yaml>>:
101  load.all.with(C.Yaml11{}, C.limits(), text)
102
103# The default limits: C.Limits{size, depth, aliases, digits} with a text of
104# at most 1048576 characters, collections nested at most 256 deep, aliases
105# copying at most 100000 nodes into a document, and numbers in bases other
106# than ten at most 1000 characters long.
107def limits() -> C.Limits:
108  C.limits()
109
110# The events of +text+, as the parser emits them, and the error that ended
111# them early, if one did. Flow collections nest at most 256 deep.
112def events(text: String) -> P.Parsed:
113  P.events(text, C.limits.depth(C.limits()))
114
115# +v+ as YAML text in block style, which load reads back as +v+.
116def dump(v: V.Yaml) -> String:
117  E.dump(v)
118
119# Values as a stream of documents, each after "---".
120def dump.all(vs: List<&2, V.Yaml>) -> String:
121  E.dump.all(vs)