~/bend-docscommunity

lib.bend source

lib.bend on the hub · documented module

# Json v1 — leaf-only JSON AST + encode/decode for Bend.# Publish entry for this package. Depends only on Base (no Parse hub import).## Number representation: U32 (unsigned 32-bit integers). No floats, signs, or# exponents in v1.## AST constructors: JNull | JBool | JNum(U32) | JStr.# Arrays/objects (nested encode/decode) are deferred to json-nested / v2.## Supported decode subset (leaves only):#   - null, true, false#   - numbers: one or more decimal digits → U32 (via U32.read)#   - strings: "..." with escapes \", \\, \/, \n, \t, \r (other \x fail)#   - optional JSON whitespace (space/tab/CR/LF) around a single leaf value# Not in v1: arrays, objects, floats, negatives, unicode \uXXXX.import Basetype Json is Data:  JNull{}  JBool{b: Bool}  JNum{n: U32}  JStr{s: String}# ---- constructors / sugar -------------------------------------------------def Json.null() -> Json:  JNull{}def Json.bool(b: Bool) -> Json:  JBool{b}def Json.num(n: U32) -> Json:  JNum{n}def Json.str(s: String) -> Json:  JStr{s}# ---- encode / show --------------------------------------------------------def Json.encode.bool(b: Bool) -> String:  match b:    case False{}:      "false"    case True{}:      "true"# Escape " and \ only when printing; other chars pass through (valid if already# JSON-safe). Decode accepts a slightly richer escape set (see decode.str).def Json.encode.esc.char(+c: Char) -> String:  match c:    case Chr{+x}:      Bool.pick(String, U32.is_eq(x, 34), "\\\"",        Bool.pick(String, U32.is_eq(x, 92), "\\\\", SCon{Chr{x}, SNil{}}))def Json.encode.esc.go(s: String) -> String:  match s:    case SNil{}:      SNil{}    case SCon{+h, t}:      String.append(Json.encode.esc.char(h), Json.encode.esc.go(t))def Json.encode.str(+s: String) -> String:  String.append("\"", String.append(Json.encode.esc.go(s), "\""))def Json.encode(j: Json) -> String:  match j:    case JNull{}:      "null"    case JBool{b}:      Json.encode.bool(b)    case JNum{n}:      U32.show(n)    case JStr{s}:      Json.encode.str(s)def Json.show(j: Json) -> String:  Json.encode(j)# ---- decode helpers (Base-only mini parser; not the Parse package) --------def Json.skip_ws(s: String) -> String:  String.trim_start(s)def Json.starts(+p: String, +s: String) -> Bool:  String.starts_with(s, p)def Json.drop(+n: Nat, s: String) -> String:  String.drop(s, n)# Span leading digits. Nat fuel + step flag (same shape as Parse.take_while).def Json.take_digits.go(  n: Nat, s: String, acc: String, step: Bool, +h: Char, ok: Bool) -> String & String:  match n s step ok:    case 0n s2 _ _:      (String.reverse(acc), s2)    case 1n+p SNil{} False{} _:      (String.reverse(acc), SNil{})    case 1n+p SCon{+h2, t} False{} _:      Json.take_digits.go(p, t, acc, True{}, h2, Char.is_digit(h2))    case 1n+p s2 True{} False{}:      (String.reverse(acc), SCon{h, s2})    case 1n+p s2 True{} True{}:      Json.take_digits.go(p, s2, SCon{h, acc}, False{}, h, False{})    case 1n+p s2 False{} _:      (String.reverse(acc), s2)def Json.take_digits(+s: String) -> String & String:  Json.take_digits.go(    Nat.add(String.length(s), String.length(s)), s, SNil{}, False{},    Char.from_u32(0), False{}  )# String body with escapes. Nat fuel (2 steps/char) + esc flag; +fuel clones# Nat for Bool.pick arms (affine otherwise).def Json.decode.str.go(  n: Nat, esc: Bool, s: String, +acc: String) -> Maybe<&1, String & String>:  match n esc s:    case 0n _ _:      None{}    case 1n+p False{} SNil{}:      None{}    case 1n+p False{} SCon{Chr{+x}, +t}:      +fuel = p      Bool.pick(Maybe<&1, String & String>, U32.is_eq(x, 34),        Some{(String.reverse(acc), t)},        Bool.pick(Maybe<&1, String & String>, U32.is_eq(x, 92),          Json.decode.str.go(fuel, True{}, t, acc),          Json.decode.str.go(fuel, False{}, t, SCon{Chr{x}, acc})))    case 1n+p True{} SNil{}:      None{}    case 1n+p True{} SCon{Chr{+x}, +t}:      +fuel = p      Bool.pick(Maybe<&1, String & String>, U32.is_eq(x, 34),        Json.decode.str.go(fuel, False{}, t, SCon{Chr{34}, acc}),        Bool.pick(Maybe<&1, String & String>, U32.is_eq(x, 92),          Json.decode.str.go(fuel, False{}, t, SCon{Chr{92}, acc}),          Bool.pick(Maybe<&1, String & String>, U32.is_eq(x, 47),            Json.decode.str.go(fuel, False{}, t, SCon{Chr{47}, acc}),            Bool.pick(Maybe<&1, String & String>, U32.is_eq(x, 110),              Json.decode.str.go(fuel, False{}, t, SCon{Chr{10}, acc}),              Bool.pick(Maybe<&1, String & String>, U32.is_eq(x, 116),                Json.decode.str.go(fuel, False{}, t, SCon{Chr{9}, acc}),                Bool.pick(Maybe<&1, String & String>, U32.is_eq(x, 114),                  Json.decode.str.go(fuel, False{}, t, SCon{Chr{13}, acc}),                  None{}))))))def Json.decode.str.after_quote(+s: String) -> Maybe<&1, String & String>:  Json.decode.str.go(    Nat.add(String.length(s), String.length(s)), False{}, s, SNil{}  )def Json.decode.lit(+rest: String, v: Json, +s: String) -> Maybe<&1, Json & String>:  Bool.pick(    Maybe<&1, Json & String>,    Json.starts(rest, s),    Some{(v, Json.drop(String.length(rest), s))},    None{}  )def Json.decode.string_val.cont(  m: Maybe<&1, String & String>) -> Maybe<&1, Json & String>:  match m:    case None{}:      None{}    case Some{(body, rest)}:      Some{(JStr{body}, rest)}def Json.decode.string_val(s: String) -> Maybe<&1, Json & String>:  Json.decode.string_val.cont(Json.decode.str.after_quote(s))def Json.decode.number.read(  rest: String, m: Maybe<&2, U32>) -> Maybe<&1, Json & String>:  match m:    case None{}:      None{}    case Some{n}:      Some{(JNum{n}, rest)}def Json.decode.number.go(  pair: String & String) -> Maybe<&1, Json & String>:  (digits, rest) = pair  match digits:    case SNil{}:      None{}    case SCon{h, t}:      Json.decode.number.read(rest, U32.read(SCon{h, t}))def Json.decode.number(s: String) -> Maybe<&1, Json & String>:  Json.decode.number.go(Json.take_digits(s))# Leaf value only (no '[' / '{').def Json.decode.value(s: String) -> Maybe<&1, Json & String>:  match s:    case SNil{}:      None{}    case SCon{Chr{+x}, +t}:      Bool.pick(Maybe<&1, Json & String>, U32.is_eq(x, 110),        Json.decode.lit("ull", JNull{}, t),        Bool.pick(Maybe<&1, Json & String>, U32.is_eq(x, 116),          Json.decode.lit("rue", JBool{True{}}, t),          Bool.pick(Maybe<&1, Json & String>, U32.is_eq(x, 102),            Json.decode.lit("alse", JBool{False{}}, t),            Bool.pick(Maybe<&1, Json & String>, U32.is_eq(x, 34),              Json.decode.string_val(t),              Bool.pick(Maybe<&1, Json & String>,                Char.is_digit(Chr{x}),                Json.decode.number(SCon{Chr{x}, t}),                None{})))))def Json.decode.finish.rest(v: Json, rest: String) -> Maybe<&2, Json>:  match rest:    case SNil{}:      Some{v}    case SCon{h, t}:      None{}def Json.decode.finish.go(m: Maybe<&1, Json & String>) -> Maybe<&2, Json>:  match m:    case None{}:      None{}    case Some{(v, rest)}:      Json.decode.finish.rest(v, Json.skip_ws(rest))def Json.decode.finish(m: Maybe<&1, Json & String>) -> Maybe<&2, Json>:  Json.decode.finish.go(m)def Json.decode(s: String) -> Maybe<&2, Json>:  Json.decode.finish(Json.decode.value(Json.skip_ws(s)))def Json.parse(s: String) -> Maybe<&2, Json>:  Json.decode(s)def Json.decode_prefix(s: String) -> Maybe<&1, Json & String>:  Json.decode.value(Json.skip_ws(s))