gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

encode.go (7257B)


      1 // Copyright 2018 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package json
      6 
      7 import (
      8 	"math"
      9 	"math/bits"
     10 	"strconv"
     11 	"strings"
     12 	"unicode/utf8"
     13 
     14 	"google.golang.org/protobuf/internal/detrand"
     15 	"google.golang.org/protobuf/internal/errors"
     16 )
     17 
     18 // kind represents an encoding type.
     19 type kind uint8
     20 
     21 const (
     22 	_ kind = (1 << iota) / 2
     23 	name
     24 	scalar
     25 	objectOpen
     26 	objectClose
     27 	arrayOpen
     28 	arrayClose
     29 )
     30 
     31 // Encoder provides methods to write out JSON constructs and values. The user is
     32 // responsible for producing valid sequences of JSON constructs and values.
     33 type Encoder struct {
     34 	indent   string
     35 	lastKind kind
     36 	indents  []byte
     37 	out      []byte
     38 }
     39 
     40 // NewEncoder returns an Encoder.
     41 //
     42 // If indent is a non-empty string, it causes every entry for an Array or Object
     43 // to be preceded by the indent and trailed by a newline.
     44 func NewEncoder(indent string) (*Encoder, error) {
     45 	e := &Encoder{}
     46 	if len(indent) > 0 {
     47 		if strings.Trim(indent, " \t") != "" {
     48 			return nil, errors.New("indent may only be composed of space or tab characters")
     49 		}
     50 		e.indent = indent
     51 	}
     52 	return e, nil
     53 }
     54 
     55 // Bytes returns the content of the written bytes.
     56 func (e *Encoder) Bytes() []byte {
     57 	return e.out
     58 }
     59 
     60 // WriteNull writes out the null value.
     61 func (e *Encoder) WriteNull() {
     62 	e.prepareNext(scalar)
     63 	e.out = append(e.out, "null"...)
     64 }
     65 
     66 // WriteBool writes out the given boolean value.
     67 func (e *Encoder) WriteBool(b bool) {
     68 	e.prepareNext(scalar)
     69 	if b {
     70 		e.out = append(e.out, "true"...)
     71 	} else {
     72 		e.out = append(e.out, "false"...)
     73 	}
     74 }
     75 
     76 // WriteString writes out the given string in JSON string value. Returns error
     77 // if input string contains invalid UTF-8.
     78 func (e *Encoder) WriteString(s string) error {
     79 	e.prepareNext(scalar)
     80 	var err error
     81 	if e.out, err = appendString(e.out, s); err != nil {
     82 		return err
     83 	}
     84 	return nil
     85 }
     86 
     87 // Sentinel error used for indicating invalid UTF-8.
     88 var errInvalidUTF8 = errors.New("invalid UTF-8")
     89 
     90 func appendString(out []byte, in string) ([]byte, error) {
     91 	out = append(out, '"')
     92 	i := indexNeedEscapeInString(in)
     93 	in, out = in[i:], append(out, in[:i]...)
     94 	for len(in) > 0 {
     95 		switch r, n := utf8.DecodeRuneInString(in); {
     96 		case r == utf8.RuneError && n == 1:
     97 			return out, errInvalidUTF8
     98 		case r < ' ' || r == '"' || r == '\\':
     99 			out = append(out, '\\')
    100 			switch r {
    101 			case '"', '\\':
    102 				out = append(out, byte(r))
    103 			case '\b':
    104 				out = append(out, 'b')
    105 			case '\f':
    106 				out = append(out, 'f')
    107 			case '\n':
    108 				out = append(out, 'n')
    109 			case '\r':
    110 				out = append(out, 'r')
    111 			case '\t':
    112 				out = append(out, 't')
    113 			default:
    114 				out = append(out, 'u')
    115 				out = append(out, "0000"[1+(bits.Len32(uint32(r))-1)/4:]...)
    116 				out = strconv.AppendUint(out, uint64(r), 16)
    117 			}
    118 			in = in[n:]
    119 		default:
    120 			i := indexNeedEscapeInString(in[n:])
    121 			in, out = in[n+i:], append(out, in[:n+i]...)
    122 		}
    123 	}
    124 	out = append(out, '"')
    125 	return out, nil
    126 }
    127 
    128 // indexNeedEscapeInString returns the index of the character that needs
    129 // escaping. If no characters need escaping, this returns the input length.
    130 func indexNeedEscapeInString(s string) int {
    131 	for i, r := range s {
    132 		if r < ' ' || r == '\\' || r == '"' || r == utf8.RuneError {
    133 			return i
    134 		}
    135 	}
    136 	return len(s)
    137 }
    138 
    139 // WriteFloat writes out the given float and bitSize in JSON number value.
    140 func (e *Encoder) WriteFloat(n float64, bitSize int) {
    141 	e.prepareNext(scalar)
    142 	e.out = appendFloat(e.out, n, bitSize)
    143 }
    144 
    145 // appendFloat formats given float in bitSize, and appends to the given []byte.
    146 func appendFloat(out []byte, n float64, bitSize int) []byte {
    147 	switch {
    148 	case math.IsNaN(n):
    149 		return append(out, `"NaN"`...)
    150 	case math.IsInf(n, +1):
    151 		return append(out, `"Infinity"`...)
    152 	case math.IsInf(n, -1):
    153 		return append(out, `"-Infinity"`...)
    154 	}
    155 
    156 	// JSON number formatting logic based on encoding/json.
    157 	// See floatEncoder.encode for reference.
    158 	fmt := byte('f')
    159 	if abs := math.Abs(n); abs != 0 {
    160 		if bitSize == 64 && (abs < 1e-6 || abs >= 1e21) ||
    161 			bitSize == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) {
    162 			fmt = 'e'
    163 		}
    164 	}
    165 	out = strconv.AppendFloat(out, n, fmt, -1, bitSize)
    166 	if fmt == 'e' {
    167 		n := len(out)
    168 		if n >= 4 && out[n-4] == 'e' && out[n-3] == '-' && out[n-2] == '0' {
    169 			out[n-2] = out[n-1]
    170 			out = out[:n-1]
    171 		}
    172 	}
    173 	return out
    174 }
    175 
    176 // WriteInt writes out the given signed integer in JSON number value.
    177 func (e *Encoder) WriteInt(n int64) {
    178 	e.prepareNext(scalar)
    179 	e.out = append(e.out, strconv.FormatInt(n, 10)...)
    180 }
    181 
    182 // WriteUint writes out the given unsigned integer in JSON number value.
    183 func (e *Encoder) WriteUint(n uint64) {
    184 	e.prepareNext(scalar)
    185 	e.out = append(e.out, strconv.FormatUint(n, 10)...)
    186 }
    187 
    188 // StartObject writes out the '{' symbol.
    189 func (e *Encoder) StartObject() {
    190 	e.prepareNext(objectOpen)
    191 	e.out = append(e.out, '{')
    192 }
    193 
    194 // EndObject writes out the '}' symbol.
    195 func (e *Encoder) EndObject() {
    196 	e.prepareNext(objectClose)
    197 	e.out = append(e.out, '}')
    198 }
    199 
    200 // WriteName writes out the given string in JSON string value and the name
    201 // separator ':'. Returns error if input string contains invalid UTF-8, which
    202 // should not be likely as protobuf field names should be valid.
    203 func (e *Encoder) WriteName(s string) error {
    204 	e.prepareNext(name)
    205 	var err error
    206 	// Append to output regardless of error.
    207 	e.out, err = appendString(e.out, s)
    208 	e.out = append(e.out, ':')
    209 	return err
    210 }
    211 
    212 // StartArray writes out the '[' symbol.
    213 func (e *Encoder) StartArray() {
    214 	e.prepareNext(arrayOpen)
    215 	e.out = append(e.out, '[')
    216 }
    217 
    218 // EndArray writes out the ']' symbol.
    219 func (e *Encoder) EndArray() {
    220 	e.prepareNext(arrayClose)
    221 	e.out = append(e.out, ']')
    222 }
    223 
    224 // prepareNext adds possible comma and indentation for the next value based
    225 // on last type and indent option. It also updates lastKind to next.
    226 func (e *Encoder) prepareNext(next kind) {
    227 	defer func() {
    228 		// Set lastKind to next.
    229 		e.lastKind = next
    230 	}()
    231 
    232 	if len(e.indent) == 0 {
    233 		// Need to add comma on the following condition.
    234 		if e.lastKind&(scalar|objectClose|arrayClose) != 0 &&
    235 			next&(name|scalar|objectOpen|arrayOpen) != 0 {
    236 			e.out = append(e.out, ',')
    237 			// For single-line output, add a random extra space after each
    238 			// comma to make output unstable.
    239 			if detrand.Bool() {
    240 				e.out = append(e.out, ' ')
    241 			}
    242 		}
    243 		return
    244 	}
    245 
    246 	switch {
    247 	case e.lastKind&(objectOpen|arrayOpen) != 0:
    248 		// If next type is NOT closing, add indent and newline.
    249 		if next&(objectClose|arrayClose) == 0 {
    250 			e.indents = append(e.indents, e.indent...)
    251 			e.out = append(e.out, '\n')
    252 			e.out = append(e.out, e.indents...)
    253 		}
    254 
    255 	case e.lastKind&(scalar|objectClose|arrayClose) != 0:
    256 		switch {
    257 		// If next type is either a value or name, add comma and newline.
    258 		case next&(name|scalar|objectOpen|arrayOpen) != 0:
    259 			e.out = append(e.out, ',', '\n')
    260 
    261 		// If next type is a closing object or array, adjust indentation.
    262 		case next&(objectClose|arrayClose) != 0:
    263 			e.indents = e.indents[:len(e.indents)-len(e.indent)]
    264 			e.out = append(e.out, '\n')
    265 		}
    266 		e.out = append(e.out, e.indents...)
    267 
    268 	case e.lastKind&name != 0:
    269 		e.out = append(e.out, ' ')
    270 		// For multi-line output, add a random extra space after key: to make
    271 		// output unstable.
    272 		if detrand.Bool() {
    273 			e.out = append(e.out, ' ')
    274 		}
    275 	}
    276 }