src/print.bend source
src/print.bend on the hub · documented module
# src/print: a value as compact or indented JSON text.import Baseimport ./value.bend as Vdef escape.nib.go(+nn: U32, digit: Bool) -> Char: match digit: case True{}: Char.from_u32(U32.add(48, nn)) case False{}: Char.from_u32(U32.add(87, nn))def escape.nib(+nn: U32) -> Char: escape.nib.go(nn, U32.is_lt(nn, 10))def escape.u(+cp: U32) -> String: "\\u00" ++ String.from_list([escape.nib(U32.div(cp, 16)), escape.nib(U32.mod(cp, 16))])def escape.rest.go(+cp: U32, ch: Char, ctrl: Bool) -> String: match ctrl: case True{}: escape.u(cp) case False{}: String.from_list([ch])def escape.rest(+cp: U32, ch: Char) -> String: escape.rest.go(cp, ch, U32.is_lt(cp, 32))# whether a code point is a Unicode scalar value: not a surrogate, and not# past U+10FFFF. No UTF-8 JSON text can hold any otherdef scalar(+cp: U32) -> Bool: Bool.not(Bool.or(Bool.and(U32.is_ge(cp, 55296), U32.is_le(cp, 57343)), U32.is_gt(cp, 1114111)))# one scalar value, escaped when JSON requires itdef escape.plain(+cp: U32, ch: Char) -> String: Bool.pick(String, U32.is_eq(cp, 34), "\\\"", Bool.pick(String, U32.is_eq(cp, 92), "\\\\", Bool.pick(String, U32.is_eq(cp, 8), "\\b", Bool.pick(String, U32.is_eq(cp, 12), "\\f", Bool.pick(String, U32.is_eq(cp, 10), "\\n", Bool.pick(String, U32.is_eq(cp, 13), "\\r", Bool.pick(String, U32.is_eq(cp, 9), "\\t", escape.rest(cp, ch))))))))# a scalar value as escape.plain writes it; any other code point as U+FFFD,# as Go's encoding/json writes invalid UTF-8def escape.scalar(+cp: U32, ch: Char, ok: Bool) -> String: match ok: case True{}: escape.plain(cp, ch) case False{}: String.from_list([Char.from_u32(65533)])# one char of a stringdef escape.char(+ch: Char) -> String: +cp = Char.to_u32(ch) escape.scalar(cp, ch, scalar(cp))# the chars of a string with quotes, backslashes, and controls escapeddef escape(cs: List<&2, Char>) -> String: match cs: case Nil{}: "" case Con{c, t}: escape.char(c) ++ escape(t)# a string in quotes, escapeddef quote(txt: String) -> String: "\"" ++ escape(String.to_list(txt)) ++ "\""# a value as compact JSONdef print(val: V.Json) -> String: match val: case V.JNull{}: "null" case V.JBool{True{}}: "true" case V.JBool{False{}}: "false" case V.JNum{raw}: raw case V.JStr{s}: quote(s) case V.JSpan{src, +n}: quote(V.span.str(src, n, U32.is_zero(n))) case V.JArr{items}: "[" ++ print(items) ++ "]" case V.JObj{pairs}: "{" ++ print(pairs) ++ "}" case V.JNil{}: "" case V.JCons{h, V.JNil{}}: print(h) case V.JCons{h, t}: print(h) ++ "," ++ print(t) case V.JPair{k, v, V.JNil{}}: quote(k) ++ ":" ++ print(v) case V.JPair{k, v, r}: quote(k) ++ ":" ++ print(v) ++ "," ++ print(r) case V.JBind{src, +n, v, V.JNil{}}: quote(V.span.str(src, n, U32.is_zero(n))) ++ ":" ++ print(v) case V.JBind{src, +n, v, r}: quote(V.span.str(src, n, U32.is_zero(n))) ++ ":" ++ print(v) ++ "," ++ print(r)# a value as indented JSON. `pad` is the spaces a value's lines start with,# after their newline. For a chain of cells, a newline and `pad` start each# element or member, and `close` follows the last one, before the bracketdef pretty.go(val: V.Json, +pad: String, +close: String) -> String: match val: case V.JNull{}: "null" case V.JBool{True{}}: "true" case V.JBool{False{}}: "false" case V.JNum{raw}: raw case V.JStr{s}: quote(s) case V.JSpan{src, +n}: quote(V.span.str(src, n, U32.is_zero(n))) case V.JArr{V.JNil{}}: "[]" case V.JArr{items}: +inner = " " ++ pad "[\n" ++ inner ++ pretty.go(items, inner, "\n" ++ pad) ++ "]" case V.JObj{V.JNil{}}: "{}" case V.JObj{pairs}: +inner = " " ++ pad "{\n" ++ inner ++ pretty.go(pairs, inner, "\n" ++ pad) ++ "}" case V.JNil{}: "" case V.JCons{h, V.JNil{}}: pretty.go(h, pad, "") ++ close case V.JCons{h, t}: pretty.go(h, pad, "") ++ ",\n" ++ pad ++ pretty.go(t, pad, close) case V.JPair{k, v, V.JNil{}}: quote(k) ++ ": " ++ pretty.go(v, pad, "") ++ close case V.JPair{k, v, r}: quote(k) ++ ": " ++ pretty.go(v, pad, "") ++ ",\n" ++ pad ++ pretty.go(r, pad, close) case V.JBind{src, +n, v, V.JNil{}}: quote(V.span.str(src, n, U32.is_zero(n))) ++ ": " ++ pretty.go(v, pad, "") ++ close case V.JBind{src, +n, v, r}: quote(V.span.str(src, n, U32.is_zero(n))) ++ ": " ++ pretty.go(v, pad, "") ++ ",\n" ++ pad ++ pretty.go(r, pad, close)# a value as JSON indented two spaces a level, one element or member a linedef pretty(val: V.Json) -> String: pretty.go(val, "", "")# a parse result: the value, or "<bad>"def maybe(mb: Maybe<&2, V.Json>) -> String: match mb: case None{}: "<bad>" case Some{j}: print(j)