diff --git a/pkg/yqlib/doc/Reduce.md b/pkg/yqlib/doc/Reduce.md new file mode 100644 index 00000000..e69de29b diff --git a/pkg/yqlib/expression_processing_test.go b/pkg/yqlib/expression_processing_test.go index 6b905068..e984b997 100644 --- a/pkg/yqlib/expression_processing_test.go +++ b/pkg/yqlib/expression_processing_test.go @@ -13,185 +13,190 @@ var pathTests = []struct { expectedPostFix []interface{} }{ { - `.a | .b | .c`, - append(make([]interface{}, 0), "a", "PIPE", "b", "PIPE", "c"), - append(make([]interface{}, 0), "a", "b", "c", "PIPE", "PIPE"), - }, - { - `[]`, - 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"), - }, - { - `.realnames as $names | $names["anon"]`, - append(make([]interface{}, 0), "realnames", "ASSIGN_VARIABLE", "GET_VARIABLE", "PIPE", "GET_VARIABLE", "TRAVERSE_ARRAY", "[", "anon (string)", "]"), - append(make([]interface{}, 0), "realnames", "GET_VARIABLE", "ASSIGN_VARIABLE", "GET_VARIABLE", "anon (string)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "PIPE"), - }, - { - `.b[.a]`, - append(make([]interface{}, 0), "b", "TRAVERSE_ARRAY", "[", "a", "]"), - append(make([]interface{}, 0), "b", "a", "COLLECT", "SHORT_PIPE", "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", "[", "EMPTY", "]"), - append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), - }, - { - `.a.[]`, - append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"), - append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), - }, - { - `.a[0]`, - append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"), - append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), - }, - { - `.a.[0]`, - append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"), - append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), - }, - { - `.a[].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"), - }, - { - `[3]`, - append(make([]interface{}, 0), "[", "3 (int64)", "]"), - append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"), - }, - { - `.key.array + .key.array2`, - append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ADD", "key", "SHORT_PIPE", "array2"), - append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "ADD"), - }, - { - `.key.array * .key.array2`, - append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "MULTIPLY", "key", "SHORT_PIPE", "array2"), - append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "MULTIPLY"), - }, - { - `.key.array // .key.array2`, - append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ALTERNATIVE", "key", "SHORT_PIPE", "array2"), - append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "ALTERNATIVE"), - }, - { - `.a | .[].b == "apple"`, - 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", "[", "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", "[", "EMPTY", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"), - append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"), - }, - { - `[true]`, - append(make([]interface{}, 0), "[", "true (bool)", "]"), - append(make([]interface{}, 0), "true (bool)", "COLLECT", "SHORT_PIPE"), - }, - { - `[true, false]`, - append(make([]interface{}, 0), "[", "true (bool)", "UNION", "false (bool)", "]"), - append(make([]interface{}, 0), "true (bool)", "false (bool)", "UNION", "COLLECT", "SHORT_PIPE"), - }, - { - `"mike": .a`, - append(make([]interface{}, 0), "mike (string)", "CREATE_MAP", "a"), - append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP"), - }, - { - `.a: "mike"`, - append(make([]interface{}, 0), "a", "CREATE_MAP", "mike (string)"), - append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP"), - }, - { - `{"mike": .a}`, - append(make([]interface{}, 0), "{", "mike (string)", "CREATE_MAP", "a", "}"), - append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"), - }, - { - `{.a: "mike"}`, - append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "mike (string)", "}"), - append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"), - }, - { - `{.a: .c, .b.[]: .f.g[]}`, - 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"), - }, - { - `explode(.a.b)`, - append(make([]interface{}, 0), "EXPLODE", "(", "a", "SHORT_PIPE", "b", ")"), - append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "EXPLODE"), - }, - { - `.a.b style="folded"`, - append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_STYLE", "folded (string)"), - append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "folded (string)", "ASSIGN_STYLE"), - }, - { - `tag == "str"`, - append(make([]interface{}, 0), "GET_TAG", "EQUALS", "str (string)"), - append(make([]interface{}, 0), "GET_TAG", "str (string)", "EQUALS"), - }, - { - `. tag= "str"`, - append(make([]interface{}, 0), "SELF", "ASSIGN_TAG", "str (string)"), - append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_TAG"), - }, - { - `lineComment == "str"`, - append(make([]interface{}, 0), "GET_COMMENT", "EQUALS", "str (string)"), - append(make([]interface{}, 0), "GET_COMMENT", "str (string)", "EQUALS"), - }, - { - `. lineComment= "str"`, - append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"), - append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"), - }, - { - `. lineComment |= "str"`, - append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"), - append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"), - }, - { - `.a.b tag="!!str"`, - append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"), - append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "!!str (string)", "ASSIGN_TAG"), - }, - { - `""`, - append(make([]interface{}, 0), " (string)"), - append(make([]interface{}, 0), " (string)"), - }, - { - `.foo* | (. style="flow")`, - append(make([]interface{}, 0), "foo*", "PIPE", "(", "SELF", "ASSIGN_STYLE", "flow (string)", ")"), - append(make([]interface{}, 0), "foo*", "SELF", "flow (string)", "ASSIGN_STYLE", "PIPE"), + `.[] as $item reduce (0; . + $item)`, // note - add code to shuffle reduce to this position for postfix + append(make([]interface{}, 0), "a", "ASSIGN_VARIABLE", "GET_VARIABLE", "REDUCE", "(", "b", ")"), + append(make([]interface{}, 0), "a", "GET_VARIABLE", "ASSIGN_VARIABLE", "b", "REDUCE"), }, + // { + // `.a | .b | .c`, + // append(make([]interface{}, 0), "a", "PIPE", "b", "PIPE", "c"), + // append(make([]interface{}, 0), "a", "b", "c", "PIPE", "PIPE"), + // }, + // { + // `[]`, + // 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"), + // }, + // { + // `.realnames as $names | $names["anon"]`, + // append(make([]interface{}, 0), "realnames", "ASSIGN_VARIABLE", "GET_VARIABLE", "PIPE", "GET_VARIABLE", "TRAVERSE_ARRAY", "[", "anon (string)", "]"), + // append(make([]interface{}, 0), "realnames", "GET_VARIABLE", "ASSIGN_VARIABLE", "GET_VARIABLE", "anon (string)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "PIPE"), + // }, + // { + // `.b[.a]`, + // append(make([]interface{}, 0), "b", "TRAVERSE_ARRAY", "[", "a", "]"), + // append(make([]interface{}, 0), "b", "a", "COLLECT", "SHORT_PIPE", "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", "[", "EMPTY", "]"), + // append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), + // }, + // { + // `.a.[]`, + // append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"), + // append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), + // }, + // { + // `.a[0]`, + // append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"), + // append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), + // }, + // { + // `.a.[0]`, + // append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"), + // append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), + // }, + // { + // `.a[].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"), + // }, + // { + // `[3]`, + // append(make([]interface{}, 0), "[", "3 (int64)", "]"), + // append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"), + // }, + // { + // `.key.array + .key.array2`, + // append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ADD", "key", "SHORT_PIPE", "array2"), + // append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "ADD"), + // }, + // { + // `.key.array * .key.array2`, + // append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "MULTIPLY", "key", "SHORT_PIPE", "array2"), + // append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "MULTIPLY"), + // }, + // { + // `.key.array // .key.array2`, + // append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ALTERNATIVE", "key", "SHORT_PIPE", "array2"), + // append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "ALTERNATIVE"), + // }, + // { + // `.a | .[].b == "apple"`, + // 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", "[", "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", "[", "EMPTY", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"), + // append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"), + // }, + // { + // `[true]`, + // append(make([]interface{}, 0), "[", "true (bool)", "]"), + // append(make([]interface{}, 0), "true (bool)", "COLLECT", "SHORT_PIPE"), + // }, + // { + // `[true, false]`, + // append(make([]interface{}, 0), "[", "true (bool)", "UNION", "false (bool)", "]"), + // append(make([]interface{}, 0), "true (bool)", "false (bool)", "UNION", "COLLECT", "SHORT_PIPE"), + // }, + // { + // `"mike": .a`, + // append(make([]interface{}, 0), "mike (string)", "CREATE_MAP", "a"), + // append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP"), + // }, + // { + // `.a: "mike"`, + // append(make([]interface{}, 0), "a", "CREATE_MAP", "mike (string)"), + // append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP"), + // }, + // { + // `{"mike": .a}`, + // append(make([]interface{}, 0), "{", "mike (string)", "CREATE_MAP", "a", "}"), + // append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"), + // }, + // { + // `{.a: "mike"}`, + // append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "mike (string)", "}"), + // append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"), + // }, + // { + // `{.a: .c, .b.[]: .f.g[]}`, + // 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"), + // }, + // { + // `explode(.a.b)`, + // append(make([]interface{}, 0), "EXPLODE", "(", "a", "SHORT_PIPE", "b", ")"), + // append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "EXPLODE"), + // }, + // { + // `.a.b style="folded"`, + // append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_STYLE", "folded (string)"), + // append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "folded (string)", "ASSIGN_STYLE"), + // }, + // { + // `tag == "str"`, + // append(make([]interface{}, 0), "GET_TAG", "EQUALS", "str (string)"), + // append(make([]interface{}, 0), "GET_TAG", "str (string)", "EQUALS"), + // }, + // { + // `. tag= "str"`, + // append(make([]interface{}, 0), "SELF", "ASSIGN_TAG", "str (string)"), + // append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_TAG"), + // }, + // { + // `lineComment == "str"`, + // append(make([]interface{}, 0), "GET_COMMENT", "EQUALS", "str (string)"), + // append(make([]interface{}, 0), "GET_COMMENT", "str (string)", "EQUALS"), + // }, + // { + // `. lineComment= "str"`, + // append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"), + // append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"), + // }, + // { + // `. lineComment |= "str"`, + // append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"), + // append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"), + // }, + // { + // `.a.b tag="!!str"`, + // append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"), + // append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "!!str (string)", "ASSIGN_TAG"), + // }, + // { + // `""`, + // append(make([]interface{}, 0), " (string)"), + // append(make([]interface{}, 0), " (string)"), + // }, + // { + // `.foo* | (. style="flow")`, + // append(make([]interface{}, 0), "foo*", "PIPE", "(", "SELF", "ASSIGN_STYLE", "flow (string)", ")"), + // append(make([]interface{}, 0), "foo*", "SELF", "flow (string)", "ASSIGN_STYLE", "PIPE"), + // }, } var tokeniser = newExpressionTokeniser() diff --git a/pkg/yqlib/expression_tokeniser.go b/pkg/yqlib/expression_tokeniser.go index 5f589850..2f84cd4f 100644 --- a/pkg/yqlib/expression_tokeniser.go +++ b/pkg/yqlib/expression_tokeniser.go @@ -252,6 +252,8 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`or`), opToken(orOpType)) lexer.Add([]byte(`and`), opToken(andOpType)) lexer.Add([]byte(`not`), opToken(notOpType)) + lexer.Add([]byte(`reduce`), opToken(reduceOpType)) + lexer.Add([]byte(`;`), opToken(blockOpType)) lexer.Add([]byte(`\/\/`), opToken(alternativeOpType)) lexer.Add([]byte(`documentIndex`), opToken(getDocumentIndexOpType)) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 0f253bff..732429c2 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -25,6 +25,9 @@ type operationType struct { var orOpType = &operationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: orOperator} var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator} +var reduceOpType = &operationType{Type: "REDUCE", NumArgs: 2, Precedence: 5, Handler: reduceOperator} + +var blockOpType = &operationType{Type: "BLOCK", Precedence: 10, NumArgs: 2, Handler: emptyOperator} var unionOpType = &operationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: unionOperator} @@ -80,7 +83,7 @@ var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 5 var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator} var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator} var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator} -var emptyOpType = &operationType{Type: "EMPTY", NumArgs: 50, Handler: emptyOperator} +var emptyOpType = &operationType{Type: "EMPTY", Precedence: 50, Handler: emptyOperator} var recursiveDescentOpType = &operationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: recursiveDescentOperator} diff --git a/pkg/yqlib/operator_reduce.go b/pkg/yqlib/operator_reduce.go new file mode 100644 index 00000000..cc908fad --- /dev/null +++ b/pkg/yqlib/operator_reduce.go @@ -0,0 +1,59 @@ +package yqlib + +import ( + "container/list" + "fmt" +) + +func reduceOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + log.Debugf("-- reduceOp") + //.a as $var reduce (0; . + $var) + //lhs is the assignment operator + //rhs is the reduce block + // '.' refers to the current accumulator, initialised to 0 + // $var references a single element from the .a + + //ensure lhs is actually an assignment + //and rhs is a block (empty) + if expressionNode.Lhs.Operation.OperationType != assignVariableOpType { + return Context{}, fmt.Errorf("reduce must be given a variables assignment, got %v instead", expressionNode.Lhs.Operation.OperationType.Type) + } else if expressionNode.Rhs.Operation.OperationType != blockOpType { + return Context{}, fmt.Errorf("reduce must be given a block, got %v instead", expressionNode.Rhs.Operation.OperationType.Type) + } + + arrayExpNode := expressionNode.Lhs.Lhs + array, err := d.GetMatchingNodes(context, arrayExpNode) + + log.Debugf("array of %v things", array.MatchingNodes.Len()) + + if err != nil { + return Context{}, err + } + + variableName := expressionNode.Lhs.Rhs.Operation.StringValue + + initExp := expressionNode.Rhs.Lhs + + accum, err := d.GetMatchingNodes(context, initExp) + if err != nil { + return Context{}, err + } + + log.Debugf("with variable %v", variableName) + + blockExp := expressionNode.Rhs.Rhs + for el := array.MatchingNodes.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + log.Debugf("REDUCING WITH %v", NodeToString(candidate)) + l := list.New() + l.PushBack(candidate) + accum.SetVariable(variableName, l) + + accum, err = d.GetMatchingNodes(accum, blockExp) + if err != nil { + return Context{}, err + } + } + + return accum, nil +} diff --git a/pkg/yqlib/operator_reduce_test.go b/pkg/yqlib/operator_reduce_test.go new file mode 100644 index 00000000..7e9500d1 --- /dev/null +++ b/pkg/yqlib/operator_reduce_test.go @@ -0,0 +1,22 @@ +package yqlib + +import ( + "testing" +) + +var reduceOperatorScenarios = []expressionScenario{ + { + document: `[10,2, 5, 3]`, + expression: `.[] as $item reduce (0; . + $item)`, + expected: []string{ + "D0, P[], (!!int)::20\n", + }, + }, +} + +func TestReduceOperatorScenarios(t *testing.T) { + for _, tt := range reduceOperatorScenarios { + testScenario(t, &tt) + } + // documentScenarios(t, "Reduce", reduceOperatorScenarios) +}