diff --git a/buffer.go b/buffer.go index 08b3c82..317e1e1 100644 --- a/buffer.go +++ b/buffer.go @@ -110,29 +110,19 @@ var ( } operations = map[string]operation{ - //"!": func(left *Node, right *Node) (result *Node, err error) { - // if right != nil { - // return nil, errorRequest("factorial MUST be called via one Node argument") - // } - // num, err := left.getUInteger() - // if err != nil { - // return - // } - // return varNode(left, "factorial", Numeric, float64(mathFactorial(num))), nil - //}, "**": func(left *Node, right *Node) (result *Node, err error) { lnum, rnum, err := _floats(left, right) if err != nil { return } - return varNode(left, "power", Numeric, math.Pow(lnum, rnum)), nil + return varNode(nil, "power", Numeric, math.Pow(lnum, rnum)), nil }, "*": func(left *Node, right *Node) (result *Node, err error) { lnum, rnum, err := _floats(left, right) if err != nil { return } - return varNode(left, "multiply", Numeric, float64(lnum*rnum)), nil + return varNode(nil, "multiply", Numeric, float64(lnum*rnum)), nil }, "/": func(left *Node, right *Node) (result *Node, err error) { lnum, rnum, err := _floats(left, right) @@ -142,7 +132,7 @@ var ( if rnum == 0 { return nil, errorRequest("division by zero") } - return varNode(left, "division", Numeric, float64(lnum/rnum)), nil + return varNode(nil, "division", Numeric, float64(lnum/rnum)), nil }, "%": func(left *Node, right *Node) (result *Node, err error) { lnum, err := left.getInteger() @@ -153,7 +143,7 @@ var ( if err != nil { return } - return varNode(left, "remainder", Numeric, float64(lnum%rnum)), nil + return varNode(nil, "remainder", Numeric, float64(lnum%rnum)), nil }, "<<": func(left *Node, right *Node) (result *Node, err error) { lnum, err := left.getInteger() @@ -164,7 +154,7 @@ var ( if err != nil { return } - return varNode(left, "left shift", Numeric, float64(lnum<>": func(left *Node, right *Node) (result *Node, err error) { lnum, err := left.getInteger() @@ -175,35 +165,35 @@ var ( if err != nil { return } - return varNode(left, "right shift", Numeric, float64(lnum>>rnum)), nil + return varNode(nil, "right shift", Numeric, float64(lnum>>rnum)), nil }, "&": func(left *Node, right *Node) (result *Node, err error) { lnum, rnum, err := _ints(left, right) if err != nil { return } - return varNode(left, "bitwise AND", Numeric, float64(lnum&rnum)), nil + return varNode(nil, "bitwise AND", Numeric, float64(lnum&rnum)), nil }, "&^": func(left *Node, right *Node) (result *Node, err error) { lnum, rnum, err := _ints(left, right) if err != nil { return } - return varNode(left, "bit clear (AND NOT)", Numeric, float64(lnum&rnum)), nil + return varNode(nil, "bit clear (AND NOT)", Numeric, float64(lnum&rnum)), nil }, "+": func(left *Node, right *Node) (result *Node, err error) { lnum, rnum, err := _floats(left, right) if err != nil { return } - return varNode(left, "sum", Numeric, float64(lnum+rnum)), nil + return varNode(nil, "sum", Numeric, float64(lnum+rnum)), nil }, "-": func(left *Node, right *Node) (result *Node, err error) { lnum, rnum, err := _floats(left, right) if err != nil { return } - return varNode(left, "sub", Numeric, float64(lnum-rnum)), nil + return varNode(nil, "sub", Numeric, float64(lnum-rnum)), nil }, "|": func(left *Node, right *Node) (result *Node, err error) { if left.IsNumeric() && right.IsNumeric() { @@ -211,7 +201,7 @@ var ( if err != nil { return nil, err } - return varNode(left, "bitwise OR", Numeric, float64(lnum|rnum)), nil + return varNode(nil, "bitwise OR", Numeric, float64(lnum|rnum)), nil } return nil, errorRequest("function 'bitwise OR' was called from non numeric node") }, @@ -221,7 +211,7 @@ var ( if err != nil { return nil, err } - return varNode(left, "bitwise XOR", Numeric, float64(lnum^rnum)), nil + return varNode(nil, "bitwise XOR", Numeric, float64(lnum^rnum)), nil } return nil, errorRequest("function 'bitwise XOR' was called from non numeric node") }, @@ -230,42 +220,42 @@ var ( if err != nil { return nil, err } - return varNode(left, "eq", Bool, bool(res)), nil + return varNode(nil, "eq", Bool, bool(res)), nil }, "!=": func(left *Node, right *Node) (result *Node, err error) { res, err := left.Eq(right) if err != nil { return nil, err } - return varNode(left, "neq", Bool, bool(!res)), nil + return varNode(nil, "neq", Bool, bool(!res)), nil }, "<": func(left *Node, right *Node) (result *Node, err error) { res, err := left.Le(right) if err != nil { return nil, err } - return varNode(left, "le", Bool, bool(!res)), nil + return varNode(nil, "le", Bool, bool(!res)), nil }, "<=": func(left *Node, right *Node) (result *Node, err error) { res, err := left.Leq(right) if err != nil { return nil, err } - return varNode(left, "leq", Bool, bool(!res)), nil + return varNode(nil, "leq", Bool, bool(!res)), nil }, ">": func(left *Node, right *Node) (result *Node, err error) { res, err := left.Ge(right) if err != nil { return nil, err } - return varNode(left, "ge", Bool, bool(!res)), nil + return varNode(nil, "ge", Bool, bool(!res)), nil }, ">=": func(left *Node, right *Node) (result *Node, err error) { res, err := left.Geq(right) if err != nil { return nil, err } - return varNode(left, "geq", Bool, bool(!res)), nil + return varNode(nil, "geq", Bool, bool(!res)), nil }, "&&": func(left *Node, right *Node) (result *Node, err error) { res := false @@ -280,7 +270,7 @@ var ( } res = rval } - return varNode(left, "AND", Bool, bool(!res)), nil + return varNode(nil, "AND", Bool, bool(!res)), nil }, "||": func(left *Node, right *Node) (result *Node, err error) { res := true @@ -295,7 +285,7 @@ var ( } res = rval } - return varNode(left, "OR", Bool, bool(!res)), nil + return varNode(nil, "OR", Bool, bool(!res)), nil }, } @@ -306,7 +296,7 @@ var ( if err != nil { return nil, err } - return varNode(node, "sin", Numeric, math.Sin(num)), nil + return varNode(nil, "sin", Numeric, math.Sin(num)), nil } return nil, errorRequest("function 'sin' was called from non numeric node") }, @@ -316,7 +306,7 @@ var ( if err != nil { return nil, err } - return varNode(node, "cos", Numeric, math.Cos(num)), nil + return varNode(nil, "cos", Numeric, math.Cos(num)), nil } return nil, errorRequest("function 'cos' was called from non numeric node") }, @@ -326,6 +316,13 @@ var ( } return nil, errorRequest("function 'length' was called from non array node") }, + "factorial": func(node *Node) (result *Node, err error) { + num, err := node.getUInteger() + if err != nil { + return + } + return varNode(nil, "factorial", Numeric, float64(mathFactorial(num))), nil + }, } constants = map[string]*Node{ "pi": varNode(nil, "pi", Numeric, float64(math.Pi)), @@ -494,12 +491,14 @@ func (b *buffer) token() (err error) { c byte stack = make([]byte, 0) start int + find bool ) tokenLoop: for ; b.index < b.length; b.index++ { c = b.data[b.index] switch { case c == quote: + find = true start = b.index err = b.step() if err != nil { @@ -511,31 +510,40 @@ tokenLoop: } b.index = start case c == bracketL: + find = true stack = append(stack, c) case c == bracketR: + find = true if len(stack) == 0 || stack[len(stack)-1] != bracketL { return b.errorSymbol() } stack = stack[:len(stack)-1] case c == parenthesesL: + find = true stack = append(stack, c) case c == parenthesesR: + find = true if len(stack) == 0 || stack[len(stack)-1] != parenthesesL { return b.errorSymbol() } stack = stack[:len(stack)-1] case c == dot || c == at || c == dollar || c == question || c == asterisk || (c >= 'A' && c <= 'z') || (c >= '0' && c <= '9'): // standard token name + find = true continue case len(stack) != 0: + find = true continue case c == minus || c == plus: - start = b.index - err = b.numeric() - if err == nil || err == io.EOF { - b.index-- - continue + if !find { + find = true + start = b.index + err = b.numeric() + if err == nil || err == io.EOF { + b.index-- + continue + } + b.index = start } - b.index = start fallthrough default: break tokenLoop @@ -642,12 +650,14 @@ func (b *buffer) rpn() (result rpn, err error) { if err != io.EOF { return nil, err } + } + current = string(b.data[start:b.index]) + result = append(result, current) + if err != nil { err = nil } else { b.index-- } - current = string(b.data[start:b.index]) - result = append(result, current) case c == parenthesesL: // ( variable = false current = string(c) @@ -728,12 +738,12 @@ func (b *buffer) errorSymbol() error { return errorSymbol(b) } -//func mathFactorial(x uint) uint { -// if x == 0 { -// return 1 -// } -// return x * mathFactorial(x-1) -//} +func mathFactorial(x uint) uint { + if x == 0 { + return 1 + } + return x * mathFactorial(x-1) +} func _floats(left, right *Node) (lnum, rnum float64, err error) { lnum, err = left.GetNumeric() diff --git a/buffer_test.go b/buffer_test.go index 7b04e7f..c6741f2 100644 --- a/buffer_test.go +++ b/buffer_test.go @@ -21,6 +21,7 @@ func TestBuffer_Token(t *testing.T) { {name: "part 1", value: "@.foo+@.bar", index: 5, fail: false}, {name: "part 2", value: "@.foo && @.bar", index: 5, fail: false}, {name: "part 3", value: "@.foo,3", index: 5, fail: false}, + {name: "part 4", value: "@.length-1", index: 8, fail: false}, {name: "number 1", value: "1", index: 1, fail: false}, {name: "number 2", value: "1.3e2", index: 5, fail: false}, @@ -61,6 +62,7 @@ func TestBuffer_RPN(t *testing.T) { {name: "example_5", value: "pi != 'bar'", expected: []string{"pi", "'bar'", "!="}}, {name: "example_6", value: "3 + 4 * -2 / (-1 - 5)**-2", expected: []string{"3", "4", "-2", "*", "-1", "5", "-", "-2", "**", "/", "+"}}, {name: "example_7", value: "1.3e2 + sin(2*pi/3)", expected: []string{"1.3e2", "2", "pi", "*", "3", "/", "sin", "+"}}, + {name: "example_8", value: "@.length-1", expected: []string{"@.length", "1", "-"}}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/jsonpath.go b/jsonpath.go index 5f39637..625ca65 100644 --- a/jsonpath.go +++ b/jsonpath.go @@ -158,7 +158,8 @@ func deReference(node *Node, commands []string) (result []*Node, err error) { c byte key string ok bool - value *Node + value, temp *Node + float float64 ) for i, cmd := range commands { switch { @@ -182,7 +183,7 @@ func deReference(node *Node, commands []string) (result []*Node, err error) { temporary = append(temporary, element.inheritors()...) } result = temporary - case strings.Contains(cmd, ":"): // array slice operator + case strings.Contains(cmd, ":"): // fixme:array slice operator keys = strings.Split(cmd, ":") if len(keys) > 3 { return nil, errorRequest("slice must contains no more than 2 colons, got '%s'", cmd) @@ -244,16 +245,20 @@ func deReference(node *Node, commands []string) (result []*Node, err error) { } temporary = make([]*Node, 0) for _, element := range result { - value, err = eval(element, rpn, cmd) - if err != nil { - return nil, errorRequest("wrong request: %s", cmd) - } - if value != nil { - ok, err = boolean(value) - if err != nil || !ok { - continue + if element.isContainer() { + for _, temp = range element.inheritors() { + value, err = eval(temp, rpn, cmd) + if err != nil { + return nil, errorRequest("wrong request: %s", cmd) + } + if value != nil { + ok, err = boolean(value) + if err != nil || !ok { + continue + } + temporary = append(temporary, temp) + } } - temporary = append(temporary, element) } } result = temporary @@ -265,12 +270,51 @@ func deReference(node *Node, commands []string) (result []*Node, err error) { } temporary = make([]*Node, 0) for _, element := range result { - value, err = eval(element, rpn, cmd) + if !element.isContainer() { + continue + } + temp, err = eval(element, rpn, cmd) if err != nil { return nil, errorRequest("wrong request: %s", cmd) } - if value != nil { - temporary = append(temporary, value) + if temp != nil { + value = nil + switch temp.Type() { + case String: + key, err = element.GetString() + if err != nil { + return nil, errorRequest("wrong type convert: %s", err.Error()) + } + value, _ = element.children[key] + case Numeric: + from, err = temp.getInteger() + if err == nil { // INTEGER + if from < 0 { + key = strconv.Itoa(element.Size() - from) + } else { + key = strconv.Itoa(from) + } + } else { + float, err = temp.GetNumeric() + if err != nil { + return nil, errorRequest("wrong type convert: %s", err.Error()) + } + key = strconv.FormatFloat(float, 'g', -1, 64) + } + value, _ = element.children[key] + case Bool: + ok, err = temp.GetBool() + if err != nil { + return nil, errorRequest("wrong type convert: %s", err.Error()) + } + if ok { + temporary = append(temporary, element.inheritors()...) + } + continue + } + if value != nil { + temporary = append(temporary, value) + } } } result = temporary @@ -318,6 +362,7 @@ func deReference(node *Node, commands []string) (result []*Node, err error) { if err != nil { return } + ok = true } else { from, err = strconv.Atoi(key) if err != nil { @@ -330,6 +375,7 @@ func deReference(node *Node, commands []string) (result []*Node, err error) { value, ok = element.children[key] } } + } else if element.IsObject() { value, ok = element.children[key] } diff --git a/jsonpath_test.go b/jsonpath_test.go index 9da646b..299e227 100644 --- a/jsonpath_test.go +++ b/jsonpath_test.go @@ -64,36 +64,39 @@ func TestJsonPath(t *testing.T) { path string expected string }{ - //{name: "root", path: "$", expected: "[$]"}, - //{name: "roots", path: "$.", expected: "[$]"}, - //{name: "all objects", path: "$..", expected: "[$, $['store'], $['store']['bicycle'], $['store']['book'], $['store']['book'][0], $['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, - //{name: "only children", path: "$.*", expected: "[$['store']]"}, - // - //{name: "by key", path: "$.store.bicycle", expected: "[$['store']['bicycle']]"}, - //{name: "all key 1", path: "$..bicycle", expected: "[$['store']['bicycle']]"}, - //{name: "all key 2", path: "$..price", expected: "[$['store']['bicycle']['price'], $['store']['book'][0]['price'], $['store']['book'][1]['price'], $['store']['book'][2]['price'], $['store']['book'][3]['price']]"}, - //{name: "all key bracket", path: "$..['price']", expected: "[$['store']['bicycle']['price'], $['store']['book'][0]['price'], $['store']['book'][1]['price'], $['store']['book'][2]['price'], $['store']['book'][3]['price']]"}, - //{name: "all fields", path: "$['store']['book'][1].*", expected: "[$['store']['book'][1]['author'], $['store']['book'][1]['category'], $['store']['book'][1]['price'], $['store']['book'][1]['title']]"}, - // - //{name: "union fields", path: "$['store']['book'][2]['author','price','title']", expected: "[$['store']['book'][2]['author'], $['store']['book'][2]['price'], $['store']['book'][2]['title']]"}, - //{name: "union indexes", path: "$['store']['book'][1,2]", expected: "[$['store']['book'][1], $['store']['book'][2]]"}, - // - //{name: "slices 1", path: "$..[1:4]", expected: "[$['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, - //{name: "slices 2", path: "$..[1:4:]", expected: "[$['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, - //{name: "slices 3", path: "$..[1:4:1]", expected: "[$['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, - //{name: "slices 4", path: "$..[1:]", expected: "[$['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, - //{name: "slices 5", path: "$..[:2]", expected: "[$['store']['book'][0], $['store']['book'][1]]"}, - //{name: "slices 6", path: "$..[:4:2]", expected: "[$['store']['book'][0], $['store']['book'][2]]"}, - //{name: "slices 7", path: "$..[:4:]", expected: "[$['store']['book'][0], $['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, - //{name: "slices 8", path: "$..[::]", expected: "[$['store']['book'][0], $['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, - //{name: "slices 9", path: "$['store']['book'][1:4:2]", expected: "[$['store']['book'][1], $['store']['book'][3]]"}, - //{name: "slices 10", path: "$['store']['book'][1:4:3]", expected: "[$['store']['book'][1]]"}, - //{name: "slices 11", path: "$['store']['book'][:-1]", expected: "[$['store']['book'][0], $['store']['book'][1], $['store']['book'][2]]"}, - //{name: "slices 12", path: "$['store']['book'][-1:]", expected: "[$['store']['book'][3]]"}, - // - //{name: "length", path: "$['store']['book'].length", expected: "[$['store']['book']['length']]"}, - // - {name: "calculated 1", path: "$['store']['book'][(@.length-1)]", expected: "[$['store']['book'][2]]"}, + {name: "root", path: "$", expected: "[$]"}, + {name: "roots", path: "$.", expected: "[$]"}, + {name: "all objects", path: "$..", expected: "[$, $['store'], $['store']['bicycle'], $['store']['book'], $['store']['book'][0], $['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, + {name: "only children", path: "$.*", expected: "[$['store']]"}, + + {name: "by key", path: "$.store.bicycle", expected: "[$['store']['bicycle']]"}, + {name: "all key 1", path: "$..bicycle", expected: "[$['store']['bicycle']]"}, + {name: "all key 2", path: "$..price", expected: "[$['store']['bicycle']['price'], $['store']['book'][0]['price'], $['store']['book'][1]['price'], $['store']['book'][2]['price'], $['store']['book'][3]['price']]"}, + {name: "all key bracket", path: "$..['price']", expected: "[$['store']['bicycle']['price'], $['store']['book'][0]['price'], $['store']['book'][1]['price'], $['store']['book'][2]['price'], $['store']['book'][3]['price']]"}, + {name: "all fields", path: "$['store']['book'][1].*", expected: "[$['store']['book'][1]['author'], $['store']['book'][1]['category'], $['store']['book'][1]['price'], $['store']['book'][1]['title']]"}, + + {name: "union fields", path: "$['store']['book'][2]['author','price','title']", expected: "[$['store']['book'][2]['author'], $['store']['book'][2]['price'], $['store']['book'][2]['title']]"}, + {name: "union indexes", path: "$['store']['book'][1,2]", expected: "[$['store']['book'][1], $['store']['book'][2]]"}, + + {name: "slices 1", path: "$..[1:4]", expected: "[$['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, + {name: "slices 2", path: "$..[1:4:]", expected: "[$['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, + {name: "slices 3", path: "$..[1:4:1]", expected: "[$['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, + {name: "slices 4", path: "$..[1:]", expected: "[$['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, + {name: "slices 5", path: "$..[:2]", expected: "[$['store']['book'][0], $['store']['book'][1]]"}, + {name: "slices 6", path: "$..[:4:2]", expected: "[$['store']['book'][0], $['store']['book'][2]]"}, + {name: "slices 7", path: "$..[:4:]", expected: "[$['store']['book'][0], $['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, + {name: "slices 8", path: "$..[::]", expected: "[$['store']['book'][0], $['store']['book'][1], $['store']['book'][2], $['store']['book'][3]]"}, + {name: "slices 9", path: "$['store']['book'][1:4:2]", expected: "[$['store']['book'][1], $['store']['book'][3]]"}, + {name: "slices 10", path: "$['store']['book'][1:4:3]", expected: "[$['store']['book'][1]]"}, + {name: "slices 11", path: "$['store']['book'][:-1]", expected: "[$['store']['book'][0], $['store']['book'][1], $['store']['book'][2]]"}, + {name: "slices 12", path: "$['store']['book'][-1:]", expected: "[$['store']['book'][3]]"}, + + {name: "length", path: "$['store']['book'].length", expected: "[$['store']['book']['length']]"}, + + {name: "calculated 1", path: "$['store']['book'][(@.length-1)]", expected: "[$['store']['book'][3]]"}, + {name: "calculated 2", path: "$['store']['book'][(3.5 - 3/2)]", expected: "[$['store']['book'][2]]"}, + {name: "calculated 3", path: "$..book[?(@.isbn)]", expected: "[$['store']['book'][2], $['store']['book'][3]]"}, + {name: "calculated 4", path: "$..book[:(factorial(2))]", expected: "[$['store']['book'][0], $['store']['book'][1], $['store']['book'][2]]"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/node.go b/node.go index cf02c74..8e1696a 100644 --- a/node.go +++ b/node.go @@ -470,7 +470,10 @@ func (n *Node) Empty() bool { // Path returns full JsonPath of current Node func (n *Node) Path() string { if n.parent == nil { - return "$" + if n.key == nil { + return "$" + } + return n.Key() } if n.key != nil { return n.parent.Path() + "['" + n.Key() + "']"