Add NumberLit parser and complete json spec
This commit is contained in:
parent
73b55459ac
commit
035af4d873
@ -36,7 +36,7 @@ var (
|
|||||||
attr := map[string]string{}
|
attr := map[string]string{}
|
||||||
|
|
||||||
for _, attrNode := range node.Children {
|
for _, attrNode := range node.Children {
|
||||||
attr[attrNode.Children[0].Token] = attrNode.Children[2].Token
|
attr[attrNode.Children[0].Token] = attrNode.Children[2].Result.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Node{Result: attr}
|
return Node{Result: attr}
|
||||||
|
16
json/json.go
16
json/json.go
@ -21,23 +21,21 @@ var (
|
|||||||
ret := map[string]interface{}{}
|
ret := map[string]interface{}{}
|
||||||
|
|
||||||
for _, prop := range n.Children[1].Children {
|
for _, prop := range n.Children[1].Children {
|
||||||
ret[prop.Children[0].Token] = prop.Children[2].Result
|
ret[prop.Children[0].Result.(string)] = prop.Children[2].Result
|
||||||
}
|
}
|
||||||
|
|
||||||
return Node{Result: ret}
|
return Node{Result: ret}
|
||||||
})
|
})
|
||||||
|
|
||||||
_null = Bind("null", nil)
|
_null = Bind("null", nil)
|
||||||
_true = Bind("true", true)
|
_true = Bind("true", true)
|
||||||
_false = Bind("false", false)
|
_false = Bind("false", false)
|
||||||
|
_string = StringLit(`"`)
|
||||||
_string = Map(StringLit(`"`), func(n Node) Node {
|
_number = NumberLit()
|
||||||
return Node{Result: n.Token}
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
value = Any(_null, _true, _false, _string, _array, _object)
|
value = Any(_null, _true, _false, _string, _number, _array, _object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Unmarshal(input string) (interface{}, error) {
|
func Unmarshal(input string) (interface{}, error) {
|
||||||
|
@ -29,15 +29,15 @@ func TestUnmarshal(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("array", func(t *testing.T) {
|
t.Run("array", func(t *testing.T) {
|
||||||
result, err := Unmarshal(`[true, null, false]`)
|
result, err := Unmarshal(`[true, null, false, -1.23e+4]`)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, []interface{}{true, nil, false}, result)
|
require.Equal(t, []interface{}{true, nil, false, -1.23e+4}, result)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("object", func(t *testing.T) {
|
t.Run("object", func(t *testing.T) {
|
||||||
result, err := Unmarshal(`{"true":true, "false":false, "null": null} `)
|
result, err := Unmarshal(`{"true":true, "false":false, "null": null, "number": 404} `)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, map[string]interface{}{"true": true, "false": false, "null": nil}, result)
|
require.Equal(t, map[string]interface{}{"true": true, "false": false, "null": nil, "number": int64(404)}, result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
65
literals.go
65
literals.go
@ -2,11 +2,12 @@ package goparsify
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strconv"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StringLit(allowedQuotes string) Parser {
|
func StringLit(allowedQuotes string) Parser {
|
||||||
return NewParser("string", func(ps *State) Node {
|
return NewParser("string literal", func(ps *State) Node {
|
||||||
ps.AutoWS()
|
ps.AutoWS()
|
||||||
|
|
||||||
for i := 0; i < len(allowedQuotes); i++ {
|
for i := 0; i < len(allowedQuotes); i++ {
|
||||||
@ -61,10 +62,10 @@ func StringLit(allowedQuotes string) Parser {
|
|||||||
if buf == nil {
|
if buf == nil {
|
||||||
result := ps.Input[ps.Pos+1 : end]
|
result := ps.Input[ps.Pos+1 : end]
|
||||||
ps.Pos = end + 1
|
ps.Pos = end + 1
|
||||||
return Node{Token: result}
|
return Node{Result: result}
|
||||||
}
|
}
|
||||||
ps.Pos = end + 1
|
ps.Pos = end + 1
|
||||||
return Node{Token: buf.String()}
|
return Node{Result: buf.String()}
|
||||||
default:
|
default:
|
||||||
r, w := utf8.DecodeRuneInString(ps.Input[end:])
|
r, w := utf8.DecodeRuneInString(ps.Input[end:])
|
||||||
end += w
|
end += w
|
||||||
@ -79,6 +80,64 @@ func StringLit(allowedQuotes string) Parser {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NumberLit() Parser {
|
||||||
|
return NewParser("number literal", func(ps *State) Node {
|
||||||
|
ps.AutoWS()
|
||||||
|
end := ps.Pos
|
||||||
|
float := false
|
||||||
|
inputLen := len(ps.Input)
|
||||||
|
|
||||||
|
if end < inputLen && (ps.Input[end] == '-' || ps.Input[end] == '+') {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
for end < inputLen && ps.Input[end] >= '0' && ps.Input[end] <= '9' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
if end < inputLen && ps.Input[end] == '.' {
|
||||||
|
float = true
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
for end < inputLen && ps.Input[end] >= '0' && ps.Input[end] <= '9' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
if end < inputLen && (ps.Input[end] == 'e' || ps.Input[end] == 'E') {
|
||||||
|
end++
|
||||||
|
float = true
|
||||||
|
|
||||||
|
if end < inputLen && (ps.Input[end] == '-' || ps.Input[end] == '+') {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
for end < inputLen && ps.Input[end] >= '0' && ps.Input[end] <= '9' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if end == ps.Pos {
|
||||||
|
ps.ErrorHere("number")
|
||||||
|
return Node{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result interface{}
|
||||||
|
var err error
|
||||||
|
if float {
|
||||||
|
result, err = strconv.ParseFloat(ps.Input[ps.Pos:end], 10)
|
||||||
|
} else {
|
||||||
|
result, err = strconv.ParseInt(ps.Input[ps.Pos:end], 10, 64)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ps.ErrorHere("number")
|
||||||
|
return Node{}
|
||||||
|
}
|
||||||
|
ps.Pos = end
|
||||||
|
return Node{Result: result}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func stringContainsByte(s string, b byte) bool {
|
func stringContainsByte(s string, b byte) bool {
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
if b == s[i] {
|
if b == s[i] {
|
||||||
|
105
literals_test.go
105
literals_test.go
@ -6,23 +6,23 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestString(t *testing.T) {
|
func TestStringLit(t *testing.T) {
|
||||||
parser := StringLit(`"'`)
|
parser := StringLit(`"'`)
|
||||||
t.Run("test double match", func(t *testing.T) {
|
t.Run("test double match", func(t *testing.T) {
|
||||||
result, p := runParser(`"hello"`, parser)
|
result, p := runParser(`"hello"`, parser)
|
||||||
require.Equal(t, `hello`, result.Token)
|
require.Equal(t, `hello`, result.Result)
|
||||||
require.Equal(t, "", p.Get())
|
require.Equal(t, "", p.Get())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("test single match", func(t *testing.T) {
|
t.Run("test single match", func(t *testing.T) {
|
||||||
result, p := runParser(`"hello"`, parser)
|
result, p := runParser(`"hello"`, parser)
|
||||||
require.Equal(t, `hello`, result.Token)
|
require.Equal(t, `hello`, result.Result)
|
||||||
require.Equal(t, "", p.Get())
|
require.Equal(t, "", p.Get())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("test nested quotes", func(t *testing.T) {
|
t.Run("test nested quotes", func(t *testing.T) {
|
||||||
result, p := runParser(`"hello 'world'"`, parser)
|
result, p := runParser(`"hello 'world'"`, parser)
|
||||||
require.Equal(t, `hello 'world'`, result.Token)
|
require.Equal(t, `hello 'world'`, result.Result)
|
||||||
require.Equal(t, "", p.Get())
|
require.Equal(t, "", p.Get())
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -52,14 +52,14 @@ func TestString(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("test escaping", func(t *testing.T) {
|
t.Run("test escaping", func(t *testing.T) {
|
||||||
result, p := runParser(`"hello \"world\""`, parser)
|
result, p := runParser(`"hello \"world\""`, parser)
|
||||||
require.Equal(t, `hello "world"`, result.Token)
|
require.Equal(t, `hello "world"`, result.Result)
|
||||||
require.Equal(t, ``, p.Get())
|
require.Equal(t, ``, p.Get())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("test escaped unicode", func(t *testing.T) {
|
t.Run("test escaped unicode", func(t *testing.T) {
|
||||||
result, p := runParser(`"hello \ubeef cake"`, parser)
|
result, p := runParser(`"hello \ubeef cake"`, parser)
|
||||||
require.Equal(t, "", p.Error.Expected)
|
require.Equal(t, "", p.Error.Expected)
|
||||||
require.Equal(t, "hello \uBEEF cake", result.Token)
|
require.Equal(t, "hello \uBEEF cake", result.Result)
|
||||||
require.Equal(t, ``, p.Get())
|
require.Equal(t, ``, p.Get())
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -75,3 +75,96 @@ func TestString(t *testing.T) {
|
|||||||
require.Equal(t, 0, p.Pos)
|
require.Equal(t, 0, p.Pos)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnhex(t *testing.T) {
|
||||||
|
tests := map[int64]string{
|
||||||
|
0xF: "F",
|
||||||
|
0x5: "5",
|
||||||
|
0xFF: "FF",
|
||||||
|
0xFFF: "FFF",
|
||||||
|
0xA4B: "a4b",
|
||||||
|
0xFFFF: "FFFF",
|
||||||
|
0xBEEFCAFE: "beeFCAfe",
|
||||||
|
}
|
||||||
|
for expected, input := range tests {
|
||||||
|
t.Run(input, func(t *testing.T) {
|
||||||
|
r, ok := unhex(input)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.EqualValues(t, expected, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Fails on non hex chars", func(t *testing.T) {
|
||||||
|
_, ok := unhex("hello")
|
||||||
|
require.False(t, ok)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNumberLit(t *testing.T) {
|
||||||
|
parser := NumberLit()
|
||||||
|
t.Run("test int", func(t *testing.T) {
|
||||||
|
result, p := runParser("1234", parser)
|
||||||
|
require.Equal(t, int64(1234), result.Result)
|
||||||
|
require.Equal(t, "", p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test float", func(t *testing.T) {
|
||||||
|
result, p := runParser("12.34", parser)
|
||||||
|
require.Equal(t, 12.34, result.Result)
|
||||||
|
require.Equal(t, "", p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test negative float", func(t *testing.T) {
|
||||||
|
result, p := runParser("-12.34", parser)
|
||||||
|
require.Equal(t, -12.34, result.Result)
|
||||||
|
require.Equal(t, "", p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without leading zero", func(t *testing.T) {
|
||||||
|
result, p := runParser("-.34", parser)
|
||||||
|
require.Equal(t, -.34, result.Result)
|
||||||
|
require.Equal(t, "", p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scientific notation", func(t *testing.T) {
|
||||||
|
result, p := runParser("12.34e3", parser)
|
||||||
|
require.Equal(t, 12.34e3, result.Result)
|
||||||
|
require.Equal(t, "", p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scientific notation without decimal", func(t *testing.T) {
|
||||||
|
result, p := runParser("34e3", parser)
|
||||||
|
require.Equal(t, 34e3, result.Result)
|
||||||
|
require.Equal(t, "", p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("scientific notation negative power", func(t *testing.T) {
|
||||||
|
result, p := runParser("34e-3", parser)
|
||||||
|
require.Equal(t, 34e-3, result.Result)
|
||||||
|
require.Equal(t, "", p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("negative scientific notation negative power", func(t *testing.T) {
|
||||||
|
result, p := runParser("-.34e-3", parser)
|
||||||
|
require.Equal(t, -.34e-3, result.Result)
|
||||||
|
require.Equal(t, "", p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("partial match", func(t *testing.T) {
|
||||||
|
result, p := runParser("-1.34foo", parser)
|
||||||
|
require.Equal(t, -1.34, result.Result)
|
||||||
|
require.Equal(t, "foo", p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non matching string", func(t *testing.T) {
|
||||||
|
_, p := runParser("foo", parser)
|
||||||
|
require.Equal(t, "offset 0: Expected number", p.Error.Error())
|
||||||
|
require.Equal(t, 0, p.Pos)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid number", func(t *testing.T) {
|
||||||
|
_, p := runParser("-.", parser)
|
||||||
|
require.Equal(t, "offset 0: Expected number", p.Error.Error())
|
||||||
|
require.Equal(t, 0, p.Pos)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -151,29 +151,6 @@ func TestParseString(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnhex(t *testing.T) {
|
|
||||||
tests := map[int64]string{
|
|
||||||
0xF: "F",
|
|
||||||
0x5: "5",
|
|
||||||
0xFF: "FF",
|
|
||||||
0xFFF: "FFF",
|
|
||||||
0xA4B: "a4b",
|
|
||||||
0xFFFF: "FFFF",
|
|
||||||
0xBEEFCAFE: "beeFCAfe",
|
|
||||||
}
|
|
||||||
for expected, input := range tests {
|
|
||||||
t.Run(input, func(t *testing.T) {
|
|
||||||
r, ok := unhex(input)
|
|
||||||
require.True(t, ok)
|
|
||||||
require.EqualValues(t, expected, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Fails on non hex chars", func(t *testing.T) {
|
|
||||||
_, ok := unhex("hello")
|
|
||||||
require.False(t, ok)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func runParser(input string, parser Parser) (Node, *State) {
|
func runParser(input string, parser Parser) (Node, *State) {
|
||||||
ps := InputString(input)
|
ps := InputString(input)
|
||||||
|
Loading…
Reference in New Issue
Block a user