From 009255006c6f6160ca9c1b3c006494e3fba4a5b9 Mon Sep 17 00:00:00 2001 From: Matthew Nibecker Date: Thu, 15 Aug 2024 13:47:13 -0700 Subject: [PATCH] vector: Rudimentary support functions This commit adds rudimentary support for functions. There are a lot of shortcomings compared to sequence notably: only supports function lower(), does not variants/unions and wrapped errors are not handled, and it is not currently tested. Future work will brings things in feature parity. --- compiler/kernel/vexpr.go | 22 ++++++++++++++++++-- runtime/vam/expr/eval.go | 29 +++++++++++++++++++++++++- runtime/vam/expr/function/function.go | 25 ++++++++++++++++++++++ runtime/vam/expr/function/string.go | 30 +++++++++++++++++++++++++++ vector/string.go | 29 ++++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 runtime/vam/expr/function/function.go create mode 100644 runtime/vam/expr/function/string.go diff --git a/compiler/kernel/vexpr.go b/compiler/kernel/vexpr.go index b474617a03..e64b16d7f9 100644 --- a/compiler/kernel/vexpr.go +++ b/compiler/kernel/vexpr.go @@ -7,6 +7,7 @@ import ( "github.com/brimdata/zed/compiler/ast/dag" "github.com/brimdata/zed/pkg/field" vamexpr "github.com/brimdata/zed/runtime/vam/expr" + vamfunc "github.com/brimdata/zed/runtime/vam/expr/function" "github.com/brimdata/zed/zson" ) @@ -35,8 +36,8 @@ func (b *Builder) compileVamExpr(e dag.Expr) (vamexpr.Evaluator, error) { return b.compileVamBinary(e) //case *dag.Conditional: // return b.compileVamConditional(*e) - //case *dag.Call: - // return b.compileVamCall(*e) + case *dag.Call: + return b.compileVamCall(e) //case *dag.RegexpMatch: // return b.compileVamRegexpMatch(e) //case *dag.RegexpSearch: @@ -142,3 +143,20 @@ func (b *Builder) compileVamExprs(in []dag.Expr) ([]vamexpr.Evaluator, error) { } return exprs, nil } + +func (b *Builder) compileVamCall(call *dag.Call) (vamexpr.Evaluator, error) { + fn, path, err := vamfunc.New(b.zctx(), call.Name, len(call.Args)) + if err != nil { + return nil, err + } + args := call.Args + if path != nil { + dagPath := &dag.This{Kind: "This", Path: path} + args = append([]dag.Expr{dagPath}, args...) + } + exprs, err := b.compileVamExprs(args) + if err != nil { + return nil, err + } + return vamexpr.NewCall(fn, exprs), nil +} diff --git a/runtime/vam/expr/eval.go b/runtime/vam/expr/eval.go index 0a09aec8b2..8b0eec037d 100644 --- a/runtime/vam/expr/eval.go +++ b/runtime/vam/expr/eval.go @@ -1,7 +1,34 @@ package expr -import "github.com/brimdata/zed/vector" +import ( + "github.com/brimdata/zed/vector" +) type Evaluator interface { Eval(vector.Any) vector.Any } + +type Function interface { + Call([]vector.Any) vector.Any +} + +type Call struct { + fn Function + exprs []Evaluator + args []vector.Any +} + +func NewCall(fn Function, exprs []Evaluator) *Call { + return &Call{ + fn: fn, + exprs: exprs, + args: make([]vector.Any, len(exprs)), + } +} + +func (c *Call) Eval(this vector.Any) vector.Any { + for k, e := range c.exprs { + c.args[k] = e.Eval(this) + } + return c.fn.Call(c.args) +} diff --git a/runtime/vam/expr/function/function.go b/runtime/vam/expr/function/function.go new file mode 100644 index 0000000000..f8b17e8881 --- /dev/null +++ b/runtime/vam/expr/function/function.go @@ -0,0 +1,25 @@ +package function + +import ( + "github.com/brimdata/zed" + "github.com/brimdata/zed/pkg/field" + "github.com/brimdata/zed/runtime/sam/expr/function" + "github.com/brimdata/zed/runtime/vam/expr" +) + +func New(zctx *zed.Context, name string, narg int) (expr.Function, field.Path, error) { + argmin := 1 + argmax := 1 + var path field.Path + var f expr.Function + switch name { + case "lower": + f = &ToLower{zctx} + default: + return nil, nil, function.ErrNoSuchFunction + } + if err := function.CheckArgCount(narg, argmin, argmax); err != nil { + return nil, nil, err + } + return f, path, nil +} diff --git a/runtime/vam/expr/function/string.go b/runtime/vam/expr/function/string.go new file mode 100644 index 0000000000..181c485fe0 --- /dev/null +++ b/runtime/vam/expr/function/string.go @@ -0,0 +1,30 @@ +package function + +import ( + "strings" + + "github.com/brimdata/zed" + "github.com/brimdata/zed/vector" +) + +// https://github.com/brimdata/zed/blob/main/docs/language/functions.md#lower +type ToLower struct { + zctx *zed.Context +} + +func (t *ToLower) Call(args []vector.Any) vector.Any { + v := vector.Under(args[0]) + if v.Type() != zed.TypeString { + // XXX This should be a wrapped error as seen in sequential world. + return vector.NewStringError(t.zctx, "lower: string arg required", v.Len()) + } + out := vector.NewStringEmpty(v.Len(), vector.NewBoolEmpty(v.Len(), nil)) + for i := uint32(0); i < v.Len(); i++ { + s, null := vector.StringValue(v, i) + if null { + out.Nulls.Set(i) + } + out.Append(strings.ToLower(s)) + } + return out +} diff --git a/vector/string.go b/vector/string.go index 09cd3f5569..0f5074b100 100644 --- a/vector/string.go +++ b/vector/string.go @@ -45,3 +45,32 @@ func (s *String) Serialize(b *zcode.Builder, slot uint32) { b.Append(zed.EncodeString(s.Value(slot))) } } + +func StringValue(val Any, slot uint32) (string, bool) { + switch val := val.(type) { + case *String: + if val.Nulls.Value(slot) { + return "", true + } + return val.Value(slot), false + case *Const: + if val.Nulls.Value(slot) { + return "", true + } + s, _ := val.AsString() + return s, false + case *Dict: + if val.Nulls.Value(slot) { + return "", true + } + slot = uint32(val.Index[slot]) + return val.Any.(*String).Value(slot), false + case *View: + slot = val.Index[slot] + if val.Any.(*String).Nulls.Value(slot) { + return "", true + } + return val.Any.(*String).Value(slot), false + } + panic(val) +}