Optimize streaming decoder

This commit is contained in:
Masaaki Goshima 2020-07-31 17:10:03 +09:00
parent 961b6a202e
commit 80acd42b80
13 changed files with 297 additions and 190 deletions

View File

@ -52,10 +52,11 @@ func Benchmark_Decode_SmallStruct_GoJayUnsafe(b *testing.B) {
func Benchmark_Decode_SmallStruct_GoJsonDecode(b *testing.B) { func Benchmark_Decode_SmallStruct_GoJsonDecode(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
reader := bytes.NewReader(SmallFixture)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
result := SmallPayload{} result := SmallPayload{}
buf := bytes.NewBuffer(SmallFixture) reader.Reset(SmallFixture)
if err := gojson.NewDecoder(buf).Decode(&result); err != nil { if err := gojson.NewDecoder(reader).Decode(&result); err != nil {
b.Fatal(err) b.Fatal(err)
} }
} }

View File

@ -49,6 +49,10 @@ func init() {
cachedDecoder = decoderMap{} cachedDecoder = decoderMap{}
} }
const (
nul = '\000'
)
// NewDecoder returns a new decoder that reads from r. // NewDecoder returns a new decoder that reads from r.
// //
// The decoder introduces its own buffering and may // The decoder introduces its own buffering and may
@ -115,11 +119,16 @@ func (d *Decoder) prepareForDecode() error {
for { for {
switch s.char() { switch s.char() {
case ' ', '\t', '\r', '\n': case ' ', '\t', '\r', '\n':
s.progress() s.cursor++
continue continue
case ',', ':': case ',', ':':
s.progress() s.cursor++
return nil return nil
case nul:
if s.read() {
continue
}
return io.EOF
} }
break break
} }
@ -167,10 +176,14 @@ func (d *Decoder) More() bool {
for { for {
switch s.char() { switch s.char() {
case ' ', '\n', '\r', '\t': case ' ', '\n', '\r', '\t':
if s.progress() { s.cursor++
continue
case '}', ']':
return false
case nul:
if s.read() {
continue continue
} }
case '}', ']':
return false return false
} }
break break
@ -184,16 +197,12 @@ func (d *Decoder) Token() (Token, error) {
c := s.char() c := s.char()
switch c { switch c {
case ' ', '\n', '\r', '\t': case ' ', '\n', '\r', '\t':
if s.progress() { s.cursor++
continue
}
case '{', '[', ']', '}': case '{', '[', ']', '}':
s.progress() s.cursor++
return Delim(c), nil return Delim(c), nil
case ',', ':': case ',', ':':
if s.progress() { s.cursor++
continue
}
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
bytes := floatBytes(s) bytes := floatBytes(s)
s := *(*string)(unsafe.Pointer(&bytes)) s := *(*string)(unsafe.Pointer(&bytes))
@ -223,7 +232,10 @@ func (d *Decoder) Token() (Token, error) {
return nil, err return nil, err
} }
return nil, nil return nil, nil
case '\000': case nul:
if s.read() {
continue
}
return nil, io.EOF return nil, io.EOF
default: default:
return nil, errInvalidCharacter(s.char(), "token", s.totalOffset()) return nil, errInvalidCharacter(s.char(), "token", s.totalOffset())

View File

@ -23,27 +23,38 @@ func (d *arrayDecoder) decodeStream(s *stream, p uintptr) error {
case '[': case '[':
idx := 0 idx := 0
for { for {
s.progress() s.cursor++
if err := d.valueDecoder.decodeStream(s, p+uintptr(idx)*d.size); err != nil { if err := d.valueDecoder.decodeStream(s, p+uintptr(idx)*d.size); err != nil {
return err return err
} }
s.skipWhiteSpace() s.skipWhiteSpace()
switch s.char() { switch s.char() {
case ']': case ']':
s.progress() s.cursor++
return nil return nil
case ',': case ',':
idx++ idx++
case nul:
if s.read() {
continue
}
goto ERROR
default: default:
return errInvalidCharacter(s.char(), "array", s.offset) goto ERROR
} }
} }
case nul:
if s.read() {
continue
}
goto ERROR
default: default:
return errUnexpectedEndOfJSON("array", s.offset) goto ERROR
} }
s.progress() s.cursor++
} }
return errUnexpectedEndOfJSON("array", s.offset) ERROR:
return errUnexpectedEndOfJSON("array", s.totalOffset())
} }
func (d *arrayDecoder) decode(buf []byte, cursor int64, p uintptr) (int64, error) { func (d *arrayDecoder) decode(buf []byte, cursor int64, p uintptr) (int64, error) {

View File

@ -11,45 +11,56 @@ func newBoolDecoder() *boolDecoder {
} }
func trueBytes(s *stream) error { func trueBytes(s *stream) error {
s.progress() if s.cursor+3 >= s.length {
if !s.read() {
return errInvalidCharacter(s.char(), "bool(true)", s.totalOffset())
}
}
s.cursor++
if s.char() != 'r' { if s.char() != 'r' {
return errInvalidCharacter(s.char(), "bool(true)", s.totalOffset()) return errInvalidCharacter(s.char(), "bool(true)", s.totalOffset())
} }
s.progress() s.cursor++
if s.char() != 'u' { if s.char() != 'u' {
return errInvalidCharacter(s.char(), "bool(true)", s.totalOffset()) return errInvalidCharacter(s.char(), "bool(true)", s.totalOffset())
} }
s.progress() s.cursor++
if s.char() != 'e' { if s.char() != 'e' {
return errInvalidCharacter(s.char(), "bool(true)", s.totalOffset()) return errInvalidCharacter(s.char(), "bool(true)", s.totalOffset())
} }
s.progress() s.cursor++
return nil return nil
} }
func falseBytes(s *stream) error { func falseBytes(s *stream) error {
s.progress() if s.cursor+4 >= s.length {
if s.read() {
return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset())
}
}
s.cursor++
if s.char() != 'a' { if s.char() != 'a' {
return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset()) return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset())
} }
s.progress() s.cursor++
if s.char() != 'l' { if s.char() != 'l' {
return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset()) return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset())
} }
s.progress() s.cursor++
if s.char() != 's' { if s.char() != 's' {
return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset()) return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset())
} }
s.progress() s.cursor++
if s.char() != 'e' { if s.char() != 'e' {
return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset()) return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset())
} }
s.progress() s.cursor++
return nil return nil
} }
func (d *boolDecoder) decodeStream(s *stream, p uintptr) error { func (d *boolDecoder) decodeStream(s *stream, p uintptr) error {
s.skipWhiteSpace() s.skipWhiteSpace()
for {
switch s.char() { switch s.char() {
case 't': case 't':
if err := trueBytes(s); err != nil { if err := trueBytes(s); err != nil {
@ -63,7 +74,15 @@ func (d *boolDecoder) decodeStream(s *stream, p uintptr) error {
} }
*(*bool)(unsafe.Pointer(p)) = false *(*bool)(unsafe.Pointer(p)) = false
return nil return nil
case nul:
if s.read() {
continue
} }
goto ERROR
}
break
}
ERROR:
return errUnexpectedEndOfJSON("bool", s.totalOffset()) return errUnexpectedEndOfJSON("bool", s.totalOffset())
} }

View File

@ -31,9 +31,14 @@ var floatTable = [256]bool{
func floatBytes(s *stream) []byte { func floatBytes(s *stream) []byte {
start := s.cursor start := s.cursor
for s.progress() { for {
s.cursor++
if floatTable[s.char()] { if floatTable[s.char()] {
continue continue
} else if s.char() == nul {
if s.read() {
continue
}
} }
break break
} }
@ -44,15 +49,21 @@ func (d *floatDecoder) decodeStreamByte(s *stream) ([]byte, error) {
for { for {
switch s.char() { switch s.char() {
case ' ', '\n', '\t', '\r': case ' ', '\n', '\t', '\r':
s.progress() s.cursor++
continue continue
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return floatBytes(s), nil return floatBytes(s), nil
case nul:
if s.read() {
continue
}
goto ERROR
default: default:
return nil, errUnexpectedEndOfJSON("float", s.offset) goto ERROR
} }
} }
return nil, errUnexpectedEndOfJSON("float", s.offset) ERROR:
return nil, errUnexpectedEndOfJSON("float", s.totalOffset())
} }
func (d *floatDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, error) { func (d *floatDecoder) decodeByte(buf []byte, cursor int64) ([]byte, int64, error) {

View File

@ -53,37 +53,53 @@ func (d *intDecoder) decodeStreamByte(s *stream) ([]byte, error) {
for { for {
switch s.char() { switch s.char() {
case ' ', '\n', '\t', '\r': case ' ', '\n', '\t', '\r':
s.progress() s.cursor++
continue continue
case '-': case '-':
start := s.cursor start := s.cursor
for s.progress() { for {
s.cursor++
if numTable[s.char()] { if numTable[s.char()] {
continue continue
} else if s.char() == nul {
if s.read() {
continue
}
} }
break break
} }
num := s.buf[start:s.cursor] num := s.buf[start:s.cursor]
s.reset() s.reset()
if len(num) < 2 { if len(num) < 2 {
return nil, errInvalidCharacter(s.char(), "number(integer)", s.totalOffset()) goto ERROR
} }
return num, nil return num, nil
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
start := s.cursor start := s.cursor
for s.progress() { for {
s.cursor++
if numTable[s.char()] { if numTable[s.char()] {
continue continue
} else if s.char() == nul {
if s.read() {
continue
}
} }
break break
} }
num := s.buf[start:s.cursor] num := s.buf[start:s.cursor]
s.reset() s.reset()
return num, nil return num, nil
case nul:
if s.read() {
continue
}
goto ERROR
default: default:
return nil, errInvalidCharacter(s.char(), "number(integer)", s.totalOffset()) goto ERROR
} }
} }
ERROR:
return nil, errUnexpectedEndOfJSON("number(integer)", s.totalOffset()) return nil, errUnexpectedEndOfJSON("number(integer)", s.totalOffset())
} }

View File

@ -22,6 +22,7 @@ var (
func (d *interfaceDecoder) decodeStream(s *stream, p uintptr) error { func (d *interfaceDecoder) decodeStream(s *stream, p uintptr) error {
s.skipWhiteSpace() s.skipWhiteSpace()
for {
switch s.char() { switch s.char() {
case '{': case '{':
var v map[interface{}]interface{} var v map[interface{}]interface{}
@ -48,75 +49,50 @@ func (d *interfaceDecoder) decodeStream(s *stream, p uintptr) error {
*(*interface{})(unsafe.Pointer(p)) = v *(*interface{})(unsafe.Pointer(p)) = v
}).decodeStream(s, p) }).decodeStream(s, p)
case '"': case '"':
s.progress() s.cursor++
start := s.cursor start := s.cursor
for { for {
switch s.char() { switch s.char() {
case '\\': case '\\':
s.progress() s.cursor++
case '"': case '"':
literal := s.buf[start:s.cursor] literal := s.buf[start:s.cursor]
s.progress() s.cursor++
*(*interface{})(unsafe.Pointer(p)) = *(*string)(unsafe.Pointer(&literal)) *(*interface{})(unsafe.Pointer(p)) = *(*string)(unsafe.Pointer(&literal))
return nil return nil
case '\000': case nul:
if s.read() {
continue
}
return errUnexpectedEndOfJSON("string", s.totalOffset()) return errUnexpectedEndOfJSON("string", s.totalOffset())
} }
s.progress() s.cursor++
} }
return errUnexpectedEndOfJSON("string", s.totalOffset()) return errUnexpectedEndOfJSON("string", s.totalOffset())
case 't': case 't':
s.progress() if err := trueBytes(s); err != nil {
if s.char() != 'r' { return err
return errInvalidCharacter(s.char(), "bool(true)", s.totalOffset())
} }
s.progress()
if s.char() != 'u' {
return errInvalidCharacter(s.char(), "bool(true)", s.totalOffset())
}
s.progress()
if s.char() != 'e' {
return errInvalidCharacter(s.char(), "bool(true)", s.totalOffset())
}
s.progress()
*(*interface{})(unsafe.Pointer(p)) = true *(*interface{})(unsafe.Pointer(p)) = true
return nil return nil
case 'f': case 'f':
s.progress() if err := falseBytes(s); err != nil {
if s.char() != 'a' { return err
return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset())
} }
s.progress()
if s.char() != 'l' {
return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset())
}
s.progress()
if s.char() != 's' {
return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset())
}
s.progress()
if s.char() != 'e' {
return errInvalidCharacter(s.char(), "bool(false)", s.totalOffset())
}
s.progress()
*(*interface{})(unsafe.Pointer(p)) = false *(*interface{})(unsafe.Pointer(p)) = false
return nil return nil
case 'n': case 'n':
s.progress() if err := nullBytes(s); err != nil {
if s.char() != 'u' { return err
return errInvalidCharacter(s.char(), "null", s.totalOffset())
} }
s.progress()
if s.char() != 'l' {
return errInvalidCharacter(s.char(), "null", s.totalOffset())
}
s.progress()
if s.char() != 'l' {
return errInvalidCharacter(s.char(), "null", s.totalOffset())
}
s.progress()
*(*interface{})(unsafe.Pointer(p)) = nil *(*interface{})(unsafe.Pointer(p)) = nil
return nil return nil
case nul:
if s.read() {
continue
}
}
break
} }
return errNotAtBeginningOfValue(s.totalOffset()) return errNotAtBeginningOfValue(s.totalOffset())
} }

View File

@ -51,16 +51,20 @@ func (d *mapDecoder) decodeStream(s *stream, p uintptr) error {
return errExpected("{ character for map value", s.totalOffset()) return errExpected("{ character for map value", s.totalOffset())
} }
mapValue := makemap(d.mapType, 0) mapValue := makemap(d.mapType, 0)
for s.progress() { for {
s.cursor++
var key interface{} var key interface{}
if err := d.setKeyStream(s, &key); err != nil { if err := d.setKeyStream(s, &key); err != nil {
return err return err
} }
s.skipWhiteSpace() s.skipWhiteSpace()
if s.char() == nul {
s.read()
}
if s.char() != ':' { if s.char() != ':' {
return errExpected("colon after object key", s.totalOffset()) return errExpected("colon after object key", s.totalOffset())
} }
s.progress() s.cursor++
if s.end() { if s.end() {
return errUnexpectedEndOfJSON("map", s.totalOffset()) return errUnexpectedEndOfJSON("map", s.totalOffset())
} }
@ -70,6 +74,9 @@ func (d *mapDecoder) decodeStream(s *stream, p uintptr) error {
} }
mapassign(d.mapType, mapValue, unsafe.Pointer(&key), unsafe.Pointer(&value)) mapassign(d.mapType, mapValue, unsafe.Pointer(&key), unsafe.Pointer(&value))
s.skipWhiteSpace() s.skipWhiteSpace()
if s.char() == nul {
s.read()
}
if s.char() == '}' { if s.char() == '}' {
*(*unsafe.Pointer)(unsafe.Pointer(p)) = mapValue *(*unsafe.Pointer)(unsafe.Pointer(p)) = mapValue
return nil return nil

View File

@ -51,14 +51,15 @@ func (d *sliceDecoder) decodeStream(s *stream, p uintptr) error {
for { for {
switch s.char() { switch s.char() {
case ' ', '\n', '\t', '\r': case ' ', '\n', '\t', '\r':
s.progress() s.cursor++
continue continue
case '[': case '[':
idx := 0 idx := 0
slice := d.newSlice() slice := d.newSlice()
cap := slice.Cap cap := slice.Cap
data := slice.Data data := slice.Data
for s.progress() { for {
s.cursor++
if cap <= idx { if cap <= idx {
src := reflect.SliceHeader{Data: data, Len: idx, Cap: cap} src := reflect.SliceHeader{Data: data, Len: idx, Cap: cap}
cap *= 2 cap *= 2
@ -70,6 +71,7 @@ func (d *sliceDecoder) decodeStream(s *stream, p uintptr) error {
return err return err
} }
s.skipWhiteSpace() s.skipWhiteSpace()
RETRY:
switch s.char() { switch s.char() {
case ']': case ']':
slice.Cap = cap slice.Cap = cap
@ -84,20 +86,34 @@ func (d *sliceDecoder) decodeStream(s *stream, p uintptr) error {
copySlice(d.elemType, dst, *slice) copySlice(d.elemType, dst, *slice)
*(*reflect.SliceHeader)(unsafe.Pointer(p)) = dst *(*reflect.SliceHeader)(unsafe.Pointer(p)) = dst
d.releaseSlice(slice) d.releaseSlice(slice)
s.progress() s.cursor++
return nil return nil
case ',': case ',':
idx++ idx++
continue continue
case nul:
if s.read() {
goto RETRY
}
slice.Cap = cap
slice.Data = data
d.releaseSlice(slice)
goto ERROR
default: default:
slice.Cap = cap slice.Cap = cap
slice.Data = data slice.Data = data
d.releaseSlice(slice) d.releaseSlice(slice)
return errInvalidCharacter(s.char(), "slice", s.totalOffset()) goto ERROR
} }
} }
case nul:
if s.read() {
continue
}
goto ERROR
} }
} }
ERROR:
return errUnexpectedEndOfJSON("slice", s.totalOffset()) return errUnexpectedEndOfJSON("slice", s.totalOffset())
} }

View File

@ -38,15 +38,6 @@ func (s *stream) end() bool {
return s.allRead && s.length <= s.cursor return s.allRead && s.length <= s.cursor
} }
func (s *stream) progress() bool {
if s.cursor < s.length-1 || s.read() {
s.cursor++
return true
}
s.cursor = s.length
return false
}
func (s *stream) progressN(n int64) bool { func (s *stream) progressN(n int64) bool {
if s.cursor+n < s.length-1 || s.read() { if s.cursor+n < s.length-1 || s.read() {
s.cursor += n s.cursor += n
@ -94,8 +85,12 @@ func (s *stream) read() bool {
func (s *stream) skipWhiteSpace() { func (s *stream) skipWhiteSpace() {
LOOP: LOOP:
if isWhiteSpace[s.char()] { if isWhiteSpace[s.char()] {
s.progress() s.cursor++
goto LOOP goto LOOP
} else if s.char() == nul {
if s.read() {
goto LOOP
}
} }
} }
@ -105,8 +100,11 @@ func (s *stream) skipValue() error {
bracketCount := 0 bracketCount := 0
for { for {
switch s.char() { switch s.char() {
case '\000': case nul:
return errUnexpectedEndOfJSON("value of object", s.offset) if s.read() {
continue
}
return errUnexpectedEndOfJSON("value of object", s.totalOffset())
case '{': case '{':
braceCount++ braceCount++
case '[': case '[':
@ -123,7 +121,13 @@ func (s *stream) skipValue() error {
return nil return nil
} }
case '"': case '"':
for s.progress() { for {
s.cursor++
if s.char() == nul {
if !s.read() {
return errUnexpectedEndOfJSON("value of string", s.totalOffset())
}
}
if s.char() != '"' { if s.char() != '"' {
continue continue
} }
@ -131,16 +135,20 @@ func (s *stream) skipValue() error {
continue continue
} }
if bracketCount == 0 && braceCount == 0 { if bracketCount == 0 && braceCount == 0 {
s.progress() s.cursor++
return nil return nil
} }
break break
} }
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
for s.progress() { for {
tk := int(s.char()) s.cursor++
if (int('0') <= tk && tk <= int('9')) || tk == '.' || tk == 'e' || tk == 'E' { if floatTable[s.char()] {
continue continue
} else if s.char() == nul {
if s.read() {
continue
}
} }
break break
} }
@ -149,7 +157,7 @@ func (s *stream) skipValue() error {
} }
continue continue
} }
s.progress() s.cursor++
} }
return errUnexpectedEndOfJSON("value of object", s.offset) return errUnexpectedEndOfJSON("value of object", s.offset)
} }

View File

@ -31,40 +31,48 @@ func (d *stringDecoder) decode(buf []byte, cursor int64, p uintptr) (int64, erro
} }
func stringBytes(s *stream) ([]byte, error) { func stringBytes(s *stream) ([]byte, error) {
s.progress() s.cursor++
start := s.cursor start := s.cursor
for { for {
switch s.char() { switch s.char() {
case '\\': case '\\':
s.progress() s.cursor++
case '"': case '"':
literal := s.buf[start:s.cursor] literal := s.buf[start:s.cursor]
s.progress() s.cursor++
s.reset() s.reset()
return literal, nil return literal, nil
case '\000': case nul:
if s.read() {
continue
}
goto ERROR goto ERROR
} }
s.progress() s.cursor++
} }
ERROR: ERROR:
return nil, errUnexpectedEndOfJSON("string", s.totalOffset()) return nil, errUnexpectedEndOfJSON("string", s.totalOffset())
} }
func nullBytes(s *stream) error { func nullBytes(s *stream) error {
s.progress() if s.cursor+3 >= s.length {
if !s.read() {
return errInvalidCharacter(s.char(), "null", s.totalOffset())
}
}
s.cursor++
if s.char() != 'u' { if s.char() != 'u' {
return errInvalidCharacter(s.char(), "null", s.totalOffset()) return errInvalidCharacter(s.char(), "null", s.totalOffset())
} }
s.progress() s.cursor++
if s.char() != 'l' { if s.char() != 'l' {
return errInvalidCharacter(s.char(), "null", s.totalOffset()) return errInvalidCharacter(s.char(), "null", s.totalOffset())
} }
s.progress() s.cursor++
if s.char() != 'l' { if s.char() != 'l' {
return errInvalidCharacter(s.char(), "null", s.totalOffset()) return errInvalidCharacter(s.char(), "null", s.totalOffset())
} }
s.progress() s.cursor++
return nil return nil
} }
@ -72,7 +80,8 @@ func (d *stringDecoder) decodeStreamByte(s *stream) ([]byte, error) {
for { for {
switch s.char() { switch s.char() {
case ' ', '\n', '\t', '\r': case ' ', '\n', '\t', '\r':
s.progress() s.cursor++
continue
case '"': case '"':
return stringBytes(s) return stringBytes(s)
case 'n': case 'n':
@ -80,11 +89,13 @@ func (d *stringDecoder) decodeStreamByte(s *stream) ([]byte, error) {
return nil, err return nil, err
} }
return []byte{'n', 'u', 'l', 'l'}, nil return []byte{'n', 'u', 'l', 'l'}, nil
default: case nul:
goto ERROR if s.read() {
continue
} }
} }
ERROR: break
}
return nil, errNotAtBeginningOfValue(s.totalOffset()) return nil, errNotAtBeginningOfValue(s.totalOffset())
} }

View File

@ -23,10 +23,13 @@ func newStructDecoder(fieldMap map[string]*structFieldSet) *structDecoder {
func (d *structDecoder) decodeStream(s *stream, p uintptr) error { func (d *structDecoder) decodeStream(s *stream, p uintptr) error {
s.skipWhiteSpace() s.skipWhiteSpace()
if s.char() == nul {
s.read()
}
if s.char() != '{' { if s.char() != '{' {
return errNotAtBeginningOfValue(s.totalOffset()) return errNotAtBeginningOfValue(s.totalOffset())
} }
s.progress() s.cursor++
for { for {
s.reset() s.reset()
key, err := d.keyDecoder.decodeStreamByte(s) key, err := d.keyDecoder.decodeStreamByte(s)
@ -34,10 +37,16 @@ func (d *structDecoder) decodeStream(s *stream, p uintptr) error {
return err return err
} }
s.skipWhiteSpace() s.skipWhiteSpace()
if s.char() == nul {
s.read()
}
if s.char() != ':' { if s.char() != ':' {
return errExpected("colon after object key", s.totalOffset()) return errExpected("colon after object key", s.totalOffset())
} }
s.progress() s.cursor++
if s.char() == nul {
s.read()
}
if s.end() { if s.end() {
return errExpected("object value after colon", s.totalOffset()) return errExpected("object value after colon", s.totalOffset())
} }
@ -53,15 +62,18 @@ func (d *structDecoder) decodeStream(s *stream, p uintptr) error {
} }
} }
s.skipWhiteSpace() s.skipWhiteSpace()
if s.char() == nul {
s.read()
}
c := s.char() c := s.char()
if c == '}' { if c == '}' {
s.progress() s.cursor++
return nil return nil
} }
if c != ',' { if c != ',' {
return errExpected("comma after object element", s.totalOffset()) return errExpected("comma after object element", s.totalOffset())
} }
s.progress() s.cursor++
} }
return nil return nil
} }

View File

@ -28,23 +28,30 @@ func (d *uintDecoder) decodeStreamByte(s *stream) ([]byte, error) {
for { for {
switch s.char() { switch s.char() {
case ' ', '\n', '\t', '\r': case ' ', '\n', '\t', '\r':
s.progress() s.cursor++
continue continue
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
start := s.cursor start := s.cursor
for s.progress() { for {
tk := int(s.char()) s.cursor++
if int('0') <= tk && tk <= int('9') { if numTable[s.char()] {
continue continue
} else if s.char() == nul {
if s.read() {
continue
}
} }
break break
} }
num := s.buf[start:s.cursor] num := s.buf[start:s.cursor]
return num, nil return num, nil
default: case nul:
return nil, errInvalidCharacter(s.char(), "number(unsigned integer)", s.totalOffset()) if s.read() {
continue
} }
} }
break
}
return nil, errUnexpectedEndOfJSON("number(unsigned integer)", s.totalOffset()) return nil, errUnexpectedEndOfJSON("number(unsigned integer)", s.totalOffset())
} }