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 SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
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}

View File

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

View File

@ -4,50 +4,19 @@ import (
"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")
var results = orderedmap.NewOrderedMap()
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
return crossFunction(d, matchingNodes, pathNode, isEquals)
}
func hasMatch(d *dataTreeNavigator, candidate *CandidateNode, lhs *PathTreeNode, rhs *PathTreeNode) (bool, error) {
childMap := orderedmap.NewOrderedMap()
childMap.Set(candidate.GetKey(), candidate)
childMatches, errChild := d.getMatchingNodes(childMap, lhs)
log.Debug("got the LHS")
if errChild != nil {
return false, errChild
func isEquals(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
value := false
if lhs.Node.Tag == "!!null" {
value = (rhs.Node.Tag == "!!null")
} else {
value = Match(lhs.Node.Value, rhs.Node.Value)
}
// TODO = handle other RHS types
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
log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
return createBooleanCandidate(lhs, value), nil
}

View File

@ -5,35 +5,49 @@ import (
)
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]`,
expression: `(.[] == "*at")`,
document: ``,
expression: `null == null`,
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: ``,
// expression: `null == ~`,
// expected: []string{
// "D0, P[], (!!bool)::true\n",
// },
// },
}
func TestEqualOperatorScenarios(t *testing.T) {

View File

@ -7,7 +7,9 @@ import (
"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)
if err != nil {
return nil, err
@ -26,7 +28,7 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap
for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() {
rhsCandidate := rightEl.Value.(*CandidateNode)
resultCandidate, err := multiply(d, lhsCandidate, rhsCandidate)
resultCandidate, err := calculation(d, lhsCandidate, rhsCandidate)
if err != nil {
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) {
@ -67,8 +74,8 @@ func createTraversalTree(path []interface{}) *PathTreeNode {
}
return &PathTreeNode{
Operation: &Operation{OperationType: Pipe},
Lhs: createTraversalTree(path[0:1]),
Rhs: createTraversalTree(path[1:])}
Lhs: createTraversalTree(path[0:1]),
Rhs: createTraversalTree(path[1:])}
}

View File

@ -6,67 +6,67 @@ import (
var multiplyOperatorScenarios = []expressionScenario{
{
document: `{a: {also: [1]}, b: {also: me}}`,
expression: `.a * .b`,
expected: []string{
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
},
}, {
document: `{a: {also: me}, b: {also: [1]}}`,
expression: `.a * .b`,
expected: []string{
"D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n",
},
}, {
document: `{a: {also: me}, b: {also: {g: wizz}}}`,
expression: `.a * .b`,
expected: []string{
"D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n",
},
}, {
document: `{a: {also: {g: wizz}}, b: {also: me}}`,
expression: `.a * .b`,
expected: []string{
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
},
}, {
document: `{a: {also: {g: wizz}}, b: {also: [1]}}`,
expression: `.a * .b`,
expected: []string{
"D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n",
},
}, {
document: `{a: {also: [1]}, b: {also: {g: wizz}}}`,
expression: `.a * .b`,
expected: []string{
"D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n",
},
}, {
document: `{a: {things: great}, b: {also: me}}`,
expression: `.a * .b`,
expected: []string{
"D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n",
},
}, {
document: `a: {things: great}
b:
also: "me"
`,
expression: `.a * .b`,
expected: []string{
`D0, P[], (!!map)::a:
things: great
also: "me"
b:
also: "me"
`,
},
}, {
document: `{a: [1,2,3], b: [3,4,5]}`,
expression: `.a * .b`,
expected: []string{
"D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n",
},
// document: `{a: {also: [1]}, b: {also: me}}`,
// expression: `.a * .b`,
// expected: []string{
// "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
// },
// }, {
// document: `{a: {also: me}, b: {also: [1]}}`,
// expression: `.a * .b`,
// expected: []string{
// "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n",
// },
// }, {
// document: `{a: {also: me}, b: {also: {g: wizz}}}`,
// expression: `.a * .b`,
// expected: []string{
// "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n",
// },
// }, {
// document: `{a: {also: {g: wizz}}, b: {also: me}}`,
// expression: `.a * .b`,
// expected: []string{
// "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
// },
// }, {
// document: `{a: {also: {g: wizz}}, b: {also: [1]}}`,
// expression: `.a * .b`,
// expected: []string{
// "D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n",
// },
// }, {
// document: `{a: {also: [1]}, b: {also: {g: wizz}}}`,
// expression: `.a * .b`,
// expected: []string{
// "D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n",
// },
// }, {
// document: `{a: {things: great}, b: {also: me}}`,
// expression: `.a * .b`,
// expected: []string{
// "D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n",
// },
// }, {
// document: `a: {things: great}
// b:
// also: "me"
// `,
// expression: `(.a * .b)`,
// expected: []string{
// `D0, P[], (!!map)::a:
// things: great
// also: "me"
// b:
// also: "me"
// `,
// },
// }, {
// document: `{a: [1,2,3], b: [3,4,5]}`,
// expression: `.a * .b`,
// expected: []string{
// "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 {
//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)
newMatches = append(newMatches, &CandidateNode{
Node: valueNode,
@ -145,7 +145,7 @@ func traverseArray(candidate *CandidateNode, pathNode *Operation) ([]*CandidateN
indexToUse := index
contentLength := int64(len(candidate.Node.Content))
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))
}

View File

@ -66,6 +66,23 @@ var traversePathOperatorScenarios = []expressionScenario{
"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) {

View File

@ -3,5 +3,6 @@ package treeops
import "github.com/elliotchance/orderedmap"
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
}

View File

@ -177,6 +177,7 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`length`), opToken(Length))
lexer.Add([]byte(`select`), opToken(Select))
lexer.Add([]byte(`or`), opToken(Or))
lexer.Add([]byte(`not`), opToken(Not))
// lexer.Add([]byte(`and`), opToken())
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": {
"cat": "apple",
"mad": "things"
"also": [
1
]
},
"b": {
"also": "me"
}
}