Skip to content

Commit

Permalink
feat(num): implement number decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
ernado committed Nov 4, 2021
1 parent a7f8461 commit fa75ae6
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 24 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ d.ObjBytes(func(d *Decoder, key []byte) error {
- [ ] Rework `Any`
- [ ] Support `Raw` for io.Reader
- [ ] Support `Capture` for io.Reader
- [ ] Decide what to do with `base64`

# Non-goals
* Code generation for decoding or encoding
Expand Down
14 changes: 7 additions & 7 deletions any_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ const (
type Any struct {
Type AnyType // zero value if AnyInvalid, can be AnyNull

Str string // AnyStr
Bool bool // AnyBool
Number json.Number // AnyNumber
Str string // AnyStr
Bool bool // AnyBool
Number Num // AnyNumber

// Key in object. Valid only if KeyValid.
Key string
Expand Down Expand Up @@ -64,7 +64,7 @@ func (v Any) Equal(b Any) bool {
case AnyStr:
return v.Str == b.Str
case AnyNumber:
return v.Number == b.Number
return v.Number.Equal(b.Number)
}
if len(v.Child) != len(b.Child) {
return false
Expand Down Expand Up @@ -96,7 +96,7 @@ func (v *Any) Read(d *Decoder) error {
case Invalid:
return errors.New("invalid")
case Number:
n, err := d.Number()
n, err := d.Num()
if err != nil {
return errors.Wrap(err, "number")
}
Expand Down Expand Up @@ -162,7 +162,7 @@ func (v Any) Write(w *Encoder) {
case AnyStr:
w.Str(v.Str)
case AnyNumber:
w.Raw(string(v.Number))
w.Num(v.Number)
case AnyBool:
w.Bool(v.Bool)
case AnyNull:
Expand Down Expand Up @@ -201,7 +201,7 @@ func (v Any) String() string {
case AnyStr:
b.WriteString(`'` + v.Str + `'`)
case AnyNumber:
b.WriteString(string(v.Number))
b.WriteString(v.Number.String())
case AnyBool:
b.WriteString(strconv.FormatBool(v.Bool))
case AnyNull:
Expand Down
40 changes: 25 additions & 15 deletions dec_float.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func init() {

// BigFloat read big.Float
func (d *Decoder) BigFloat() (*big.Float, error) {
str, err := d.number(nil)
str, err := d.numberAppend(nil)
if err != nil {
return nil, errors.Wrap(err, "number")
}
Expand All @@ -54,7 +54,7 @@ func (d *Decoder) BigFloat() (*big.Float, error) {

// BigInt read big.Int
func (d *Decoder) BigInt() (*big.Int, error) {
str, err := d.number(nil)
str, err := d.numberAppend(nil)
if err != nil {
return nil, errors.Wrap(err, "number")
}
Expand Down Expand Up @@ -162,18 +162,28 @@ NonDecimalLoop:
return d.f32Slow()
}

func (d *Decoder) number(b []byte) ([]byte, error) {
func (d *Decoder) number() []byte {
start := d.head
for i := d.head; i < d.tail; i++ {
switch c := d.buf[i]; c {
case '+', '-', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
continue
default:
// End of number.
d.head = i
return d.buf[start:d.head]
}
}
// Buffer is number within head:tail.
d.head = d.tail
return d.buf[start:d.tail]
}

func (d *Decoder) numberAppend(b []byte) ([]byte, error) {
for {
for i := d.head; i < d.tail; i++ {
switch c := d.buf[i]; c {
case '+', '-', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
b = append(b, c)
continue
default:
// End of number.
d.head = i
return b, nil
}
b = append(b, d.number()...)
if d.head != d.tail {
return b, nil
}
if err := d.read(); err != nil {
if err == io.EOF {
Expand Down Expand Up @@ -297,7 +307,7 @@ NonDecimal:
func (d *Decoder) floatSlow(size int) (float64, error) {
var buf [32]byte

str, err := d.number(buf[:0])
str, err := d.numberAppend(buf[:0])
if err != nil {
return 0, errors.Wrap(err, "number")
}
Expand Down Expand Up @@ -342,7 +352,7 @@ func validateFloat(str []byte) error {

// Number reads json.Number.
func (d *Decoder) Number() (json.Number, error) {
str, err := d.number(nil)
str, err := d.numberAppend(nil)
if err != nil {
return "", err
}
Expand Down
3 changes: 1 addition & 2 deletions dec_float_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func TestDecoder_BigFloat(t *testing.T) {
func TestDecoder_Float32(t *testing.T) {
v, err := DecodeStr(`429496739.0`).Float32()
require.NoError(t, err)
require.InEpsilon(t, 429496729.0, v, 1e-6)
require.InEpsilon(t, 429496729.0, v, epsilon)
}

func TestDecoder_Float64(t *testing.T) {
Expand All @@ -116,7 +116,6 @@ func TestDecoder_Float64(t *testing.T) {
},
} {
t.Run(tc.String, func(t *testing.T) {
const epsilon = 1e-6
t.Run("32Str", func(t *testing.T) {
v, err := DecodeStr(tc.String).Float32()
require.InEpsilon(t, tc.Value, v, epsilon)
Expand Down
64 changes: 64 additions & 0 deletions num.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package jx

import (
"bytes"
"strings"

"github.com/ogen-go/errors"
)

// NumFormat is format of Num.Value.
Expand Down Expand Up @@ -63,6 +66,14 @@ type Num struct {
Value []byte
}

// Equal reports whether numbers are strictly equal, including their formats.
func (n Num) Equal(v Num) bool {
if n.Format != v.Format {
return false
}
return bytes.Equal(n.Value, v.Value)
}

func (n Num) String() string {
if n.Format.Invalid() {
return "<invalid>"
Expand Down Expand Up @@ -137,3 +148,56 @@ func (e *Encoder) Num(v Num) {
e.byte('"')
}
}

// Num decodes number.
func (d *Decoder) Num() (Num, error) {
return d.NumTo(Num{})
}

// NumTo decodes number into Num.
func (d *Decoder) NumTo(v Num) (Num, error) {
var str bool
switch d.Next() {
case String:
// Consume start of the string.
d.head++
str = true
case Number: // float or integer
default:
return v, errors.Errorf("unexpected %s", d.Next())
}
if d.reader == nil {
// Can use underlying buffer directly.
v.Value = d.number()
} else {
buf, err := d.numberAppend(v.Value[:0])
if err != nil {
return v, errors.Wrap(err, "decode")
}
v.Value = buf
}

var dot bool
for _, c := range v.Value {
if c != '.' {
continue
}
dot = true
break
}
if dot {
v.Format = NumFormatFloat
if str {
v.Format = NumFormatFloatStr
}
} else {
v.Format = NumFormatInt
if str {
v.Format = NumFormatIntStr
}
}

// TODO(ernado): Additional validity checks

return v, nil
}
42 changes: 42 additions & 0 deletions num_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ func TestEncoder_Num(t *testing.T) {
require.Equal(t, e.String(), "123")
}

const epsilon = 1e-6

func TestNum(t *testing.T) {
t.Run("ZeroValue", func(t *testing.T) {
// Zero value is invalid because there is no Nun.Value.
Expand All @@ -40,6 +42,12 @@ func TestNum(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 123, n)
})
t.Run("Decode", func(t *testing.T) {
n, err := DecodeStr("12345").NumTo(Num{})
require.NoError(t, err)
require.Equal(t, NumFormatInt, n.Format)
require.Equal(t, "12345", n.String())
})
t.Run("Methods", func(t *testing.T) {
assert.True(t, v.Positive())
assert.True(t, v.Format.Int())
Expand All @@ -50,6 +58,40 @@ func TestNum(t *testing.T) {
assert.Equal(t, "123", v.String())
})
})
t.Run("Integer", func(t *testing.T) {
const (
s = `1.23`
f = 1.23
)
v := Num{
Format: NumFormatFloat,
Value: []byte(s),
}
t.Run("Encode", func(t *testing.T) {
var e Encoder
e.Num(v)
require.Equal(t, e.String(), s)

n, err := DecodeBytes(e.Bytes()).Float64()
require.NoError(t, err)
require.InEpsilon(t, f, n, epsilon)
})
t.Run("Decode", func(t *testing.T) {
n, err := DecodeStr(s).NumTo(Num{})
require.NoError(t, err)
require.Equal(t, NumFormatFloat, n.Format)
require.Equal(t, s, n.String())
})
t.Run("Methods", func(t *testing.T) {
assert.True(t, v.Positive())
assert.True(t, v.Format.Float())
assert.False(t, v.Format.Invalid())
assert.False(t, v.Negative())
assert.False(t, v.Zero())
assert.Equal(t, 1, v.Sign())
assert.Equal(t, s, v.String())
})
})
}

func BenchmarkNum(b *testing.B) {
Expand Down

0 comments on commit fa75ae6

Please sign in to comment.