From 7d2cf1e68739cc1f9849f6a4dfe29fb1f1422045 Mon Sep 17 00:00:00 2001 From: Celestino Amoroso Date: Sat, 25 Apr 2026 06:55:09 +0200 Subject: [PATCH] new operator 'join' --- operator-digest.go | 4 +-- operator-filter.go | 2 +- operator-join.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++ symbol-map.go | 1 + symbol.go | 2 ++ t_operator_test.go | 4 ++- 6 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 operator-join.go diff --git a/operator-digest.go b/operator-digest.go index de7c6e8..a52e953 100644 --- a/operator-digest.go +++ b/operator-digest.go @@ -9,7 +9,7 @@ import ( "io" ) -//-------- map term +//-------- digest term func newDigestTerm(tk *Token) (inst *term) { return &term{ @@ -35,7 +35,7 @@ func evalDigest(ctx ExprContext, opTerm *term) (v any, err error) { } 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)) + return nil, fmt.Errorf("left operand of DIGEST must be an iterable data-source; got %s", TypeName(leftValue)) } lastValue = nil diff --git a/operator-filter.go b/operator-filter.go index aa679d6..8943080 100644 --- a/operator-filter.go +++ b/operator-filter.go @@ -35,7 +35,7 @@ func evalFilter(ctx ExprContext, opTerm *term) (v any, err error) { } 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)) + return nil, fmt.Errorf("left operand of FILTER must be an iterable data-source; got %s", TypeName(leftValue)) } values := newListA() diff --git a/operator-join.go b/operator-join.go new file mode 100644 index 0000000..62c9317 --- /dev/null +++ b/operator-join.go @@ -0,0 +1,65 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// operator-join.go +package expr + +import ( + "fmt" + "io" +) + +//-------- join term + +func newJoinTerm(tk *Token) (inst *term) { + return &term{ + tk: *tk, + children: make([]*term, 0, 2), + position: posInfix, + priority: priIterOp, + evalFunc: evalJoin, + } +} + +func evalJoin(ctx ExprContext, opTerm *term) (v any, err error) { + var leftValue, rightValue any + var itLeft, itRight Iterator + var item any + + if err = opTerm.checkOperands(); err != nil { + return + } + + if leftValue, err = opTerm.children[0].compute(ctx); err != nil { + return + } + + if rightValue, err = opTerm.children[1].compute(ctx); err != nil { + return + } + + if itLeft, err = NewIterator(leftValue); err != nil { + return nil, fmt.Errorf("left operand of JOIN must be an iterable data-source; got %s", TypeName(leftValue)) + } + + if itRight, err = NewIterator(rightValue); err != nil { + return nil, fmt.Errorf("right operand of JOIN must be an iterable data-source; got %s", TypeName(rightValue)) + } + + values := newListA() + for _, it := range []Iterator{itLeft, itRight} { + for item, err = it.Next(); err == nil; item, err = it.Next() { + values.appendItem(item) + } + } + if err == io.EOF { + err = nil + } + v = values + return +} + +// init +func init() { + registerTermConstructor(SymKwJoin, newJoinTerm) +} diff --git a/symbol-map.go b/symbol-map.go index 2215983..9ec6f22 100644 --- a/symbol-map.go +++ b/symbol-map.go @@ -138,6 +138,7 @@ func init() { SymKwMap: {"map", symClassOperator, posInfix}, SymKwFilter: {"filter", symClassOperator, posInfix}, SymKwDigest: {"digest", symClassOperator, posInfix}, + SymKwJoin: {"join", symClassOperator, posInfix}, SymKwFunc: {"func(", symClassDeclaration, posPrefix}, SymKwBuiltin: {"builtin", symClassOperator, posPrefix}, SymKwPlugin: {"plugin", symClassOperator, posPrefix}, diff --git a/symbol.go b/symbol.go index 4583335..d1bf178 100644 --- a/symbol.go +++ b/symbol.go @@ -122,6 +122,7 @@ const ( SymKwMap SymKwFilter SymKwDigest + SymKwJoin SymKwNil SymKwUnset ) @@ -145,5 +146,6 @@ func init() { "NIL": SymKwNil, "UNSET": SymKwUnset, "DIGEST": SymKwDigest, + "JOIN": SymKwJoin, } } diff --git a/t_operator_test.go b/t_operator_test.go index 668f883..b40134e 100644 --- a/t_operator_test.go +++ b/t_operator_test.go @@ -37,10 +37,12 @@ func TestOperator(t *testing.T) { /* 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}, /* 26 */ {`max=0; [2,3,1] digest max=(($_ > max) ? {$_} :: {max})`, int64(3), nil}, + /* 27 */ {`["a","b"] join ["x"]`, newList([]any{"a", "b", "x"}), nil}, + /* 28 */ {`["a","b"] join ["x"-true]`, nil, `[1:21] left operand 'x' [string] and right operand 'true' [bool] are not compatible with operator "-"`}, } // t.Setenv("EXPR_PATH", ".") - // runTestSuiteSpec(t, section, inputs, 25) + // runTestSuiteSpec(t, section, inputs, 28) runTestSuite(t, section, inputs) }