src/parse.bend source
src/parse.bend on the hub · documented module
# src/parse: tokens to one JSON value. Commas separate elements, a colon# separates a key from its value, and a text has one root.import Baseimport ./value.bend as Vimport ./lex.bend as Lex# where the next token of an open array or object has to betype Slot is Data: SValue{} SComma{} SKey{} SColon{}# an object key: owned text, or the first `n` chars of a source suffixtype Key is Data: KOwn{s: String} KCut{src: String, n: U32}# 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, slot: Slot} FObj{pairs: V.Json, key: Key, slot: Slot}# 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>}def word.ok(+raw: String) -> Bool: Bool.or(String.eq(raw, "null"), Bool.or(String.eq(raw, "true"), Bool.or(String.eq(raw, "false"), V.num.ok(raw))))# 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})))def step.ready(stack: List<&2, Frame>, root: Maybe<&2, V.Json>) -> Bool: match stack root: case Nil{} None{}: True{} case Con{FArr{items, SValue{}}, rest} r: True{} case Con{FObj{pairs, key, SValue{}}, rest} r: True{} case s r: False{}def put.pair(key: Key, val: V.Json, pairs: V.Json) -> V.Json: match key: case KOwn{s}: V.JPair{s, val, pairs} case KCut{src, n}: V.JBind{src, n, val, pairs}def put.text(key: Key) -> V.Json: match key: case KOwn{s}: V.JStr{s} case KCut{src, n}: V.JSpan{src, n}# a finished value lands in the innermost open frame, or becomes the rootdef put(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, vv: V.Json, bad: Bool) -> P: match stack root vv: case Nil{} None{} val: P{bad, Some{val}, Nil{}} case Nil{} Some{r} val: P{True{}, Some{r}, Nil{}} case Con{FArr{items, SValue{}}, rest} r val: P{bad, r, FArr{V.JCons{val, items}, SComma{}} <> rest} case Con{FObj{pairs, k, SValue{}}, rest} r val: P{bad, r, FObj{put.pair(k, val, pairs), KOwn{""}, SComma{}} <> rest} case s r val: P{True{}, r, Nil{}}def put.str(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, key: Key, bad: Bool) -> P: match stack: case Con{FObj{pairs, k, SKey{}}, rest}: P{bad, root, FObj{pairs, key, SColon{}} <> rest} case other: put(stack, root, put.text(key), bad)def step.open_arr.go(bad: Bool, root: Maybe<&2, V.Json>, stack: List<&2, Frame>, ok: Bool) -> P: match ok: case True{}: P{bad, root, FArr{V.JNil{}, SValue{}} <> stack} case False{}: P{True{}, root, Nil{}}def step.open_arr(st: P) -> P: P{bad, +root, +stack} = st step.open_arr.go(bad, root, stack, step.ready(stack, root))def step.open_obj.go(bad: Bool, root: Maybe<&2, V.Json>, stack: List<&2, Frame>, ok: Bool) -> P: match ok: case True{}: P{bad, root, FObj{V.JNil{}, KOwn{""}, SKey{}} <> stack} case False{}: P{True{}, root, Nil{}}def step.open_obj(st: P) -> P: P{bad, +root, +stack} = st step.open_obj.go(bad, root, stack, step.ready(stack, root))def step.comma(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, bad: Bool) -> P: match stack: case Con{FArr{items, SComma{}}, rest}: P{bad, root, FArr{items, SValue{}} <> rest} case Con{FObj{pairs, k, SComma{}}, rest}: P{bad, root, FObj{pairs, KOwn{""}, SKey{}} <> rest} case other: P{True{}, root, Nil{}}def step.colon(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, bad: Bool) -> P: match stack: case Con{FObj{pairs, k, SColon{}}, rest}: P{bad, root, FObj{pairs, k, SValue{}} <> rest} case other: P{True{}, root, Nil{}}def step.word.go(raw: String, st: P, ok: Bool) -> P: P{bad, root, stack} = st match ok: case True{}: put(stack, root, word(raw), bad) case False{}: P{True{}, root, stack}def step.word(+raw: String, st: P) -> P: step.word.go(raw, st, word.ok(raw))# a `]`: the innermost frame must be an array, empty or just after a valuedef close_arr(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, bad: Bool) -> P: match stack: case Con{FArr{V.JNil{}, SValue{}}, rest}: put(rest, root, V.JArr{V.JNil{}}, bad) case Con{FArr{items, SComma{}}, 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, empty or just after a valuedef close_obj(stack: List<&2, Frame>, root: Maybe<&2, V.Json>, bad: Bool) -> P: match stack: case Con{FObj{V.JNil{}, k, SKey{}}, rest}: put(rest, root, V.JObj{V.JNil{}}, bad) case Con{FObj{pairs, k, SComma{}}, rest}: put(rest, root, V.JObj{V.reverse(pairs, V.JNil{})}, bad) case other: P{True{}, root, Nil{}}def step.span.ok(+raw: String, ok: Bool) -> (V.Json & Bool): match ok: case True{}: (V.JNum{raw}, True{}) case False{}: (V.JNull{}, False{})def step.span.use(+raw: String) -> (V.Json & Bool): step.span.ok(raw, V.num.ok(raw))def step.span.chk(src: String, +nn: U32) -> (V.Json & Bool): step.span.use(V.span.str(src, nn, U32.is_zero(nn)))def step.span.go(src: String, nn: U32, is_null: Bool, is_true: Bool, is_false: Bool) -> (V.Json & Bool): match is_null is_true is_false: case True{} x y: (V.JNull{}, True{}) case False{} True{} y: (V.JBool{True{}}, True{}) case False{} False{} True{}: (V.JBool{False{}}, True{}) case False{} False{} False{}: step.span.chk(src, nn)def step.span.use2(st: P, val: V.Json, ok: Bool) -> P: P{bad, root, stack} = st match ok: case True{}: put(stack, root, val, bad) case False{}: P{True{}, root, stack}def step.span.open(st: P, got: (V.Json & Bool)) -> P: (v, ok) = got step.span.use2(st, v, ok)def step.span(+src: String, +nn: U32, st: P) -> P: step.span.open(st, step.span.go(src, nn, V.span.eq(src, nn, "null"), V.span.eq(src, nn, "true"), V.span.eq(src, nn, "false")))# one tokendef step(tok: Lex.Tok, st: P) -> P: match tok: case Lex.TOpenArr{}: step.open_arr(st) case Lex.TOpenObj{}: step.open_obj(st) 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{}: P{bad, root, stack} = st step.colon(stack, root, bad) case Lex.TComma{}: P{bad, root, stack} = st step.comma(stack, root, bad) case Lex.TStr{s}: P{bad, root, stack} = st put.str(stack, root, KOwn{s}, bad) case Lex.TWord{raw}: step.word(raw, st) case Lex.TStrS{src, n}: P{bad, root, stack} = st put.str(stack, root, KCut{src, n}, bad) case Lex.TWordS{src, n}: step.span(src, n, st) 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(txt: String) -> Maybe<&2, V.Json>: finish(run(Lex.tokens(txt), P{False{}, None{}, Nil{}}))