github.com/grbit/go-json@v0.11.0/internal/encoder/compact.go (about)

     1  package encoder
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strconv"
     7  	"unsafe"
     8  
     9  	"github.com/grbit/go-json/internal/errors"
    10  )
    11  
    12  var (
    13  	isWhiteSpace = [256]bool{
    14  		' ':  true,
    15  		'\n': true,
    16  		'\t': true,
    17  		'\r': true,
    18  	}
    19  	isHTMLEscapeChar = [256]bool{
    20  		'<': true,
    21  		'>': true,
    22  		'&': true,
    23  	}
    24  	nul = byte('\000')
    25  )
    26  
    27  func Compact(buf *bytes.Buffer, src []byte, escape bool) error {
    28  	if len(src) == 0 {
    29  		return errors.ErrUnexpectedEndOfJSON("", 0)
    30  	}
    31  	buf.Grow(len(src))
    32  	dst := buf.Bytes()
    33  
    34  	ctx := TakeRuntimeContext()
    35  	ctxBuf := ctx.Buf[:0]
    36  	ctxBuf = append(append(ctxBuf, src...), nul)
    37  	ctx.Buf = ctxBuf
    38  
    39  	if err := compactAndWrite(buf, dst, ctxBuf, escape); err != nil {
    40  		ReleaseRuntimeContext(ctx)
    41  		return err
    42  	}
    43  	ReleaseRuntimeContext(ctx)
    44  	return nil
    45  }
    46  
    47  func compactAndWrite(buf *bytes.Buffer, dst []byte, src []byte, escape bool) error {
    48  	dst, err := compact(dst, src, escape)
    49  	if err != nil {
    50  		return err
    51  	}
    52  	if _, err := buf.Write(dst); err != nil {
    53  		return err
    54  	}
    55  	return nil
    56  }
    57  
    58  func compact(dst, src []byte, escape bool) ([]byte, error) {
    59  	buf, cursor, err := compactValue(dst, src, 0, escape)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	if err := validateEndBuf(src, cursor); err != nil {
    64  		return nil, err
    65  	}
    66  	return buf, nil
    67  }
    68  
    69  func validateEndBuf(src []byte, cursor int64) error {
    70  	for {
    71  		switch src[cursor] {
    72  		case ' ', '\t', '\n', '\r':
    73  			cursor++
    74  			continue
    75  		case nul:
    76  			return nil
    77  		}
    78  		return errors.ErrSyntax(
    79  			fmt.Sprintf("invalid character '%c' after top-level value", src[cursor]),
    80  			cursor+1,
    81  		)
    82  	}
    83  }
    84  
    85  func skipWhiteSpace(buf []byte, cursor int64) int64 {
    86  LOOP:
    87  	if isWhiteSpace[buf[cursor]] {
    88  		cursor++
    89  		goto LOOP
    90  	}
    91  	return cursor
    92  }
    93  
    94  func compactValue(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
    95  	for {
    96  		switch src[cursor] {
    97  		case ' ', '\t', '\n', '\r':
    98  			cursor++
    99  			continue
   100  		case '{':
   101  			return compactObject(dst, src, cursor, escape)
   102  		case '}':
   103  			return nil, 0, errors.ErrSyntax("unexpected character '}'", cursor)
   104  		case '[':
   105  			return compactArray(dst, src, cursor, escape)
   106  		case ']':
   107  			return nil, 0, errors.ErrSyntax("unexpected character ']'", cursor)
   108  		case '"':
   109  			return compactString(dst, src, cursor, escape)
   110  		case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
   111  			return compactNumber(dst, src, cursor)
   112  		case 't':
   113  			return compactTrue(dst, src, cursor)
   114  		case 'f':
   115  			return compactFalse(dst, src, cursor)
   116  		case 'n':
   117  			return compactNull(dst, src, cursor)
   118  		default:
   119  			return nil, 0, errors.ErrSyntax(fmt.Sprintf("unexpected character '%c'", src[cursor]), cursor)
   120  		}
   121  	}
   122  }
   123  
   124  func compactObject(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
   125  	if src[cursor] == '{' {
   126  		dst = append(dst, '{')
   127  	} else {
   128  		return nil, 0, errors.ErrExpected("expected { character for object value", cursor)
   129  	}
   130  	cursor = skipWhiteSpace(src, cursor+1)
   131  	if src[cursor] == '}' {
   132  		dst = append(dst, '}')
   133  		return dst, cursor + 1, nil
   134  	}
   135  	var err error
   136  	for {
   137  		cursor = skipWhiteSpace(src, cursor)
   138  		dst, cursor, err = compactString(dst, src, cursor, escape)
   139  		if err != nil {
   140  			return nil, 0, err
   141  		}
   142  		cursor = skipWhiteSpace(src, cursor)
   143  		if src[cursor] != ':' {
   144  			return nil, 0, errors.ErrExpected("colon after object key", cursor)
   145  		}
   146  		dst = append(dst, ':')
   147  		dst, cursor, err = compactValue(dst, src, cursor+1, escape)
   148  		if err != nil {
   149  			return nil, 0, err
   150  		}
   151  		cursor = skipWhiteSpace(src, cursor)
   152  		switch src[cursor] {
   153  		case '}':
   154  			dst = append(dst, '}')
   155  			cursor++
   156  			return dst, cursor, nil
   157  		case ',':
   158  			dst = append(dst, ',')
   159  		default:
   160  			return nil, 0, errors.ErrExpected("comma after object value", cursor)
   161  		}
   162  		cursor++
   163  	}
   164  }
   165  
   166  func compactArray(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
   167  	if src[cursor] == '[' {
   168  		dst = append(dst, '[')
   169  	} else {
   170  		return nil, 0, errors.ErrExpected("expected [ character for array value", cursor)
   171  	}
   172  	cursor = skipWhiteSpace(src, cursor+1)
   173  	if src[cursor] == ']' {
   174  		dst = append(dst, ']')
   175  		return dst, cursor + 1, nil
   176  	}
   177  	var err error
   178  	for {
   179  		dst, cursor, err = compactValue(dst, src, cursor, escape)
   180  		if err != nil {
   181  			return nil, 0, err
   182  		}
   183  		cursor = skipWhiteSpace(src, cursor)
   184  		switch src[cursor] {
   185  		case ']':
   186  			dst = append(dst, ']')
   187  			cursor++
   188  			return dst, cursor, nil
   189  		case ',':
   190  			dst = append(dst, ',')
   191  		default:
   192  			return nil, 0, errors.ErrExpected("comma after array value", cursor)
   193  		}
   194  		cursor++
   195  	}
   196  }
   197  
   198  func compactString(dst, src []byte, cursor int64, escape bool) ([]byte, int64, error) {
   199  	if src[cursor] != '"' {
   200  		return nil, 0, errors.ErrInvalidCharacter(src[cursor], "string", cursor)
   201  	}
   202  	start := cursor
   203  	for {
   204  		cursor++
   205  		c := src[cursor]
   206  		if escape {
   207  			if isHTMLEscapeChar[c] {
   208  				dst = append(dst, src[start:cursor]...)
   209  				dst = append(dst, `\u00`...)
   210  				dst = append(dst, hex[c>>4], hex[c&0xF])
   211  				start = cursor + 1
   212  			} else if c == 0xE2 && cursor+2 < int64(len(src)) && src[cursor+1] == 0x80 && src[cursor+2]&^1 == 0xA8 {
   213  				dst = append(dst, src[start:cursor]...)
   214  				dst = append(dst, `\u202`...)
   215  				dst = append(dst, hex[src[cursor+2]&0xF])
   216  				cursor += 2
   217  				start = cursor + 3
   218  			}
   219  		}
   220  		switch c {
   221  		case '\\':
   222  			cursor++
   223  			if src[cursor] == nul {
   224  				return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src)))
   225  			}
   226  		case '"':
   227  			cursor++
   228  			return append(dst, src[start:cursor]...), cursor, nil
   229  		case nul:
   230  			return nil, 0, errors.ErrUnexpectedEndOfJSON("string", int64(len(src)))
   231  		}
   232  	}
   233  }
   234  
   235  func compactNumber(dst, src []byte, cursor int64) ([]byte, int64, error) {
   236  	start := cursor
   237  	for {
   238  		cursor++
   239  		if floatTable[src[cursor]] {
   240  			continue
   241  		}
   242  		break
   243  	}
   244  	num := src[start:cursor]
   245  	if _, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&num)), 64); err != nil {
   246  		return nil, 0, err
   247  	}
   248  	dst = append(dst, num...)
   249  	return dst, cursor, nil
   250  }
   251  
   252  func compactTrue(dst, src []byte, cursor int64) ([]byte, int64, error) {
   253  	if cursor+3 >= int64(len(src)) {
   254  		return nil, 0, errors.ErrUnexpectedEndOfJSON("true", cursor)
   255  	}
   256  	if !bytes.Equal(src[cursor:cursor+4], []byte(`true`)) {
   257  		return nil, 0, errors.ErrInvalidCharacter(src[cursor], "true", cursor)
   258  	}
   259  	dst = append(dst, "true"...)
   260  	cursor += 4
   261  	return dst, cursor, nil
   262  }
   263  
   264  func compactFalse(dst, src []byte, cursor int64) ([]byte, int64, error) {
   265  	if cursor+4 >= int64(len(src)) {
   266  		return nil, 0, errors.ErrUnexpectedEndOfJSON("false", cursor)
   267  	}
   268  	if !bytes.Equal(src[cursor:cursor+5], []byte(`false`)) {
   269  		return nil, 0, errors.ErrInvalidCharacter(src[cursor], "false", cursor)
   270  	}
   271  	dst = append(dst, "false"...)
   272  	cursor += 5
   273  	return dst, cursor, nil
   274  }
   275  
   276  func compactNull(dst, src []byte, cursor int64) ([]byte, int64, error) {
   277  	if cursor+3 >= int64(len(src)) {
   278  		return nil, 0, errors.ErrUnexpectedEndOfJSON("null", cursor)
   279  	}
   280  	if !bytes.Equal(src[cursor:cursor+4], []byte(`null`)) {
   281  		return nil, 0, errors.ErrInvalidCharacter(src[cursor], "null", cursor)
   282  	}
   283  	dst = append(dst, "null"...)
   284  	cursor += 4
   285  	return dst, cursor, nil
   286  }