From 07b053f040cf9d11fc3748ad5008fca81f00bc3e Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 3 Feb 2021 17:20:54 +1100 Subject: [PATCH] Fixing op precedences --- pkg/yqlib/doc/Path.md | 11 +++++++++ pkg/yqlib/expression_parser.go | 2 +- pkg/yqlib/expression_parser_test.go | 2 +- pkg/yqlib/expression_postfix.go | 13 +--------- pkg/yqlib/expression_processing_test.go | 33 ++++++++++++++----------- pkg/yqlib/expression_tokeniser.go | 8 ++++++ 6 files changed, 41 insertions(+), 28 deletions(-) diff --git a/pkg/yqlib/doc/Path.md b/pkg/yqlib/doc/Path.md index 1748c486..9bf1f404 100644 --- a/pkg/yqlib/doc/Path.md +++ b/pkg/yqlib/doc/Path.md @@ -79,3 +79,14 @@ then yq eval '.a.[] | select(. == "*og") | [{"path":path, "value":.}]' sample.yml ``` will output +```yaml +- path: + - a + - 1 + value: dog +- path: + - a + - 2 + value: frog +``` + diff --git a/pkg/yqlib/expression_parser.go b/pkg/yqlib/expression_parser.go index 56d56789..ad3c969d 100644 --- a/pkg/yqlib/expression_parser.go +++ b/pkg/yqlib/expression_parser.go @@ -70,7 +70,7 @@ func (p *expressionParserImpl) createExpressionTree(postFixPath []*Operation) (* stack = append(stack, &newNode) } if len(stack) != 1 { - return nil, fmt.Errorf("expected end of expression but found '%v', please check expression syntax", strings.TrimSpace(stack[1].Operation.StringValue)) + return nil, fmt.Errorf("Bad expression, please check expression syntax") } return stack[0], nil } diff --git a/pkg/yqlib/expression_parser_test.go b/pkg/yqlib/expression_parser_test.go index 3a24e984..420ec2c6 100644 --- a/pkg/yqlib/expression_parser_test.go +++ b/pkg/yqlib/expression_parser_test.go @@ -38,5 +38,5 @@ func TestPathTreeOneArgForOneArgOp(t *testing.T) { func TestPathTreeExtraArgs(t *testing.T) { _, err := NewExpressionParser().ParseExpression("sortKeys(.) explode(.)") - test.AssertResultComplex(t, "expected end of expression but found 'explode', please check expression syntax", err.Error()) + test.AssertResultComplex(t, "Bad expression, please check expression syntax", err.Error()) } diff --git a/pkg/yqlib/expression_postfix.go b/pkg/yqlib/expression_postfix.go index 2fbba373..c814269c 100644 --- a/pkg/yqlib/expression_postfix.go +++ b/pkg/yqlib/expression_postfix.go @@ -43,15 +43,9 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope opener = openCollectObject collectOperator = collectObjectOpType } - itemsInMiddle := false + for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener { opStack, result = popOpToResult(opStack, result) - itemsInMiddle = true - } - if !itemsInMiddle { - // must be an empty collection, add the empty object as a LHS parameter - result = append(result, &Operation{OperationType: emptyOpType}) - log.Debugf("put EMPTY onto the result") } if len(opStack) == 0 { return nil, errors.New("Bad path expression, got close collect brackets without matching opening bracket") @@ -61,16 +55,11 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope log.Debugf("deleteing open bracket from opstack") //and append a collect to the opStack - //WHY AM I NOT PUTTING THIS STRAIGHT ONTO THE RESULT LIKE EMPTY? result = append(result, &Operation{OperationType: collectOperator}) log.Debugf("put collect onto the result") result = append(result, &Operation{OperationType: shortPipeOpType}) log.Debugf("put shortpipe onto the result") - // opStack = append(opStack, &token{TokenType: operationToken, Operation: &Operation{OperationType: shortPipeOpType}}) - // log.Debugf("put shortPipe onto the opstack") - // opStack = append(opStack, &token{TokenType: operationToken, Operation: &Operation{OperationType: collectOperator}}) - // log.Debugf("put collect onto the opstack") case closeBracket: for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != openBracket { opStack, result = popOpToResult(opStack, result) diff --git a/pkg/yqlib/expression_processing_test.go b/pkg/yqlib/expression_processing_test.go index 1e7d3b62..98d007f0 100644 --- a/pkg/yqlib/expression_processing_test.go +++ b/pkg/yqlib/expression_processing_test.go @@ -19,9 +19,19 @@ var pathTests = []struct { }, { `[]`, - append(make([]interface{}, 0), "[", "]"), + append(make([]interface{}, 0), "[", "EMPTY", "]"), append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"), }, + { + `{}`, + append(make([]interface{}, 0), "{", "EMPTY", "}"), + append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE"), + }, + { + `[{}]`, // [{}] + append(make([]interface{}, 0), "[", "{", "EMPTY", "}", "]"), + append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE", "COLLECT", "SHORT_PIPE"), + }, { `.b[.a]`, append(make([]interface{}, 0), "b", "TRAVERSE_ARRAY", "[", "a", "]"), @@ -29,17 +39,17 @@ var pathTests = []struct { }, { `.[]`, - append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "]"), + append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]"), append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), }, { `.a[]`, - append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "]"), + append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"), append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), }, { `.a.[]`, - append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "]"), + append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"), append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), }, { @@ -54,7 +64,7 @@ var pathTests = []struct { }, { `.a[].c`, - append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "c"), + append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "c"), append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "c", "SHORT_PIPE"), }, { @@ -79,17 +89,17 @@ var pathTests = []struct { }, { `.a | .[].b == "apple"`, - append(make([]interface{}, 0), "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"), + append(make([]interface{}, 0), "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"), append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"), }, { `(.a | .[].b) == "apple"`, - append(make([]interface{}, 0), "(", "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"), + append(make([]interface{}, 0), "(", "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"), append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"), }, { `.[] | select(. == "*at")`, - append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"), + append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"), append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"), }, { @@ -124,7 +134,7 @@ var pathTests = []struct { }, { `{.a: .c, .b.[]: .f.g[]}`, - append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "TRAVERSE_ARRAY", "[", "]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "TRAVERSE_ARRAY", "[", "]", "}"), + append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "}"), append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "f", "g", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"), }, { @@ -177,11 +187,6 @@ var pathTests = []struct { append(make([]interface{}, 0), "foo*", "PIPE", "(", "SELF", "ASSIGN_STYLE", "flow (string)", ")"), append(make([]interface{}, 0), "foo*", "SELF", "flow (string)", "ASSIGN_STYLE", "PIPE"), }, - { - `{}`, - append(make([]interface{}, 0), "{", "}"), - append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE"), - }, } var tokeniser = newExpressionTokeniser() diff --git a/pkg/yqlib/expression_tokeniser.go b/pkg/yqlib/expression_tokeniser.go index 82f383c7..a08bea50 100644 --- a/pkg/yqlib/expression_tokeniser.go +++ b/pkg/yqlib/expression_tokeniser.go @@ -409,6 +409,14 @@ func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postPr postProcessedTokens = append(postProcessedTokens, currentToken) + if index != len(tokens)-1 && + ((currentToken.TokenType == openCollect && tokens[index+1].TokenType == closeCollect) || + (currentToken.TokenType == openCollectObject && tokens[index+1].TokenType == closeCollectObject)) { + + op := &Operation{OperationType: emptyOpType, StringValue: "EMPTY"} + postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op}) + } + if index != len(tokens)-1 && currentToken.CheckForPostTraverse && tokens[index+1].TokenType == operationToken && tokens[index+1].Operation.OperationType == traversePathOpType {