diff --git a/pkg/yqlib/expression_parser_test.go b/pkg/yqlib/expression_parser_test.go index 420ec2c6..f1e05e17 100644 --- a/pkg/yqlib/expression_parser_test.go +++ b/pkg/yqlib/expression_parser_test.go @@ -6,37 +6,66 @@ import ( "github.com/mikefarah/yq/v4/test" ) -func TestPathTreeNoArgsForTwoArgOp(t *testing.T) { +func TestParserNoMatchingCloseCollect(t *testing.T) { + _, err := NewExpressionParser().ParseExpression("[1,2") + test.AssertResultComplex(t, "Bad expression, could not find matching `]`", err.Error()) +} +func TestParserNoMatchingCloseObjectInCollect(t *testing.T) { + _, err := NewExpressionParser().ParseExpression(`[{"b": "c"]`) + test.AssertResultComplex(t, "Bad expression, could not find matching `}`", err.Error()) +} + +func TestParserNoMatchingCloseInCollect(t *testing.T) { + _, err := NewExpressionParser().ParseExpression(`[(.a]`) + test.AssertResultComplex(t, "Bad expression, could not find matching `)`", err.Error()) +} + +func TestParserNoMatchingCloseCollectObject(t *testing.T) { + _, err := NewExpressionParser().ParseExpression(`{"a": "b"`) + test.AssertResultComplex(t, "Bad expression, could not find matching `}`", err.Error()) +} + +func TestParserNoMatchingCloseCollectInCollectObject(t *testing.T) { + _, err := NewExpressionParser().ParseExpression(`{"b": [1}`) + test.AssertResultComplex(t, "Bad expression, could not find matching `]`", err.Error()) +} + +func TestParserNoMatchingCloseBracketInCollectObject(t *testing.T) { + _, err := NewExpressionParser().ParseExpression(`{"b": (1}`) + test.AssertResultComplex(t, "Bad expression, could not find matching `)`", err.Error()) +} + +func TestParserNoArgsForTwoArgOp(t *testing.T) { _, err := NewExpressionParser().ParseExpression("=") test.AssertResultComplex(t, "'=' expects 2 args but there is 0", err.Error()) } -func TestPathTreeOneLhsArgsForTwoArgOp(t *testing.T) { +func TestParserOneLhsArgsForTwoArgOp(t *testing.T) { _, err := NewExpressionParser().ParseExpression(".a =") test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error()) } -func TestPathTreeOneRhsArgsForTwoArgOp(t *testing.T) { +func TestParserOneRhsArgsForTwoArgOp(t *testing.T) { _, err := NewExpressionParser().ParseExpression("= .a") test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error()) } -func TestPathTreeTwoArgsForTwoArgOp(t *testing.T) { +func TestParserTwoArgsForTwoArgOp(t *testing.T) { _, err := NewExpressionParser().ParseExpression(".a = .b") test.AssertResultComplex(t, nil, err) } -func TestPathTreeNoArgsForOneArgOp(t *testing.T) { +func TestParserNoArgsForOneArgOp(t *testing.T) { _, err := NewExpressionParser().ParseExpression("explode") test.AssertResultComplex(t, "'explode' expects 1 arg but received none", err.Error()) } -func TestPathTreeOneArgForOneArgOp(t *testing.T) { +func TestParserOneArgForOneArgOp(t *testing.T) { _, err := NewExpressionParser().ParseExpression("explode(.)") test.AssertResultComplex(t, nil, err) } -func TestPathTreeExtraArgs(t *testing.T) { +func TestParserExtraArgs(t *testing.T) { _, err := NewExpressionParser().ParseExpression("sortKeys(.) explode(.)") 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 cdfa36e4..e237f888 100644 --- a/pkg/yqlib/expression_postfix.go +++ b/pkg/yqlib/expression_postfix.go @@ -2,6 +2,7 @@ package yqlib import ( "errors" + "fmt" logging "gopkg.in/op/go-logging.v1" ) @@ -24,6 +25,17 @@ func popOpToResult(opStack []*token, result []*Operation) ([]*token, []*Operatio return opStack, append(result, newOp.Operation) } +func validateNoOpenTokens(token *token) error { + if token.TokenType == openCollect { + return fmt.Errorf(("Bad expression, could not find matching `]`")) + } else if token.TokenType == openCollectObject { + return fmt.Errorf(("Bad expression, could not find matching `}`")) + } else if token.TokenType == openBracket { + return fmt.Errorf(("Bad expression, could not find matching `)`")) + } + return nil +} + func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Operation, error) { var result []*Operation // surround the whole thing with brackets @@ -45,6 +57,10 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope } for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener { + missingClosingTokenErr := validateNoOpenTokens(opStack[len(opStack)-1]) + if missingClosingTokenErr != nil { + return nil, missingClosingTokenErr + } opStack, result = popOpToResult(opStack, result) } if len(opStack) == 0 { @@ -76,6 +92,11 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope case closeBracket: for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != openBracket { + missingClosingTokenErr := validateNoOpenTokens(opStack[len(opStack)-1]) + if missingClosingTokenErr != nil { + return nil, missingClosingTokenErr + } + opStack, result = popOpToResult(opStack, result) } if len(opStack) == 0 { @@ -98,6 +119,8 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope } } + log.Debugf("opstackLen: %v", len(opStack)) + if log.IsEnabledFor(logging.DEBUG) { log.Debugf("PostFix Result:") for _, currentToken := range result { diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 29054578..9a55e53e 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -162,6 +162,9 @@ func createValueOperation(value interface{}, stringValue string) *Operation { // debugging purposes only func (p *Operation) toString() string { + if p == nil { + return "OP IS NIL" + } if p.OperationType == traversePathOpType { return fmt.Sprintf("%v", p.Value) } else if p.OperationType == selfReferenceOpType {