src/print.bend

emerging-ezjson@1.1.0.0 · 157 lines · 0 definitions

1# src/print: a value as compact or indented JSON text.
2import Base
3import emerging-ezjson@1.1.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)