src/entry.bend

bend-toon-codec@0.1.0.0 · 118 lines · 17 definitions

1import Base
2import bend-toon-codec@0.1.0.0/src/json.bend as J
3import bend-toon-codec@0.1.0.0/src/text.bend as T
4import bend-toon-codec@0.1.0.0/src/split.bend as S
5import bend-toon-codec@0.1.0.0/src/prim.bend as P
6import bend-toon-codec@0.1.0.0/src/head.bend as Hd
7import bend-toon-codec@0.1.0.0/src/hdr.bend as H
8
9# One logical line seen as an entry (spec sections 5, 6, 8): `key: value`,
10# `key:` (opens an object), `key[N]...:` (an array header), or none of these.
11
12type Ent is Data:
13  ENot{}
14  EVal{k: String, v: J.Json}
15  EOpen{k: String}
16  EArr{k: Maybe<&2, String>, h: H.Hdr}
17
18def val.done(+k: String, +r: Result<&2, &2, String, J.Json>) -> Result<&2, &2, String, Ent>:
19  match r:
20    case Done{v}:
21      Done{EVal{k, v}}
22    case Fail{e}:
23      Fail{e}
24
25def val.pick(+blank: Bool, +arr: Bool, +k: String, +t: String) -> Result<&2, &2, String, Ent>:
26  match blank arr:
27    case True{} _:
28      Done{EOpen{k}}
29    case False{} True{}:
30      Done{EArr{Some{k}, H.empty()}}
31    case False{} False{}:
32      val.done(k, P.parse(t))
33
34# The text after the colon of `key:`.
35def val(+k: String, +v: String) -> Result<&2, &2, String, Ent>:
36  val.pick(String.is_empty(String.trim(v)), String.eq(String.trim(v), "[]"), k, String.trim(v))
37
38def hdr.done(+k: Maybe<&2, String>, +r: Result<&2, &2, String, H.Hdr>) -> Result<&2, &2, String, Ent>:
39  match r:
40    case Done{h}:
41      Done{EArr{k, h}}
42    case Fail{e}:
43      Fail{e}
44
45def quoted.pick(+colon: Bool, +brack: Bool, +k: String, +rest: String) -> Result<&2, &2, String, Ent>:
46  match colon brack:
47    case True{} _:
48      val(Hd.mark() ++ k, Hd.drop(rest))
49    case False{} True{}:
50      hdr.done(Some{Hd.mark() ++ k}, H.parse(rest))
51    case False{} False{}:
52      Done{ENot{}}
53
54# quoted key, then what follows its closing quote
55def quoted.rest(+k: String, +rest: String) -> Result<&2, &2, String, Ent>:
56  quoted.pick(Hd.lead(rest, 58), Hd.lead(rest, 91), k, rest)
57
58def quoted(+r: Result<&2, &2, String, T.Cut>) -> Result<&2, &2, String, Ent>:
59  match r:
60    case Fail{e}:
61      Fail{e}
62    case Done{T.Cut{k, rest}}:
63      quoted.rest(k, rest)
64
65# The key text before the colon at `ci`, and the value after it.
66def kv(+text: String, +ci: U32) -> Result<&2, &2, String, Ent>:
67  val(String.trim(String.take(text, U32.to_nat(ci))), String.drop(text, U32.to_nat((ci + 1 : U32))))
68
69def key.opt.pick(+blank: Bool, +k: String) -> Maybe<&2, String>:
70  match blank:
71    case True{}:
72      None{}
73    case False{}:
74      Some{k}
75
76def key.opt(+raw: String) -> Maybe<&2, String>:
77  key.opt.pick(String.is_empty(String.trim(raw)), String.trim(raw))
78
79# A header that did not parse: with no colon at all the line is no entry; with
80# one, strict mode reports the header and lenient mode reads a literal key.
81def hdr.bad(+nocolon: Bool, +strict: Bool, +e: String, +text: String, +ci: U32) -> Result<&2, &2, String, Ent>:
82  match nocolon strict:
83    case True{} _:
84      Done{ENot{}}
85    case False{} True{}:
86      Fail{e}
87    case False{} False{}:
88      kv(text, ci)
89
90def hdr.at(+strict: Bool, +text: String, +ci: U32, +bi: U32, +r: Result<&2, &2, String, H.Hdr>) -> Result<&2, &2, String, Ent>:
91  match r:
92    case Done{h}:
93      Done{EArr{key.opt(String.take(text, U32.to_nat(bi))), h}}
94    case Fail{e}:
95      hdr.bad(U32.is_eq(ci, S.none()), strict, e, text, ci)
96
97def bare.pick(+strict: Bool, +bfirst: Bool, +nocolon: Bool, +text: String, +ci: U32, +bi: U32) -> Result<&2, &2, String, Ent>:
98  match bfirst nocolon:
99    case True{} _:
100      hdr.at(strict, text, ci, bi, H.parse(String.drop(text, U32.to_nat(bi))))
101    case False{} True{}:
102      Done{ENot{}}
103    case False{} False{}:
104      kv(text, ci)
105
106def bare(+strict: Bool, +text: String) -> Result<&2, &2, String, Ent>:
107  bare.pick(strict, U32.is_lt(S.find(text, 91, 0), S.find(text, 58, 0)), U32.is_eq(S.find(text, 58, 0), S.none()), text, S.find(text, 58, 0), S.find(text, 91, 0))
108
109def parse.pick(+isq: Bool, +strict: Bool, +text: String) -> Result<&2, &2, String, Ent>:
110  match isq:
111    case True{}:
112      quoted(T.unq(Hd.drop(text)))
113    case False{}:
114      bare(strict, text)
115
116# Parse a line's content (after indentation or after a list hyphen).
117def parse(+strict: Bool, +text: String) -> Result<&2, &2, String, Ent>:
118  parse.pick(Hd.lead(text, 34), strict, text)