~mna/zerojson

e61da2dcc2878885092923614f4bddc9c421878f — Martin Angers 1 year, 16 days ago 7e6746b
scan nested objects and arrays
3 files changed, 92 insertions(+), 21 deletions(-)

M zerojson.go
M zerojson_bench_test.go
M zerojson_test.go
M zerojson.go => zerojson.go +85 -21
@@ 144,6 144,8 @@ type parser struct {
	// The type may be:
	// '{' : start of an object (v is '{')
	// '[' : start of an array (v is '[')
	// '}' : end of an object (v is '}')
	// ']' : end of an array (v is ']')
	// '"' : a string (v is the full string, with quotes)
	// '1' : a number (v is the full number)
	// 't' : true (v is the full literal)


@@ 158,6 160,7 @@ type parser struct {

	// parser state
	cur   byte
	allow byte // '\x00'=any value, '<'=key, '>'=value, ':'=colon, ','=comma or ']' or '}'
	pos   int
	stack stack
}


@@ 180,30 183,91 @@ loop:
		typ = p.cur
		p.advance()

		switch typ {
		case '{':
			p.stack.push(typ)
		case '[':
			p.stack.push(typ)
		case '"':
			err = p.scanString()
		case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
			first := typ
			typ = '1'
			err = p.scanNumber(first)
		case 't':
			err = p.scanToken(trueTrail)
		case 'f':
			err = p.scanToken(falseTrail)
		case 'n':
			err = p.scanToken(nullTrail)
		switch p.allow {
		case ':':
			if typ == ':' {
				p.allow = '>'
				continue
			}
			err = ErrInvalidCodePoint

		case ',':
			closing := p.stack.peek() + 2 // turn '[' or '{' to ']' or '}'
			if typ == ',' {
				// expect another value in array or anothery key in object
				if closing == '}' {
					p.allow = '<'
				} else {
					p.allow = '>'
				}
				continue
			}

			if typ == closing {
				p.stack.pop()
				peek := p.stack.peek()
				switch peek {
				case '{', '[':
					p.allow = ','
				default:
					p.allow = 0
				}
			} else {
				err = ErrInvalidCodePoint
			}

		case '<':
			// key expected, must be a string or a closing object
			switch typ {
			case '"':
				err = p.scanString()
				p.allow = ':'
			case '}':
				p.stack.pop()
				peek := p.stack.peek()
				switch peek {
				case '{', '[':
					p.allow = ','
				default:
					p.allow = 0
				}

			default:
				err = ErrInvalidCodePoint
			}

		default:
			if p.eof() {
				// TODO: if a token was required, error
				break loop
			if p.allow == '>' {
				p.allow = ','
			}

			switch typ {
			case '{':
				p.stack.push(typ)
				p.allow = '<'
			case '[':
				p.stack.push(typ)
				p.allow = '>'
			case '"':
				err = p.scanString()
			case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
				first := typ
				typ = '1'
				err = p.scanNumber(first)
			case 't':
				err = p.scanToken(trueTrail)
			case 'f':
				err = p.scanToken(falseTrail)
			case 'n':
				err = p.scanToken(nullTrail)

			default:
				if p.eof() {
					// TODO: if a token was required, error
					break loop
				}
				err = ErrInvalidCodePoint
			}
			err = ErrInvalidCodePoint
		}

		if e := p.emit(start, typ, p.input[start:p.pos], err); e != nil {

M zerojson_bench_test.go => zerojson_bench_test.go +4 -0
@@ 26,6 26,10 @@ func BenchmarkNumber(b *testing.B) {
	benchmarkInput(b, `123.456e+7890`)
}

func BenchmarkSmallObject(b *testing.B) {
	benchmarkInput(b, `{"key": true}`)
}

func benchmarkInput(b *testing.B, input string) {
	p := parser{
		input: []byte(fmt.Sprintf(" %s ", input)),

M zerojson_test.go => zerojson_test.go +3 -0
@@ 95,6 95,9 @@ func TestParser(t *testing.T) {
		{"-1E+3", "0: 1: -1E+3", nil},
		{"-1e-3", "0: 1: -1e-3", nil},
		{"-1E-3", "0: 1: -1E-3", nil},

		{"{}", "0: {: {\n1: }: }", nil},
		{`{"a" : 1}`, "0: {: {\n1: \": \"a\"\n7: 1: 1\n8: }: }", nil},
	}

	for _, c := range cases {