summaryrefslogtreecommitdiff
path: root/encode_string.go
diff options
context:
space:
mode:
Diffstat (limited to 'encode_string.go')
-rw-r--r--encode_string.go111
1 files changed, 111 insertions, 0 deletions
diff --git a/encode_string.go b/encode_string.go
new file mode 100644
index 0000000..c5cb442
--- /dev/null
+++ b/encode_string.go
@@ -0,0 +1,111 @@
+// Copyright (C) 2022-2023 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package lowmemjson
+
+import (
+ "io"
+ "unicode/utf8"
+
+ "git.lukeshu.com/go/lowmemjson/internal"
+)
+
+func writeStringUnicodeEscape(w io.Writer, c rune) (int, error) {
+ buf := [6]byte{
+ '\\',
+ 'u',
+ internal.Hex[(c>>12)&0xf],
+ internal.Hex[(c>>8)&0xf],
+ internal.Hex[(c>>4)&0xf],
+ internal.Hex[(c>>0)&0xf],
+ }
+ return w.Write(buf[:])
+}
+
+func writeStringShortEscape(w io.Writer, c rune) (int, error) {
+ var b byte
+ switch c {
+ case '"', '\\', '/':
+ b = byte(c)
+ case '\b':
+ b = 'b'
+ case '\f':
+ b = 'f'
+ case '\n':
+ b = 'n'
+ case '\r':
+ b = 'r'
+ case '\t':
+ b = 't'
+ default:
+ panic("should not happen")
+ }
+ buf := [2]byte{'\\', b}
+ return w.Write(buf[:])
+}
+
+func writeStringChar(w io.Writer, c rune, wasEscaped BackslashEscapeMode, escaper BackslashEscaper) (int, error) {
+ if escaper == nil {
+ escaper = EscapeDefault
+ }
+ switch escaper(c, wasEscaped) {
+ case BackslashEscapeNone:
+ switch {
+ case c < 0x0020: // override, gotta escape these
+ switch c {
+ case '\b', '\f', '\n', '\r', '\t': // short-escape if possible
+ return writeStringShortEscape(w, c)
+ default:
+ return writeStringUnicodeEscape(w, c)
+ }
+ case c == '"' || c == '\\': // override, gotta escape these
+ return writeStringShortEscape(w, c)
+ default: // obey
+ return writeRune(w, c)
+ }
+ case BackslashEscapeShort:
+ switch c {
+ case '"', '\\', '/', '\b', '\f', '\n', '\r', '\t': // obey
+ return writeStringShortEscape(w, c)
+ default: // override, can't short-escape these
+ return writeRune(w, c)
+ }
+ case BackslashEscapeUnicode:
+ switch {
+ case c > 0xFFFF: // override, can't escape these (TODO: unless we use UTF-16 surrogates?)
+ return writeRune(w, c)
+ default: // obey
+ return writeStringUnicodeEscape(w, c)
+ }
+ default:
+ panic("escaper returned an invalid escape mode")
+ }
+}
+
+func encodeStringFromString(w io.Writer, escaper BackslashEscaper, str string) {
+ encodeWriteByte(w, '"')
+ for _, c := range str {
+ if _, err := writeStringChar(w, c, BackslashEscapeNone, escaper); err != nil {
+ panic(encodeError{err})
+ }
+ }
+ encodeWriteByte(w, '"')
+}
+
+func encodeStringFromBytes(w io.Writer, escaper BackslashEscaper, str []byte) {
+ encodeWriteByte(w, '"')
+ for i := 0; i < len(str); {
+ c, size := utf8.DecodeRune(str[i:])
+ if _, err := writeStringChar(w, c, BackslashEscapeNone, escaper); err != nil {
+ panic(encodeError{err})
+ }
+ i += size
+ }
+ encodeWriteByte(w, '"')
+}
+
+func init() {
+ internal.EncodeStringFromString = func(w io.Writer, s string) { encodeStringFromString(w, nil, s) }
+ internal.EncodeStringFromBytes = func(w io.Writer, s []byte) { encodeStringFromBytes(w, nil, s) }
+}