diff --git a/html/html.go b/html/html.go
index 90192a1..748065f 100644
--- a/html/html.go
+++ b/html/html.go
@@ -36,7 +36,7 @@ var (
attr := map[string]string{}
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}
diff --git a/json/json.go b/json/json.go
index 4155bd2..7c84f42 100644
--- a/json/json.go
+++ b/json/json.go
@@ -21,23 +21,21 @@ var (
ret := map[string]interface{}{}
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}
})
- _null = Bind("null", nil)
- _true = Bind("true", true)
- _false = Bind("false", false)
-
- _string = Map(StringLit(`"`), func(n Node) Node {
- return Node{Result: n.Token}
- })
+ _null = Bind("null", nil)
+ _true = Bind("true", true)
+ _false = Bind("false", false)
+ _string = StringLit(`"`)
+ _number = NumberLit()
)
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) {
diff --git a/json/json_test.go b/json/json_test.go
index 8f2e148..3cc7934 100644
--- a/json/json_test.go
+++ b/json/json_test.go
@@ -29,15 +29,15 @@ func TestUnmarshal(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.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) {
- 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.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)
})
}
diff --git a/literals.go b/literals.go
index 241dd93..5dd9fef 100644
--- a/literals.go
+++ b/literals.go
@@ -2,11 +2,12 @@ package goparsify
import (
"bytes"
+ "strconv"
"unicode/utf8"
)
func StringLit(allowedQuotes string) Parser {
- return NewParser("string", func(ps *State) Node {
+ return NewParser("string literal", func(ps *State) Node {
ps.AutoWS()
for i := 0; i < len(allowedQuotes); i++ {
@@ -61,10 +62,10 @@ func StringLit(allowedQuotes string) Parser {
if buf == nil {
result := ps.Input[ps.Pos+1 : end]
ps.Pos = end + 1
- return Node{Token: result}
+ return Node{Result: result}
}
ps.Pos = end + 1
- return Node{Token: buf.String()}
+ return Node{Result: buf.String()}
default:
r, w := utf8.DecodeRuneInString(ps.Input[end:])
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 {
for i := 0; i < len(s); i++ {
if b == s[i] {
diff --git a/literals_test.go b/literals_test.go
index 15b971d..83e087b 100644
--- a/literals_test.go
+++ b/literals_test.go
@@ -6,23 +6,23 @@ import (
"github.com/stretchr/testify/require"
)
-func TestString(t *testing.T) {
+func TestStringLit(t *testing.T) {
parser := StringLit(`"'`)
t.Run("test double match", func(t *testing.T) {
result, p := runParser(`"hello"`, parser)
- require.Equal(t, `hello`, result.Token)
+ require.Equal(t, `hello`, result.Result)
require.Equal(t, "", p.Get())
})
t.Run("test single match", func(t *testing.T) {
result, p := runParser(`"hello"`, parser)
- require.Equal(t, `hello`, result.Token)
+ require.Equal(t, `hello`, result.Result)
require.Equal(t, "", p.Get())
})
t.Run("test nested quotes", func(t *testing.T) {
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())
})
@@ -52,14 +52,14 @@ func TestString(t *testing.T) {
t.Run("test escaping", func(t *testing.T) {
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())
})
t.Run("test escaped unicode", func(t *testing.T) {
result, p := runParser(`"hello \ubeef cake"`, parser)
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())
})
@@ -75,3 +75,96 @@ func TestString(t *testing.T) {
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)
+ })
+}
diff --git a/parser_test.go b/parser_test.go
index 3a3b778..08a8f7b 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -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) {
ps := InputString(input)