Skip to content

Commit 005b097

Browse files
authored
Boolean fix (#1148)
* Fixing booleans * Fixed "and", "or" evaluating RHS when not required
1 parent e08b680 commit 005b097

File tree

4 files changed

+115
-58
lines changed

4 files changed

+115
-58
lines changed

examples/data1.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
cat: purrs
1+
a: mike
2+
b: [t, f]

pkg/yqlib/operator_booleans.go

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,41 +27,48 @@ func isTruthy(c *CandidateNode) (bool, error) {
2727
return isTruthyNode(node)
2828
}
2929

30-
type boolOp func(bool, bool) bool
30+
func getBoolean(candidate *CandidateNode) (bool, error) {
31+
if candidate != nil {
32+
candidate.Node = unwrapDoc(candidate.Node)
33+
return isTruthy(candidate)
34+
}
35+
return false, nil
36+
}
3137

32-
func performBoolOp(op boolOp) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
33-
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
34-
owner := lhs
38+
func getOwner(lhs *CandidateNode, rhs *CandidateNode) *CandidateNode {
39+
owner := lhs
3540

36-
if lhs == nil && rhs == nil {
37-
owner = &CandidateNode{}
38-
} else if lhs == nil {
39-
owner = rhs
40-
}
41+
if lhs == nil && rhs == nil {
42+
owner = &CandidateNode{}
43+
} else if lhs == nil {
44+
owner = rhs
45+
}
46+
return owner
47+
}
4148

42-
var errDecoding error
43-
lhsTrue := false
44-
if lhs != nil {
45-
lhs.Node = unwrapDoc(lhs.Node)
46-
lhsTrue, errDecoding = isTruthy(lhs)
49+
func returnRhsTruthy(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
50+
owner := getOwner(lhs, rhs)
51+
rhsBool, err := getBoolean(rhs)
52+
if err != nil {
53+
return nil, err
54+
}
4755

48-
if errDecoding != nil {
49-
return nil, errDecoding
50-
}
56+
return createBooleanCandidate(owner, rhsBool), nil
57+
}
58+
59+
func returnLHSWhen(targetBool bool) func(lhs *CandidateNode) (*CandidateNode, error) {
60+
return func(lhs *CandidateNode) (*CandidateNode, error) {
61+
var err error
62+
var lhsBool bool
63+
64+
if lhsBool, err = getBoolean(lhs); err != nil || lhsBool != targetBool {
65+
return nil, err
5166
}
52-
log.Debugf("-- lhsTrue", lhsTrue)
53-
54-
rhsTrue := false
55-
if rhs != nil {
56-
rhs.Node = unwrapDoc(rhs.Node)
57-
rhsTrue, errDecoding = isTruthy(rhs)
58-
if errDecoding != nil {
59-
return nil, errDecoding
60-
}
67+
owner := &CandidateNode{}
68+
if lhs != nil {
69+
owner = lhs
6170
}
62-
log.Debugf("-- rhsTrue", rhsTrue)
63-
64-
return createBooleanCandidate(owner, op(lhsTrue, rhsTrue)), nil
71+
return createBooleanCandidate(owner, targetBool), nil
6572
}
6673
}
6774

@@ -133,20 +140,21 @@ func anyOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
133140
}
134141

135142
func orOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
136-
log.Debugf("-- orOp")
137-
return crossFunction(d, context.ReadOnlyClone(), expressionNode, performBoolOp(
138-
func(b1 bool, b2 bool) bool {
139-
log.Debugf("-- peformingOrOp with %v and %v", b1, b2)
140-
return b1 || b2
141-
}), true)
143+
prefs := crossFunctionPreferences{
144+
CalcWhenEmpty: true,
145+
Calculation: returnRhsTruthy,
146+
LhsResultValue: returnLHSWhen(true),
147+
}
148+
return crossFunctionWithPrefs(d, context.ReadOnlyClone(), expressionNode, prefs)
142149
}
143150

144151
func andOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
145-
log.Debugf("-- AndOp")
146-
return crossFunction(d, context.ReadOnlyClone(), expressionNode, performBoolOp(
147-
func(b1 bool, b2 bool) bool {
148-
return b1 && b2
149-
}), true)
152+
prefs := crossFunctionPreferences{
153+
CalcWhenEmpty: true,
154+
Calculation: returnRhsTruthy,
155+
LhsResultValue: returnLHSWhen(false),
156+
}
157+
return crossFunctionWithPrefs(d, context.ReadOnlyClone(), expressionNode, prefs)
150158
}
151159

152160
func notOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {

pkg/yqlib/operator_booleans_test.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ var booleanOperatorScenarios = []expressionScenario{
4444
"D0, P[], (doc)::b: hi\n",
4545
},
4646
},
47+
{
48+
skipDoc: true,
49+
description: "And should not run 2nd arg if first is false",
50+
expression: `false and test(3)`,
51+
expected: []string{
52+
"D0, P[], (!!bool)::false\n",
53+
},
54+
},
55+
{
56+
skipDoc: true,
57+
description: "Or should not run 2nd arg if first is true",
58+
expression: `true or test(3)`,
59+
expected: []string{
60+
"D0, P[], (!!bool)::true\n",
61+
},
62+
},
4763
{
4864
description: "`and` example",
4965
expression: `true and false`,
@@ -151,12 +167,21 @@ var booleanOperatorScenarios = []expressionScenario{
151167
document: `{a: true, b: false}`,
152168
expression: `.[] or (false, true)`,
153169
expected: []string{
154-
"D0, P[a], (!!bool)::true\n",
155170
"D0, P[a], (!!bool)::true\n",
156171
"D0, P[b], (!!bool)::false\n",
157172
"D0, P[b], (!!bool)::true\n",
158173
},
159174
},
175+
{
176+
skipDoc: true,
177+
document: `{a: true, b: false}`,
178+
expression: `.[] and (false, true)`,
179+
expected: []string{
180+
"D0, P[a], (!!bool)::false\n",
181+
"D0, P[a], (!!bool)::true\n",
182+
"D0, P[b], (!!bool)::false\n",
183+
},
184+
},
160185
{
161186
skipDoc: true,
162187
document: `{}`,

pkg/yqlib/operators.go

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,25 @@ func emptyOperator(d *dataTreeNavigator, context Context, expressionNode *Expres
5454

5555
type crossFunctionCalculation func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
5656

57-
func resultsForRHS(d *dataTreeNavigator, context Context, lhsCandidate *CandidateNode, rhs Context, calculation crossFunctionCalculation, results *list.List, calcWhenEmpty bool) error {
57+
func resultsForRHS(d *dataTreeNavigator, context Context, lhsCandidate *CandidateNode, prefs crossFunctionPreferences, rhsExp *ExpressionNode, results *list.List) error {
5858

59-
if calcWhenEmpty && rhs.MatchingNodes.Len() == 0 {
60-
resultCandidate, err := calculation(d, context, lhsCandidate, nil)
59+
if prefs.LhsResultValue != nil {
60+
result, err := prefs.LhsResultValue(lhsCandidate)
61+
if err != nil {
62+
return err
63+
} else if result != nil {
64+
results.PushBack(result)
65+
return nil
66+
}
67+
}
68+
69+
rhs, err := d.GetMatchingNodes(context, rhsExp)
70+
if err != nil {
71+
return err
72+
}
73+
74+
if prefs.CalcWhenEmpty && rhs.MatchingNodes.Len() == 0 {
75+
resultCandidate, err := prefs.Calculation(d, context, lhsCandidate, nil)
6176
if err != nil {
6277
return err
6378
}
@@ -70,7 +85,7 @@ func resultsForRHS(d *dataTreeNavigator, context Context, lhsCandidate *Candidat
7085
for rightEl := rhs.MatchingNodes.Front(); rightEl != nil; rightEl = rightEl.Next() {
7186
log.Debugf("Applying calc")
7287
rhsCandidate := rightEl.Value.(*CandidateNode)
73-
resultCandidate, err := calculation(d, context, lhsCandidate, rhsCandidate)
88+
resultCandidate, err := prefs.Calculation(d, context, lhsCandidate, rhsCandidate)
7489
if err != nil {
7590
return err
7691
}
@@ -81,22 +96,24 @@ func resultsForRHS(d *dataTreeNavigator, context Context, lhsCandidate *Candidat
8196
return nil
8297
}
8398

84-
func doCrossFunc(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, calculation crossFunctionCalculation, calcWhenEmpty bool) (Context, error) {
99+
type crossFunctionPreferences struct {
100+
CalcWhenEmpty bool
101+
// if this returns a result node,
102+
// we wont bother calculating the RHS
103+
LhsResultValue func(*CandidateNode) (*CandidateNode, error)
104+
Calculation crossFunctionCalculation
105+
}
106+
107+
func doCrossFunc(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, prefs crossFunctionPreferences) (Context, error) {
85108
var results = list.New()
86109
lhs, err := d.GetMatchingNodes(context, expressionNode.LHS)
87110
if err != nil {
88111
return Context{}, err
89112
}
90113
log.Debugf("crossFunction LHS len: %v", lhs.MatchingNodes.Len())
91114

92-
rhs, err := d.GetMatchingNodes(context, expressionNode.RHS)
93-
94-
if err != nil {
95-
return Context{}, err
96-
}
97-
98-
if calcWhenEmpty && lhs.MatchingNodes.Len() == 0 {
99-
err := resultsForRHS(d, context, nil, rhs, calculation, results, calcWhenEmpty)
115+
if prefs.CalcWhenEmpty && lhs.MatchingNodes.Len() == 0 {
116+
err := resultsForRHS(d, context, nil, prefs, expressionNode.RHS, results)
100117
if err != nil {
101118
return Context{}, err
102119
}
@@ -105,7 +122,7 @@ func doCrossFunc(d *dataTreeNavigator, context Context, expressionNode *Expressi
105122
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
106123
lhsCandidate := el.Value.(*CandidateNode)
107124

108-
err := resultsForRHS(d, context, lhsCandidate, rhs, calculation, results, calcWhenEmpty)
125+
err = resultsForRHS(d, context, lhsCandidate, prefs, expressionNode.RHS, results)
109126
if err != nil {
110127
return Context{}, err
111128
}
@@ -115,6 +132,11 @@ func doCrossFunc(d *dataTreeNavigator, context Context, expressionNode *Expressi
115132
}
116133

117134
func crossFunction(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, calculation crossFunctionCalculation, calcWhenEmpty bool) (Context, error) {
135+
prefs := crossFunctionPreferences{CalcWhenEmpty: calcWhenEmpty, Calculation: calculation}
136+
return crossFunctionWithPrefs(d, context, expressionNode, prefs)
137+
}
138+
139+
func crossFunctionWithPrefs(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, prefs crossFunctionPreferences) (Context, error) {
118140
var results = list.New()
119141

120142
var evaluateAllTogether = true
@@ -124,15 +146,16 @@ func crossFunction(d *dataTreeNavigator, context Context, expressionNode *Expres
124146
break
125147
}
126148
}
149+
127150
if evaluateAllTogether {
128151
log.Debug("crossFunction evaluateAllTogether!")
129-
return doCrossFunc(d, context, expressionNode, calculation, calcWhenEmpty)
152+
return doCrossFunc(d, context, expressionNode, prefs)
130153
}
131154

132155
log.Debug("crossFunction evaluate apart!")
133156

134157
for matchEl := context.MatchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
135-
innerResults, err := doCrossFunc(d, context.SingleChildContext(matchEl.Value.(*CandidateNode)), expressionNode, calculation, calcWhenEmpty)
158+
innerResults, err := doCrossFunc(d, context.SingleChildContext(matchEl.Value.(*CandidateNode)), expressionNode, prefs)
136159
if err != nil {
137160
return Context{}, err
138161
}

0 commit comments

Comments
 (0)