src/print.bend
emerging-ezjson@1.2.0.0 · 157 lines · 0 definitions
1# src/print: a value as compact or indented JSON text.
2import Base
3import emerging-ezjson@1.2.0.0/src/value.bend as V
4
5def escape.nib.go(+nn: U32, digit: Bool) -> Char:
6 match digit:
7 case True{}:
8 Char.from_u32(U32.add(48, nn))
9 case False{}:
10 Char.from_u32(U32.add(87, nn))
11
12def escape.nib(+nn: U32) -> Char:
13 escape.nib.go(nn, U32.is_lt(nn, 10))
14
15def escape.u(+cp: U32) -> String:
16 "\\u00" ++ String.from_list([escape.nib(U32.div(cp, 16)), escape.nib(U32.mod(cp, 16))])
17
18def escape.rest.go(+cp: U32, ch: Char, ctrl: Bool) -> String:
19 match ctrl:
20 case True{}:
21 escape.u(cp)
22 case False{}:
23 String.from_list([ch])
24
25def escape.rest(+cp: U32, ch: Char) -> String:
26 escape.rest.go(cp, ch, U32.is_lt(cp, 32))
27
28# whether a code point is a Unicode scalar value: not a surrogate, and not
29# past U+10FFFF. No UTF-8 JSON text can hold any other
30def scalar(+cp: U32) -> Bool:
31 Bool.not(Bool.or(Bool.and(U32.is_ge(cp, 55296), U32.is_le(cp, 57343)), U32.is_gt(cp, 1114111)))
32
33# one scalar value, escaped when JSON requires it
34def escape.plain(+cp: U32, ch: Char) -> String:
35 Bool.pick(String, U32.is_eq(cp, 34), "\\\"",
36 Bool.pick(String, U32.is_eq(cp, 92), "\\\\",
37 Bool.pick(String, U32.is_eq(cp, 8), "\\b",
38 Bool.pick(String, U32.is_eq(cp, 12), "\\f",
39 Bool.pick(String, U32.is_eq(cp, 10), "\\n",
40 Bool.pick(String, U32.is_eq(cp, 13), "\\r",
41 Bool.pick(String, U32.is_eq(cp, 9), "\\t",
42 escape.rest(cp, ch))))))))
43
44# a scalar value as escape.plain writes it; any other code point as U+FFFD,
45# as Go's encoding/json writes invalid UTF-8
46def escape.scalar(+cp: U32, ch: Char, ok: Bool) -> String:
47 match ok:
48 case True{}:
49 escape.plain(cp, ch)
50 case False{}:
51 String.from_list([Char.from_u32(65533)])
52
53# one char of a string
54def escape.char(+ch: Char) -> String:
55 +cp = Char.to_u32(ch)
56 escape.scalar(cp, ch, scalar(cp))
57
58# the chars of a string with quotes, backslashes, and controls escaped
59def escape(cs: List<&2, Char>) -> String:
60 match cs:
61 case Nil{}:
62 ""
63 case Con{c, t}:
64 escape.char(c) ++ escape(t)
65
66# a string in quotes, escaped
67def quote(txt: String) -> String:
68 "\"" ++ escape(String.to_list(txt)) ++ "\""
69
70# a value as compact JSON
71def print(val: V.Json) -> String:
72 match val:
73 case V.JNull{}:
74 "null"
75 case V.JBool{True{}}:
76 "true"
77 case V.JBool{False{}}:
78 "false"
79 case V.JNum{raw}:
80 raw
81 case V.JStr{s}:
82 quote(s)
83 case V.JSpan{src, +n}:
84 quote(V.span.str(src, n, U32.is_zero(n)))
85 case V.JArr{items}:
86 "[" ++ print(items) ++ "]"
87 case V.JObj{pairs}:
88 "{" ++ print(pairs) ++ "}"
89 case V.JNil{}:
90 ""
91 case V.JCons{h, V.JNil{}}:
92 print(h)
93 case V.JCons{h, t}:
94 print(h) ++ "," ++ print(t)
95 case V.JPair{k, v, V.JNil{}}:
96 quote(k) ++ ":" ++ print(v)
97 case V.JPair{k, v, r}:
98 quote(k) ++ ":" ++ print(v) ++ "," ++ print(r)
99 case V.JBind{src, +n, v, V.JNil{}}:
100 quote(V.span.str(src, n, U32.is_zero(n))) ++ ":" ++ print(v)
101 case V.JBind{src, +n, v, r}:
102 quote(V.span.str(src, n, U32.is_zero(n))) ++ ":" ++ print(v) ++ "," ++ print(r)
103
104# a value as indented JSON. `pad` is the spaces a value's lines start with,
105# after their newline. For a chain of cells, a newline and `pad` start each
106# element or member, and `close` follows the last one, before the bracket
107def pretty.go(val: V.Json, +pad: String, +close: String) -> String:
108 match val:
109 case V.JNull{}:
110 "null"
111 case V.JBool{True{}}:
112 "true"
113 case V.JBool{False{}}:
114 "false"
115 case V.JNum{raw}:
116 raw
117 case V.JStr{s}:
118 quote(s)
119 case V.JSpan{src, +n}:
120 quote(V.span.str(src, n, U32.is_zero(n)))
121 case V.JArr{V.JNil{}}:
122 "[]"
123 case V.JArr{items}:
124 +inner = " " ++ pad
125 "[\n" ++ inner ++ pretty.go(items, inner, "\n" ++ pad) ++ "]"
126 case V.JObj{V.JNil{}}:
127 "{}"
128 case V.JObj{pairs}:
129 +inner = " " ++ pad
130 "{\n" ++ inner ++ pretty.go(pairs, inner, "\n" ++ pad) ++ "}"
131 case V.JNil{}:
132 ""
133 case V.JCons{h, V.JNil{}}:
134 pretty.go(h, pad, "") ++ close
135 case V.JCons{h, t}:
136 pretty.go(h, pad, "") ++ ",\n" ++ pad ++ pretty.go(t, pad, close)
137 case V.JPair{k, v, V.JNil{}}:
138 quote(k) ++ ": " ++ pretty.go(v, pad, "") ++ close
139 case V.JPair{k, v, r}:
140 quote(k) ++ ": " ++ pretty.go(v, pad, "") ++ ",\n" ++ pad ++ pretty.go(r, pad, close)
141 case V.JBind{src, +n, v, V.JNil{}}:
142 quote(V.span.str(src, n, U32.is_zero(n))) ++ ": " ++ pretty.go(v, pad, "") ++ close
143 case V.JBind{src, +n, v, r}:
144 quote(V.span.str(src, n, U32.is_zero(n))) ++ ": " ++ pretty.go(v, pad, "") ++ ",\n" ++ pad ++
145 pretty.go(r, pad, close)
146
147# a value as JSON indented two spaces a level, one element or member a line
148def pretty(val: V.Json) -> String:
149 pretty.go(val, "", "")
150
151# a parse result: the value, or "<bad>"
152def maybe(mb: Maybe<&2, V.Json>) -> String:
153 match mb:
154 case None{}:
155 "<bad>"
156 case Some{j}:
157 print(j)