summaryrefslogtreecommitdiff
path: root/misc.go
diff options
context:
space:
mode:
Diffstat (limited to 'misc.go')
-rw-r--r--misc.go131
1 files changed, 131 insertions, 0 deletions
diff --git a/misc.go b/misc.go
new file mode 100644
index 0000000..132b177
--- /dev/null
+++ b/misc.go
@@ -0,0 +1,131 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package lowmemjson
+
+import (
+ "encoding/json"
+ "io"
+ "reflect"
+ "unicode/utf8"
+)
+
+const Tab = "\t"
+
+const hex = "0123456789abcdef"
+
+var (
+ numberType = reflect.TypeOf(json.Number(""))
+ byteType = reflect.TypeOf(byte(0))
+ byteSliceType = reflect.TypeOf(([]byte)(nil))
+)
+
+// generic I/O /////////////////////////////////////////////////////////////////
+
+func decodeRune[T interface{ []byte | string }](s T) (r rune, size int) {
+ iface := any(s)
+ if str, ok := iface.(string); ok {
+ return utf8.DecodeRuneInString(str)
+ } else {
+ return utf8.DecodeRune(iface.([]byte))
+ }
+}
+
+func writeByte(w io.Writer, c byte) error {
+ if br, ok := w.(interface{ WriteByte(byte) error }); ok {
+ return br.WriteByte(c)
+ }
+ var buf [1]byte
+ buf[0] = c
+ if _, err := w.Write(buf[:]); err != nil {
+ return err
+ }
+ return nil
+}
+
+func writeRune(w io.Writer, c rune) (int, error) {
+ if rw, ok := w.(interface{ WriteRune(rune) (int, error) }); ok {
+ return rw.WriteRune(c)
+ }
+ var buf [utf8.UTFMax]byte
+ n := utf8.EncodeRune(buf[:], c)
+ return w.Write(buf[:n])
+}
+
+// JSON string encoding ////////////////////////////////////////////////////////
+
+func UnicodeEscapeJSSafe(c rune, _ bool) bool {
+ // JSON is notionally a JS subset, but that's not actually
+ // true.
+ //
+ // http://timelessrepo.com/json-isnt-a-javascript-subset
+ switch c {
+ case '\u2028', '\u2029':
+ return true
+ default:
+ return false
+ }
+}
+
+func UnicodeEscapeHTMLSafe(c rune, wasEscaped bool) bool {
+ switch c {
+ case '&', '<', '>':
+ return true
+ default:
+ return UnicodeEscapeJSSafe(c, wasEscaped)
+ }
+}
+
+func UnicodeEscapeDefault(c rune, wasEscaped bool) bool {
+ switch c {
+ case '\b', '\f', utf8.RuneError:
+ return true
+ default:
+ return UnicodeEscapeHTMLSafe(c, wasEscaped)
+ }
+}
+
+func writeStringUnicodeEscape(w io.Writer, c rune) (int, error) {
+ buf := [6]byte{
+ '\\',
+ 'u',
+ hex[(c>>12)&0xf],
+ hex[(c>>8)&0xf],
+ hex[(c>>4)&0xf],
+ hex[(c>>0)&0xf],
+ }
+ return w.Write(buf[:])
+}
+func writeStringShortEscape(w io.Writer, c byte) (int, error) {
+ buf := [2]byte{'\\', c}
+ return w.Write(buf[:])
+}
+func writeStringChar(w io.Writer, c rune, wasEscaped bool, escaper func(rune, bool) bool) (int, error) {
+ if escaper == nil {
+ escaper = UnicodeEscapeDefault
+ }
+ switch {
+ case c <= 0xFFFF && escaper(c, wasEscaped):
+ return writeStringUnicodeEscape(w, c)
+ case c == '"' || c == '\\':
+ return writeStringShortEscape(w, byte(c))
+ case c < 0x0020:
+ switch c {
+ case '\b':
+ return writeStringShortEscape(w, 'b')
+ case '\f':
+ return writeStringShortEscape(w, 'f')
+ case '\n':
+ return writeStringShortEscape(w, 'n')
+ case '\r':
+ return writeStringShortEscape(w, 'r')
+ case '\t':
+ return writeStringShortEscape(w, 't')
+ default:
+ return writeStringUnicodeEscape(w, c)
+ }
+ default:
+ return writeRune(w, c)
+ }
+}