Skip to content

Commit

Permalink
improvements (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
spyzhov authored Jun 11, 2019
1 parent 3c5cd15 commit 6d5bc1b
Show file tree
Hide file tree
Showing 14 changed files with 639 additions and 99 deletions.
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ go:
- 1.11.x
- 1.12.x

env:
- GO111MODULE=on

# Only clone the most recent commit.
git:
depth: 1
Expand All @@ -26,7 +29,7 @@ notifications:
# build and immediately stop. It's sorta like having set -e enabled in bash.
# Make sure golangci-lint is vendored.
before_script:
- go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
- go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.17.0
- go get -u github.com/kisielk/errcheck
- go get golang.org/x/tools/cmd/cover # coveralls.io
- go get github.com/mattn/goveralls # coveralls.io
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ golangcilint:

#go get github.com/fzipp/gocyclo
gocyclo:
gocyclo -top 10 .
gocyclo -top 10 .
47 changes: 29 additions & 18 deletions buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,44 +117,51 @@ func (b *buffer) skipAny(s map[byte]bool) error {

func (b *buffer) numeric() error {
var c byte
const (
_none = 0 // 0
_sign = 1 << 0 // 1
_dot = 1 << 1 // 2
_num = 1 << 2 // 4
_exp = 1 << 3 // 8
)
find := 0
for ; b.index < b.length; b.index++ {
c = b.data[b.index]
switch true {
case c >= '0' && c <= '9':
find |= 4
find |= _num
case c == '.':
if find&2 == 0 && find&8 == 0 { // exp part of numeric MUST contains only digits
find &= 2
if find&_exp != 0 { // exp part of numeric MUST contains only digits
return errorSymbol(b)
}
if find&_dot == 0 {
find |= _dot
} else {
if find&4 == 0 {
return errorSymbol(b)
}
return nil
return errorSymbol(b)
}
case c == '+' || c == '-':
if find == 0 || find == 8 {
find |= 1
if find == _none || find == _exp {
find |= _sign
} else {
if find&4 == 0 {
if find&_num == 0 {
return errorSymbol(b)
}
return nil
}
case c == 'e' || c == 'E':
if find&8 == 0 && find&4 != 0 { // exp without base part
find = 8
if find&_exp == 0 && find&_num != 0 { // exp without base part
find = _exp
} else {
return errorSymbol(b)
}
default:
if find&4 != 0 {
if find&_num != 0 {
return nil
}
return errorSymbol(b)
}
}
if find&4 != 0 {
if find&_num != 0 {
return io.EOF
}
return errorEOF(b)
Expand Down Expand Up @@ -226,22 +233,23 @@ tokenLoop:
switch {
case c == quote:
find = true
start = b.index
err = b.step()
if err != nil {
return b.errorEOF()
}
err = b.skip(quote)
if err == nil || err == io.EOF {
continue
if err == io.EOF {
return b.errorEOF()
}
b.index = start
case c == bracketL:
find = true
stack = append(stack, c)
case c == bracketR:
find = true
if len(stack) == 0 {
if first == b.index {
return b.errorSymbol()
}
break tokenLoop
}
if stack[len(stack)-1] != bracketL {
Expand All @@ -254,6 +262,9 @@ tokenLoop:
case c == parenthesesR:
find = true
if len(stack) == 0 {
if first == b.index {
return b.errorSymbol()
}
break tokenLoop
}
if stack[len(stack)-1] != parenthesesL {
Expand Down
133 changes: 132 additions & 1 deletion buffer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ajson

import (
"io"
"strings"
"testing"
)

Expand Down Expand Up @@ -33,6 +34,31 @@ func TestBuffer_Token(t *testing.T) {

{name: "fail 1", value: "@.foo[", fail: true},
{name: "fail 2", value: "@.foo[(]", fail: true},
{name: "fail 3", value: "'", fail: true},
{name: "fail 4", value: "'x", fail: true},

{name: "parentheses 0", value: "()", index: 2, fail: false},
{name: "parentheses 1", value: "(@)", index: 3, fail: false},
{name: "parentheses 2", value: "(", fail: true},
{name: "parentheses 3", value: ")", fail: true},
{name: "parentheses 4", value: "(x", fail: true},
{name: "parentheses 5", value: "((())", fail: true},
{name: "parentheses 6", value: "@)", index: 1, fail: false},
{name: "parentheses 7", value: "[)", fail: true},
{name: "parentheses 8", value: "[())", fail: true},

{name: "bracket 0", value: "[]", index: 2, fail: false},
{name: "bracket 1", value: "[@]", index: 3, fail: false},
{name: "bracket 2", value: "[", fail: true},
{name: "bracket 3", value: "]", fail: true},
{name: "bracket 4", value: "[x", fail: true},
{name: "bracket 5", value: "[[[]]", fail: true},
{name: "bracket 6", value: "@]", index: 1, fail: false},
{name: "bracket 7", value: "(]", fail: true},
{name: "bracket 8", value: "([]]", fail: true},

{name: "sign 1", value: "+X", index: 1},
{name: "sign 2", value: "-X", index: 1},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down Expand Up @@ -65,6 +91,12 @@ func TestBuffer_RPN(t *testing.T) {
{name: "example_8", value: "@.length-1", expected: []string{"@.length", "1", "-"}},
{name: "example_9", value: "@.length+-1", expected: []string{"@.length", "-1", "+"}},
{name: "example_10", value: "@.length/e", expected: []string{"@.length", "e", "/"}},
{name: "example_11", value: "", expected: []string{}},

{name: "1 /", value: "1 /", expected: []string{"1", "/"}},
{name: "1 + ", value: "1 + ", expected: []string{"1", "+"}},
{name: "1 -", value: "1 -", expected: []string{"1", "-"}},
{name: "1 * ", value: "1 * ", expected: []string{"1", "*"}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand All @@ -79,11 +111,50 @@ func TestBuffer_RPN(t *testing.T) {
}
}

func TestBuffer_RPNError(t *testing.T) {
tests := []struct {
value string
}{
{value: "1 + / 1"},
{value: "1 * / 1"},
{value: "1 - / 1"},
{value: "1 / / 1"},

{value: "1 + * 1"},
{value: "1 * * 1"},
{value: "1 - * 1"},
{value: "1 / * 1"},

{value: "1e1.1 + 1"},

{value: "len('string)"},
{value: "'Hello ' + 'World"},

{value: "@.length + $['length')"},
{value: "2 + 2)"},
{value: "(2 + 2"},

{value: "e + q"},
{value: "foo(e)"},
{value: "++2"},
}
for _, test := range tests {
t.Run(test.value, func(t *testing.T) {
buf := newBuffer([]byte(test.value))
result, err := buf.rpn()
if err == nil {
t.Errorf("Expected error, nil given, with result: %v", strings.Join(result, ", "))
}
})
}
}

func TestTokenize(t *testing.T) {
tests := []struct {
name string
value string
expected []string
fail bool
}{
{name: "example_1", value: "@.length", expected: []string{"@.length"}},
{name: "example_2", value: "1 + 2", expected: []string{"1", "+", "2"}},
Expand All @@ -96,15 +167,75 @@ func TestTokenize(t *testing.T) {
{name: "example_9", value: "'foo'", expected: []string{"'foo'"}},
{name: "example_10", value: "$.foo[(@.length - 3):3:]", expected: []string{"$.foo[(@.length - 3):3:]"}},
{name: "example_11", value: "$..", expected: []string{"$.."}},
{name: "blank", value: "", expected: []string{}},
{name: "number", value: "1e", fail: true},
{name: "string", value: "'foo", fail: true},
{name: "fail", value: "@.[", fail: true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result, err := tokenize(test.value)
if err != nil {
if test.fail {
if err == nil {
t.Error("Expected error: nil given")
}
} else if err != nil {
t.Errorf("Unexpected error: %s", err.Error())
} else if !sliceEqual(test.expected, result) {
t.Errorf("Error on RPN(%s): result doesn't match\nExpected: %s\nActual: %s", test.value, sliceString(test.expected), sliceString(result))
}
})
}
}

func TestBuffer_Current(t *testing.T) {
buf := newBuffer([]byte{})
_, err := buf.current()
if err != io.EOF {
t.Error("Unexpected result: io.EOF expected")
}
}

func TestBuffer_Numeric(t *testing.T) {
tests := []struct {
value string
index int
fail bool
}{
{value: "1", index: 1, fail: false},
{value: "0", index: 1, fail: false},
{value: "1.3e2", index: 5, fail: false},
{value: "-1.3e2", index: 6, fail: false},
{value: "-1.3e-2", index: 7, fail: false},
{value: "..3", index: 0, fail: true},
{value: "e.", index: 0, fail: true},
{value: ".e.", index: 0, fail: true},
{value: "1.e1", index: 4, fail: false},
{value: "0.e0", index: 4, fail: false},
{value: "0.e0", index: 4, fail: false},
{value: "0+0", index: 1, fail: false},
{value: "0-1", index: 1, fail: false},
{value: "++1", index: 0, fail: true},
{value: "--1", index: 0, fail: true},
{value: "-+1", index: 0, fail: true},
{value: "+-1", index: 0, fail: true},
{value: "+", index: 0, fail: true},
{value: "-", index: 0, fail: true},
{value: ".", index: 0, fail: true},
{value: "e", index: 0, fail: true},
{value: "+a", index: 0, fail: true},
}
for _, test := range tests {
t.Run(test.value, func(t *testing.T) {
buf := newBuffer([]byte(test.value))
err := buf.numeric()
if !test.fail && err != nil && err != io.EOF {
t.Errorf("Unexpected error: %s", err.Error())
} else if test.fail && (err == nil || err == io.EOF) {
t.Errorf("Expected error, got nothing")
} else if !test.fail && test.index != buf.index {
t.Errorf("Wrong index: expected %d, got %d", test.index, buf.index)
}
})
}
}
6 changes: 3 additions & 3 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ package ajson

import "io"

//UnmarshalSafe do the same thing as Unmarshal, but copy data to the local variable, to make it editable.
// UnmarshalSafe do the same thing as Unmarshal, but copy data to the local variable, to make it editable.
func UnmarshalSafe(data []byte) (root *Node, err error) {
var safe []byte
safe = append(safe, data...)
return Unmarshal(safe)
}

//Unmarshal parses the JSON-encoded data and return the root node of struct.
// Unmarshal parses the JSON-encoded data and return the root node of struct.
//
//Doesn't calculate values, just type of stored value. It will store link to the data, on all life long.
// Doesn't calculate values, just type of stored value. It will store link to the data, on all life long.
func Unmarshal(data []byte) (root *Node, err error) {
buf := newBuffer(data)
var (
Expand Down
5 changes: 5 additions & 0 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ func TestUnmarshal_StringSimpleCorrupted(t *testing.T) {
{name: "one quote", input: []byte("\"")},
{name: "one quote char", input: []byte("\"c")},
{name: "wrong quotes", input: []byte("'cat'")},
{name: "double string", input: []byte("\"Hello\" \"World\"")},
{name: "quotes in quotes", input: []byte("\"good \"cat\"\"")},
}
for _, test := range tests {
Expand Down Expand Up @@ -231,6 +232,7 @@ func TestUnmarshal_NullSimpleCorrupted(t *testing.T) {
{name: "NILL", input: []byte("NILL")},
{name: "spaces", input: []byte("Nu ll")},
{name: "null1", input: []byte("null1")},
{name: "double", input: []byte("null null")},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down Expand Up @@ -263,6 +265,7 @@ func TestUnmarshal_BoolSimpleCorrupted(t *testing.T) {
simpleCorrupted("fals"),
simpleCorrupted("tre"),
simpleCorrupted("fal se"),
simpleCorrupted("true false"),
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down Expand Up @@ -345,6 +348,8 @@ func TestUnmarshal_ObjectSimpleCorrupted(t *testing.T) {
simpleCorrupted(`{}1`),
simpleCorrupted(`1{}`),
simpleCorrupted(`{"x"::1}`),
simpleCorrupted(`{null:null}`),
simpleCorrupted(`{"foo:"bar"}`),
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down
Loading

0 comments on commit 6d5bc1b

Please sign in to comment.