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)