~/bend-docscommunity

json/parse.bend source

json/parse.bend on the hub · documented module

# json/parse: tokens to a value, one token per step, over an explicit stack of# open arrays and objects: a loop, with no fuel and no recursion on the value.# Lenient about commas and colons; strict about nesting and a single root.import Baseimport ./value.bend as Vimport ./lex.bend as Lex# an open array (its items, reversed) or an open object (its pairs, reversed,# and the key awaiting its value)type Frame is Data:  FArr{items: V.Json}  FObj{pairs: V.Json, key: Maybe<&2, String>}# the parser's state: an error was seen, the finished root, the open framestype P is Data:  P{bad: Bool, root: Maybe<&2, V.Json>, stack: List<&2, Frame>}# a bare word as a value: null, true, false, else a number kept as textdef word(+raw: String) -> V.Json:  Bool.pick(V.Json, String.eq(raw, "null"), V.JNull{},    Bool.pick(V.Json, String.eq(raw, "true"), V.JBool{True{}},      Bool.pick(V.Json, String.eq(raw, "false"), V.JBool{False{}}, V.JNum{raw})))# a finished value lands in the innermost open frame, or becomes the rootdef put(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, v: V.Json, bad: Bool) -> P:  match stack root v:    case Nil{} None{} val:      P{bad, Some{val}, Nil{}}    case Nil{} Some{r} val:      P{True{}, Some{r}, Nil{}}    case Con{FArr{items}, rest} r val:      P{bad, r, FArr{V.JCons{val, items}} <> rest}    case Con{FObj{pairs, None{}}, rest} r V.JStr{k}:      P{bad, r, FObj{pairs, Some{k}} <> rest}    case Con{FObj{pairs, None{}}, rest} r val:      P{True{}, r, rest}    case Con{FObj{pairs, Some{k}}, rest} r val:      P{bad, r, FObj{V.JPair{k, val, pairs}, None{}} <> rest}# a `]`: the innermost frame must be an arraydef close_arr(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, bad: Bool) -> P:  match stack:    case Con{FArr{items}, rest}:      put(rest, root, V.JArr{V.reverse(items, V.JNil{})}, bad)    case other:      P{True{}, root, Nil{}}# a `}`: the innermost frame must be an object with no key waitingdef close_obj(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, bad: Bool) -> P:  match stack:    case Con{FObj{pairs, None{}}, rest}:      put(rest, root, V.JObj{V.reverse(pairs, V.JNil{})}, bad)    case other:      P{True{}, root, Nil{}}# one tokendef step(tok: Lex.Tok, st: P) -> P:  match tok:    case Lex.TOpenArr{}:      P{bad, root, stack} = st      P{bad, root, FArr{V.JNil{}} <> stack}    case Lex.TOpenObj{}:      P{bad, root, stack} = st      P{bad, root, FObj{V.JNil{}, None{}} <> stack}    case Lex.TCloseArr{}:      P{bad, root, stack} = st      close_arr(stack, root, bad)    case Lex.TCloseObj{}:      P{bad, root, stack} = st      close_obj(stack, root, bad)    case Lex.TColon{}:      st    case Lex.TComma{}:      st    case Lex.TStr{s}:      P{bad, root, stack} = st      put(stack, root, V.JStr{s}, bad)    case Lex.TWord{raw}:      P{bad, root, stack} = st      put(stack, root, word(raw), bad)    case Lex.TBad{}:      P{bad, root, stack} = st      P{True{}, root, stack}# every token, in orderdef run(toks: List<&2, Lex.Tok>, st: P) -> P:  match toks:    case Nil{}:      st    case Con{t, rest}:      run(rest, step(t, st))# the root, when nothing went wrong and nothing stayed opendef result(bad: Bool, stack: List<&2, Frame>, root: Maybe<&2, V.Json>) -> Maybe<&2, V.Json>:  match bad stack root:    case False{} Nil{} Some{j}:      Some{j}    case b s r:      None{}# the value once the tokens are overdef finish(st: P) -> Maybe<&2, V.Json>:  P{bad, root, stack} = st  result(bad, stack, root)# a text as a value, or None when it is not JSONdef parse(s: String) -> Maybe<&2, V.Json>:  finish(run(Lex.tokens(s), P{False{}, None{}, Nil{}}))