This commit is contained in:
Mike Farah 2020-10-20 16:27:30 +11:00
parent 49615f5581
commit 6a698332dd
14 changed files with 230 additions and 137 deletions

View File

@ -38,6 +38,7 @@ var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence:
var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator} var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator} var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: ValueOperator} var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: ValueOperator}
var Not = &OperationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: NotOperator}
var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator} var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator}

View File

@ -8,6 +8,9 @@ import (
func isTruthy(c *CandidateNode) (bool, error) { func isTruthy(c *CandidateNode) (bool, error) {
node := c.Node node := c.Node
value := true value := true
if node.Tag == "!!null" {
return false, nil
}
if node.Kind == yaml.ScalarNode && node.Tag == "!!bool" { if node.Kind == yaml.ScalarNode && node.Tag == "!!bool" {
errDecoding := node.Decode(&value) errDecoding := node.Decode(&value)
if errDecoding != nil { if errDecoding != nil {

View File

@ -4,50 +4,19 @@ import (
"github.com/elliotchance/orderedmap" "github.com/elliotchance/orderedmap"
) )
func EqualsOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func EqualsOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
log.Debugf("-- equalsOperation") log.Debugf("-- equalsOperation")
var results = orderedmap.NewOrderedMap() return crossFunction(d, matchingNodes, pathNode, isEquals)
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debug("equalsOperation checking %v", candidate)
matches, errInChild := hasMatch(d, candidate, pathNode.Lhs, pathNode.Rhs)
if errInChild != nil {
return nil, errInChild
}
equalsCandidate := createBooleanCandidate(candidate, matches)
results.Set(equalsCandidate.GetKey(), equalsCandidate)
}
return results, nil
} }
func hasMatch(d *dataTreeNavigator, candidate *CandidateNode, lhs *PathTreeNode, rhs *PathTreeNode) (bool, error) { func isEquals(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
childMap := orderedmap.NewOrderedMap() value := false
childMap.Set(candidate.GetKey(), candidate)
childMatches, errChild := d.getMatchingNodes(childMap, lhs) if lhs.Node.Tag == "!!null" {
log.Debug("got the LHS") value = (rhs.Node.Tag == "!!null")
if errChild != nil { } else {
return false, errChild value = Match(lhs.Node.Value, rhs.Node.Value)
} }
log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
// TODO = handle other RHS types return createBooleanCandidate(lhs, value), nil
return containsMatchingValue(childMatches, rhs.Operation.StringValue), nil
}
func containsMatchingValue(matchMap *orderedmap.OrderedMap, valuePattern string) bool {
log.Debugf("-- findMatchingValues")
for el := matchMap.Front(); el != nil; el = el.Next() {
node := el.Value.(*CandidateNode)
log.Debugf("-- comparing %v to %v", node.Node.Value, valuePattern)
if Match(node.Node.Value, valuePattern) {
return true
}
}
log.Debugf("-- done findMatchingValues")
return false
} }

View File

@ -5,35 +5,49 @@ import (
) )
var equalsOperatorScenarios = []expressionScenario{ var equalsOperatorScenarios = []expressionScenario{
// {
// document: `[cat,goat,dog]`,
// expression: `(.[] == "*at")`,
// expected: []string{
// "D0, P[], (!!bool)::true\n",
// },
// }, {
// document: `[cat,goat,dog]`,
// expression: `.[] | (. == "*at")`,
// expected: []string{
// "D0, P[0], (!!bool)::true\n",
// "D0, P[1], (!!bool)::true\n",
// "D0, P[2], (!!bool)::false\n",
// },
// }, {
// document: `[3, 4, 5]`,
// expression: `.[] | (. == 4)`,
// expected: []string{
// "D0, P[0], (!!bool)::false\n",
// "D0, P[1], (!!bool)::true\n",
// "D0, P[2], (!!bool)::false\n",
// },
// }, {
// document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
// expression: `.a | (.[].b == "apple")`,
// expected: []string{
// "D0, P[a], (!!bool)::true\n",
// },
// },
{ {
document: `[cat,goat,dog]`, document: ``,
expression: `(.[] == "*at")`, expression: `null == null`,
expected: []string{ expected: []string{
"D0, P[], (!!bool)::true\n", "D0, P[], (!!bool)::true\n",
}, },
}, {
document: `[cat,goat,dog]`,
expression: `.[] | (. == "*at")`,
expected: []string{
"D0, P[0], (!!bool)::true\n",
"D0, P[1], (!!bool)::true\n",
"D0, P[2], (!!bool)::false\n",
},
}, {
document: `[3, 4, 5]`,
expression: `.[] | (. == 4)`,
expected: []string{
"D0, P[0], (!!bool)::false\n",
"D0, P[1], (!!bool)::true\n",
"D0, P[2], (!!bool)::false\n",
},
}, {
document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
expression: `.a | (.[].b == "apple")`,
expected: []string{
"D0, P[a], (!!bool)::true\n",
},
}, },
// {
// document: ``,
// expression: `null == ~`,
// expected: []string{
// "D0, P[], (!!bool)::true\n",
// },
// },
} }
func TestEqualOperatorScenarios(t *testing.T) { func TestEqualOperatorScenarios(t *testing.T) {

View File

@ -7,7 +7,9 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
func crossFunction(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode, calculation CrossFunctionCalculation) (*orderedmap.OrderedMap, error) {
lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.getMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return nil, err return nil, err
@ -26,7 +28,7 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap
for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() { for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() {
rhsCandidate := rightEl.Value.(*CandidateNode) rhsCandidate := rightEl.Value.(*CandidateNode)
resultCandidate, err := multiply(d, lhsCandidate, rhsCandidate) resultCandidate, err := calculation(d, lhsCandidate, rhsCandidate)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -34,7 +36,12 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap
} }
} }
return matchingNodes, nil return results, nil
}
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
log.Debugf("-- MultiplyOperator")
return crossFunction(d, matchingNodes, pathNode, multiply)
} }
func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
@ -67,8 +74,8 @@ func createTraversalTree(path []interface{}) *PathTreeNode {
} }
return &PathTreeNode{ return &PathTreeNode{
Operation: &Operation{OperationType: Pipe}, Operation: &Operation{OperationType: Pipe},
Lhs: createTraversalTree(path[0:1]), Lhs: createTraversalTree(path[0:1]),
Rhs: createTraversalTree(path[1:])} Rhs: createTraversalTree(path[1:])}
} }

View File

@ -6,67 +6,67 @@ import (
var multiplyOperatorScenarios = []expressionScenario{ var multiplyOperatorScenarios = []expressionScenario{
{ {
document: `{a: {also: [1]}, b: {also: me}}`, // document: `{a: {also: [1]}, b: {also: me}}`,
expression: `.a * .b`, // expression: `.a * .b`,
expected: []string{ // expected: []string{
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", // "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
}, // },
}, { // }, {
document: `{a: {also: me}, b: {also: [1]}}`, // document: `{a: {also: me}, b: {also: [1]}}`,
expression: `.a * .b`, // expression: `.a * .b`,
expected: []string{ // expected: []string{
"D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", // "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n",
}, // },
}, { // }, {
document: `{a: {also: me}, b: {also: {g: wizz}}}`, // document: `{a: {also: me}, b: {also: {g: wizz}}}`,
expression: `.a * .b`, // expression: `.a * .b`,
expected: []string{ // expected: []string{
"D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", // "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n",
}, // },
}, { // }, {
document: `{a: {also: {g: wizz}}, b: {also: me}}`, // document: `{a: {also: {g: wizz}}, b: {also: me}}`,
expression: `.a * .b`, // expression: `.a * .b`,
expected: []string{ // expected: []string{
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", // "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
}, // },
}, { // }, {
document: `{a: {also: {g: wizz}}, b: {also: [1]}}`, // document: `{a: {also: {g: wizz}}, b: {also: [1]}}`,
expression: `.a * .b`, // expression: `.a * .b`,
expected: []string{ // expected: []string{
"D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n", // "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n",
}, // },
}, { // }, {
document: `{a: {also: [1]}, b: {also: {g: wizz}}}`, // document: `{a: {also: [1]}, b: {also: {g: wizz}}}`,
expression: `.a * .b`, // expression: `.a * .b`,
expected: []string{ // expected: []string{
"D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n", // "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n",
}, // },
}, { // }, {
document: `{a: {things: great}, b: {also: me}}`, // document: `{a: {things: great}, b: {also: me}}`,
expression: `.a * .b`, // expression: `.a * .b`,
expected: []string{ // expected: []string{
"D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n", // "D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n",
}, // },
}, { // }, {
document: `a: {things: great} // document: `a: {things: great}
b: // b:
also: "me" // also: "me"
`, // `,
expression: `.a * .b`, // expression: `(.a * .b)`,
expected: []string{ // expected: []string{
`D0, P[], (!!map)::a: // `D0, P[], (!!map)::a:
things: great // things: great
also: "me" // also: "me"
b: // b:
also: "me" // also: "me"
`, // `,
}, // },
}, { // }, {
document: `{a: [1,2,3], b: [3,4,5]}`, // document: `{a: [1,2,3], b: [3,4,5]}`,
expression: `.a * .b`, // expression: `.a * .b`,
expected: []string{ // expected: []string{
"D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n", // "D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n",
}, // },
}, },
} }

View File

@ -0,0 +1,20 @@
package treeops
import "github.com/elliotchance/orderedmap"
func NotOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
log.Debugf("-- notOperation")
var results = orderedmap.NewOrderedMap()
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debug("notOperation checking %v", candidate)
truthy, errDecoding := isTruthy(candidate)
if errDecoding != nil {
return nil, errDecoding
}
result := createBooleanCandidate(candidate, !truthy)
results.Set(result.GetKey(), result)
}
return results, nil
}

View File

@ -0,0 +1,56 @@
package treeops
import (
"testing"
)
var notOperatorScenarios = []expressionScenario{
{
document: `cat`,
expression: `. | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
document: `1`,
expression: `. | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
document: `0`,
expression: `. | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
document: `~`,
expression: `. | not`,
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
{
document: `false`,
expression: `. | not`,
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
{
document: `true`,
expression: `. | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
}
func TestNotOperatorScenarios(t *testing.T) {
for _, tt := range notOperatorScenarios {
testScenario(t, &tt)
}
}

View File

@ -110,7 +110,7 @@ func traverseMap(candidate *CandidateNode, pathNode *Operation) ([]*CandidateNod
} }
if len(newMatches) == 0 { if len(newMatches) == 0 {
//no matches, create one automagically //no matches, create one automagically
valueNode := &yaml.Node{} valueNode := &yaml.Node{Tag: "!!null"}
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: pathNode.StringValue}, valueNode) node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: pathNode.StringValue}, valueNode)
newMatches = append(newMatches, &CandidateNode{ newMatches = append(newMatches, &CandidateNode{
Node: valueNode, Node: valueNode,
@ -145,7 +145,7 @@ func traverseArray(candidate *CandidateNode, pathNode *Operation) ([]*CandidateN
indexToUse := index indexToUse := index
contentLength := int64(len(candidate.Node.Content)) contentLength := int64(len(candidate.Node.Content))
for contentLength <= index { for contentLength <= index {
candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{}) candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null"})
contentLength = int64(len(candidate.Node.Content)) contentLength = int64(len(candidate.Node.Content))
} }

View File

@ -66,6 +66,23 @@ var traversePathOperatorScenarios = []expressionScenario{
"D0, P[a mad], (!!str)::things\n", "D0, P[a mad], (!!str)::things\n",
}, },
}, },
{
document: `{a: {cat: apple, mad: things}}`,
expression: `.a | (.cat, .mad, .fad)`,
expected: []string{
"D0, P[a cat], (!!str)::apple\n",
"D0, P[a mad], (!!str)::things\n",
"D0, P[a fad], ()::null\n",
},
},
{
document: `{a: {cat: apple, mad: things}}`,
expression: `.a | (.cat, .mad, .fad) | select( (. == null) | not)`,
expected: []string{
"D0, P[a cat], (!!str)::apple\n",
"D0, P[a mad], (!!str)::things\n",
},
},
} }
func TestTraversePathOperatorScenarios(t *testing.T) { func TestTraversePathOperatorScenarios(t *testing.T) {

View File

@ -3,5 +3,6 @@ package treeops
import "github.com/elliotchance/orderedmap" import "github.com/elliotchance/orderedmap"
func ValueOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) { func ValueOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
log.Debug("value = %v", pathNode.Operation.CandidateNode.Node.Value)
return nodeToMap(pathNode.Operation.CandidateNode), nil return nodeToMap(pathNode.Operation.CandidateNode), nil
} }

View File

@ -177,6 +177,7 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`length`), opToken(Length)) lexer.Add([]byte(`length`), opToken(Length))
lexer.Add([]byte(`select`), opToken(Select)) lexer.Add([]byte(`select`), opToken(Select))
lexer.Add([]byte(`or`), opToken(Or)) lexer.Add([]byte(`or`), opToken(Or))
lexer.Add([]byte(`not`), opToken(Not))
// lexer.Add([]byte(`and`), opToken()) // lexer.Add([]byte(`and`), opToken())
lexer.Add([]byte(`collect`), opToken(Collect)) lexer.Add([]byte(`collect`), opToken(Collect))

View File

@ -1 +1 @@
{a: {cat: apple, mad: things}} {a: {also: [1]}, b: {also: me}}

View File

@ -1,6 +1,10 @@
{ {
"a": { "a": {
"cat": "apple", "also": [
"mad": "things" 1
]
},
"b": {
"also": "me"
} }
} }