1 # src/print: a value as compact or indented JSON text. 2 import Base 3 import ./value.bend as V 4 5 def 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 12 def escape.nib(+nn: U32) -> Char: 13 escape.nib.go(nn, U32.is_lt(nn, 10)) 14 15 def escape.u(+cp: U32) -> String: 16 "\\u00" ++ String.from_list([escape.nib(U32.div(cp, 16)), escape.nib(U32.mod(cp, 16))]) 17 18 def 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 25 def 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 30 def 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 34 def 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 46 def 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 54 def 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 59 def 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 67 def quote(txt: String) -> String: 68 "\"" ++ escape(String.to_list(txt)) ++ "\"" 69 70 # a value as compact JSON 71 def 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 107 def 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 148 def pretty(val: V.Json) -> String: 149 pretty.go(val, "", "") 150 151 # a parse result: the value, or "" 152 def maybe(mb: Maybe<&2, V.Json>) -> String: 153 match mb: 154 case None{}: 155 "" 156 case Some{j}: 157 print(j)