1 import Base 2 import ./json.bend as J 3 import ./text.bend as T 4 import ./split.bend as S 5 import ./prim.bend as P 6 import ./head.bend as Hd 7 import ./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 12 type 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 18 def 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 25 def 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:`. 35 def 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 38 def 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 45 def 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 55 def 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 58 def 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. 66 def 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 69 def 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 76 def 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. 81 def 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 90 def 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 97 def 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 106 def 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 109 def 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). 117 def parse(+strict: Bool, +text: String) -> Result<&2, &2, String, Ent>: 118 parse.pick(Hd.lead(text, 34), strict, text)