7e6746b3f84b4e0a2ffd54916f5de69074f8f081 — Martin Angers 6 months ago 90c97f1
scan numbers, test and benchmark
3 files changed, 112 insertions(+), 2 deletions(-)

M zerojson.go
M zerojson_bench_test.go
M zerojson_test.go
M zerojson.go => zerojson.go +56 -2
@@ 31,6 31,9 @@
 	// ErrIncompleteHexEsc is the error representing an invalid \uXXXX hexadecimal
 	// escape sequence in a string value.
 	ErrIncompleteHexEsc errorString = "incomplete hexadecimal escape sequence"
+
+	// ErrIncompleteNumber is the error representing an invalid number value.
+	ErrIncompleteNumber errorString = "incomplete number"
 )
 
 // JSON supports 7 different values:


@@ 185,9 188,9 @@
 		case '"':
 			err = p.scanString()
 		case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
-			//first := typ
+			first := typ
 			typ = '1'
-			//err = p.scanNumber(first)
+			err = p.scanNumber(first)
 		case 't':
 			err = p.scanToken(trueTrail)
 		case 'f':


@@ 210,6 213,57 @@
 	return p.emit(p.pos, p.cur, nil, io.EOF)
 }
 
+func (p *parser) scanNumber(first byte) error {
+	// if first == '-', the next char must be 0-9
+	if first == '-' {
+		if '0' <= p.cur && p.cur <= '9' {
+			first = p.cur
+			p.advance()
+		} else {
+			return ErrIncompleteNumber
+		}
+	}
+
+	// if first digit is not '0', scan any subsequent digits
+	if first != '0' {
+		for '0' <= p.cur && p.cur <= '9' {
+			p.advance()
+		}
+	}
+
+	// scan any fractional digit
+	if p.cur == '.' {
+		var digitSeen bool
+		p.advance()
+		for '0' <= p.cur && p.cur <= '9' {
+			digitSeen = true
+			p.advance()
+		}
+		if !digitSeen {
+			return ErrIncompleteNumber
+		}
+	}
+
+	// scan any exponent
+	if p.cur == 'e' || p.cur == 'E' {
+		p.advance()
+		if p.cur == '+' || p.cur == '-' {
+			p.advance()
+		}
+
+		var digitSeen bool
+		for '0' <= p.cur && p.cur <= '9' {
+			digitSeen = true
+			p.advance()
+		}
+		if !digitSeen {
+			return ErrIncompleteNumber
+		}
+	}
+
+	return nil
+}
+
 func (p *parser) scanString() error {
 	for p.cur != '"' {
 		switch {

M zerojson_bench_test.go => zerojson_bench_test.go +4 -0
@@ 22,6 22,10 @@
 	benchmarkInput(b, `"a\bcde\fghijklm\nopq\rs\t\uaB12vwxyz\"\\\/"`)
 }
 
+func BenchmarkNumber(b *testing.B) {
+	benchmarkInput(b, `123.456e+7890`)
+}
+
 func benchmarkInput(b *testing.B, input string) {
 	p := parser{
 		input: []byte(fmt.Sprintf(" %s ", input)),

M zerojson_test.go => zerojson_test.go +52 -0
@@ 43,6 43,58 @@
 		{`"\a"`, "0: \": \"", ErrUnclosedString},
 		{`"\uabc"`, "0: \": \"\\uabc", ErrIncompleteHexEsc},
 		{"\"\x00\"", "0: \": \"", ErrUnclosedString},
+
+		{"0", "0: 1: 0", nil},
+		{"1", "0: 1: 1", nil},
+		{"2", "0: 1: 2", nil},
+		{"3", "0: 1: 3", nil},
+		{"4", "0: 1: 4", nil},
+		{"5", "0: 1: 5", nil},
+		{"6", "0: 1: 6", nil},
+		{"7", "0: 1: 7", nil},
+		{"8", "0: 1: 8", nil},
+		{"9", "0: 1: 9", nil},
+		{"00", "0: 1: 0\n1: 1: 0\n", nil}, // TODO: would it be better to error?
+		{"-0", "0: 1: -0", nil},
+		{"-1", "0: 1: -1", nil},
+		{"-2", "0: 1: -2", nil},
+		{"-3", "0: 1: -3", nil},
+		{"-4", "0: 1: -4", nil},
+		{"-5", "0: 1: -5", nil},
+		{"-6", "0: 1: -6", nil},
+		{"-7", "0: 1: -7", nil},
+		{"-8", "0: 1: -8", nil},
+		{"-9", "0: 1: -9", nil},
+		{"-01", "0: 1: -0\n2: 1: 1", nil}, // TODO: same
+		{"1234567890", "0: 1: 1234567890", nil},
+		{"-1234567890", "0: 1: -1234567890", nil},
+		{"1.0123456789", "0: 1: 1.0123456789", nil},
+		{"1.", "0: 1: 1.", ErrIncompleteNumber},
+		{"-", "0: 1: -", ErrIncompleteNumber},
+		{"0.123456789", "0: 1: 0.123456789", nil},
+		{"-0.123456789", "0: 1: -0.123456789", nil},
+
+		{"1.2e3", "0: 1: 1.2e3", nil},
+		{"1.2E3", "0: 1: 1.2E3", nil},
+		{"1.2e+3", "0: 1: 1.2e+3", nil},
+		{"1.2E+3", "0: 1: 1.2E+3", nil},
+		{"1.2e-3", "0: 1: 1.2e-3", nil},
+		{"1.2E-3", "0: 1: 1.2E-3", nil},
+		{"-1.2e+3", "0: 1: -1.2e+3", nil},
+		{"-1.2E+3", "0: 1: -1.2E+3", nil},
+		{"-1.2e-3", "0: 1: -1.2e-3", nil},
+		{"-1.2E-3", "0: 1: -1.2E-3", nil},
+
+		{"1e3", "0: 1: 1e3", nil},
+		{"1E3", "0: 1: 1E3", nil},
+		{"1e+3", "0: 1: 1e+3", nil},
+		{"1E+3", "0: 1: 1E+3", nil},
+		{"1e-3", "0: 1: 1e-3", nil},
+		{"1E-3", "0: 1: 1E-3", nil},
+		{"-1e+3", "0: 1: -1e+3", nil},
+		{"-1E+3", "0: 1: -1E+3", nil},
+		{"-1e-3", "0: 1: -1e-3", nil},
+		{"-1E-3", "0: 1: -1E-3", nil},
 	}
 
 	for _, c := range cases {