diff --git a/list-type.go b/list-type.go index eddf8cd..b2442c1 100644 --- a/list-type.go +++ b/list-type.go @@ -134,7 +134,7 @@ func (ls1 *ListType) Equals(ls2 ListType) (answer bool) { answer = true for index, i1 := range *ls1 { // if i1 != (ls2)[index] { - if reflect.DeepEqual(i1, ls2[index]) { + if !reflect.DeepEqual(i1, ls2[index]) { answer = false break } diff --git a/operator-filter.go b/operator-filter.go new file mode 100644 index 0000000..459041f --- /dev/null +++ b/operator-filter.go @@ -0,0 +1,70 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// operator-filter.go +package expr + +import ( + "fmt" + "io" +) + +//-------- map term + +func newFilterTerm(tk *Token) (inst *term) { + return &term{ + tk: *tk, + children: make([]*term, 0, 2), + position: posInfix, + priority: priIterOp, + evalFunc: evalFilter, + } +} + +func evalFilter(ctx ExprContext, opTerm *term) (v any, err error) { + var leftValue, rightValue any + var it Iterator + var item any + + if err = opTerm.checkOperands(); err != nil { + return + } + + if leftValue, err = opTerm.children[0].compute(ctx); err != nil { + return + } + + if it, err = NewIterator(leftValue); err != nil { + return nil, fmt.Errorf("left operand of MAP must be an iterable data-source; got %s", TypeName(leftValue)) + } + + values := newListA() + for item, err = it.Next(); err == nil; item, err = it.Next() { + ctx.SetVar("_", item) + ctx.SetVar("_index", it.Index()) + ctx.SetVar("_count", it.Count()) + if rightValue, err = opTerm.children[1].compute(ctx); err == nil { + if success, valid := ToBool(rightValue); valid { + if success { + values.appendItem(item) + } + } else { + err = fmt.Errorf("filter expression must return a boolean or a castable to boolean, got %v [%T]", rightValue, rightValue) + } + } + ctx.DeleteVar("_") + if err != nil { + break + } + } + if err == io.EOF { + err = nil + } + v = values + return +} + +// init +func init() { + registerTermConstructor(SymKwFilter, newFilterTerm) +} diff --git a/symbol-map.go b/symbol-map.go index 9e0e3c7..a5b175c 100644 --- a/symbol-map.go +++ b/symbol-map.go @@ -131,10 +131,13 @@ func init() { // // SymClosedComment // 0: '*/' // // SymOneLineComment // 0: '//' // keywordBase - SymKwAnd: {"and", symClassOperator, posInfix}, - SymKwNot: {"not", symClassOperator, posInfix}, - SymKwOr: {"or", symClassOperator, posInfix}, - SymKwBut: {"but", symClassOperator, posInfix}, + SymKwAnd: {"and", symClassOperator, posInfix}, + SymKwNot: {"not", symClassOperator, posInfix}, + SymKwOr: {"or", symClassOperator, posInfix}, + SymKwBut: {"but", symClassOperator, posInfix}, + SymKwMap: {"map", symClassOperator, posInfix}, + SymKwFilter: {"filter", symClassOperator, posInfix}, + // SymKwDigest: {"digest", symClassOperator, posInfix}, SymKwFunc: {"func(", symClassDeclaration, posPrefix}, SymKwBuiltin: {"builtin", symClassOperator, posPrefix}, SymKwPlugin: {"plugin", symClassOperator, posPrefix}, diff --git a/symbol.go b/symbol.go index 3c6788e..a395136 100644 --- a/symbol.go +++ b/symbol.go @@ -120,6 +120,8 @@ const ( SymKwIn SymKwInclude SymKwMap + SymKwFilter + // SymKwDigest SymKwNil SymKwUnset ) @@ -137,9 +139,11 @@ func init() { "IN": SymKwIn, "INCLUDE": SymKwInclude, "MAP": SymKwMap, + "FILTER": SymKwFilter, "NOT": SymKwNot, "OR": SymKwOr, "NIL": SymKwNil, "UNSET": SymKwUnset, + // "DIGEST": SymKwDigest, } } diff --git a/t_iterator_test.go b/t_iterator_test.go index 2bb78c2..9857aa9 100644 --- a/t_iterator_test.go +++ b/t_iterator_test.go @@ -32,8 +32,10 @@ func TestIteratorParser(t *testing.T) { /* 21 */ {`it=$({1:"one",2:"two",3:"three"}, "desc", "key"); it++`, int64(3), nil}, /* 22 */ {`it=$({1:"one",2:"two",3:"three"}, "asc", "item"); it++`, NewList([]any{int64(1), "one"}), nil}, /* 23 */ {`builtin "os.file"; fileReadIterator("test-file.txt") map ${_index}`, NewList([]any{int64(0), int64(1)}), nil}, + /* 24 */ {`builtin "os.file"; #(fileReadIterator("test-file.txt") filter (#${_} == 2))`, int64(0), nil}, + /* 25 */ {`builtin "os.file"; #(fileReadIterator("test-file.txt") filter (#${_} == 3))`, int64(2), nil}, } - runTestSuiteSpec(t, section, inputs, 23) - // runTestSuite(t, section, inputs) + // runTestSuiteSpec(t, section, inputs, 23) + runTestSuite(t, section, inputs) } diff --git a/t_operator_test.go b/t_operator_test.go index cf9afc5..a06c4d5 100644 --- a/t_operator_test.go +++ b/t_operator_test.go @@ -33,12 +33,13 @@ func TestOperator(t *testing.T) { /* 20 */ {`a=1; a^=2`, int64(3), nil}, /* 21 */ {`a=1; ++a`, int64(2), nil}, /* 22 */ {`a=1; --a`, int64(0), nil}, - /* 23 */ {`[1,2,3] map var("_")`, []any{1, 2, 3}, nil}, - /* 24 */ {`[1,2,3] map $_`, newList([]any{1, 2, 3}), nil}, + /* 23 */ {`[1,2,3] map var("_")`, newList([]any{int64(1), int64(2), int64(3)}), nil}, + /* 24 */ {`[1,2,3] map $_`, newList([]any{int64(1), int64(2), int64(3)}), nil}, + /* 25 */ {`[1,2,3,4] filter ($_ % 2 == 0)`, newList([]any{int64(2), int64(4)}), nil}, } // t.Setenv("EXPR_PATH", ".") - runTestSuiteSpec(t, section, inputs, 24) - // runTestSuite(t, section, inputs) + // runTestSuiteSpec(t, section, inputs, 25) + runTestSuite(t, section, inputs) }