// Copyright (C) 2022-2023 Luke Shumaker // // 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 internal.AllWriter, 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 w.WriteRune(c) } case BackslashEscapeShort: switch c { case '"', '\\', '/', '\b', '\f', '\n', '\r', '\t': // obey return writeStringShortEscape(w, c) default: // override, can't short-escape these return w.WriteRune(c) } case BackslashEscapeUnicode: switch { case c > 0xFFFF: // override, can't escape these (TODO: unless we use UTF-16 surrogates?) return w.WriteRune(c) default: // obey return writeStringUnicodeEscape(w, c) } default: panic("escaper returned an invalid escape mode") } } func encodeStringFromString(w internal.AllWriter, escaper BackslashEscaper, str string) error { if err := w.WriteByte('"'); err != nil { return err } for _, c := range str { if _, err := writeStringChar(w, c, BackslashEscapeNone, escaper); err != nil { return err } } if err := w.WriteByte('"'); err != nil { return err } return nil } func encodeStringFromBytes(w internal.AllWriter, escaper BackslashEscaper, str []byte) error { if err := w.WriteByte('"'); err != nil { return err } for i := 0; i < len(str); { c, size := utf8.DecodeRune(str[i:]) if _, err := writeStringChar(w, c, BackslashEscapeNone, escaper); err != nil { return err } i += size } if err := w.WriteByte('"'); err != nil { return err } return nil } func init() { internal.EncodeStringFromString = func(w io.Writer, s string) { if err := encodeStringFromString(internal.NewAllWriter(w), nil, s); err != nil { panic(err) } } internal.EncodeStringFromBytes = func(w io.Writer, s []byte) { if err := encodeStringFromBytes(internal.NewAllWriter(w), nil, s); err != nil { panic(err) } } }