lang: core, funcs: Port some functions to CallableFunc API

Some modern features of our function engine and language might require
this new API, so port what we can and figure out the rest later.
This commit is contained in:
James Shubin
2025-03-16 23:23:57 -04:00
parent f313380480
commit 642c6b952f
29 changed files with 702 additions and 291 deletions

View File

@@ -64,6 +64,9 @@ func init() {
"func(float, float) float", // floating-point addition
}),
F: func(ctx context.Context, input []types.Value) (types.Value, error) {
if l := len(input); l != 2 { // catch programming bugs
return nil, fmt.Errorf("invalid len %d", l)
}
switch k := input[0].Type().Kind; k {
case types.KindStr:
return &types.StrValue{
@@ -476,6 +479,9 @@ type OperatorFunc struct {
init *interfaces.Init
last types.Value // last value received to use for diff
lastOp string
fn interfaces.FuncSig
result types.Value // last calculated output
}
@@ -654,8 +660,6 @@ func (obj *OperatorFunc) Init(init *interfaces.Init) error {
// Stream returns the changing values that this func has over time.
func (obj *OperatorFunc) Stream(ctx context.Context) error {
var op, lastOp string
var fn interfaces.FuncSig
defer close(obj.init.Output) // the sender closes
for {
select {
@@ -685,43 +689,12 @@ func (obj *OperatorFunc) Stream(ctx context.Context) error {
return fmt.Errorf("bad args, got: %v, want: %v", keys, obj.Type.Ord)
}
// build up arg list
args := []types.Value{}
for _, name := range obj.Type.Ord {
v, exists := input.Struct()[name]
if !exists {
// programming error
return fmt.Errorf("function engine was early, missing arg: %s", name)
}
if name == operatorArgName {
op = v.Str()
continue // skip over the operator arg
}
args = append(args, v)
args, err := interfaces.StructToCallableArgs(input) // []types.Value, error)
if err != nil {
return err
}
if op == "" {
// programming error
return fmt.Errorf("operator cannot be empty, args: %v", keys)
}
// operator selection is dynamic now, although mostly it
// should not change... to do so is probably uncommon...
if fn == nil {
fn = obj.findFunc(op)
} else if op != lastOp {
// TODO: check sig is compatible instead?
return fmt.Errorf("op changed from %s to %s", lastOp, op)
}
if fn == nil {
return fmt.Errorf("func not found for operator `%s` with sig: `%+v`", op, obj.Type)
}
lastOp = op
var result types.Value
result, err := fn(ctx, args) // (Value, error)
result, err := obj.Call(ctx, args) // (Value, error)
if err != nil {
return errwrap.Wrapf(err, "problem running function")
}
@@ -749,6 +722,42 @@ func (obj *OperatorFunc) Stream(ctx context.Context) error {
}
}
// Call this function with the input args and return the value if it is possible
// to do so at this time.
func (obj *OperatorFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
op := args[0].Str()
if op == "" {
// programming error
return nil, fmt.Errorf("operator cannot be empty, args: %v", args)
}
// operator selection is dynamic now, although mostly it
// should not change... to do so is probably uncommon...
if obj.fn == nil {
obj.fn = obj.findFunc(op)
} else if op != obj.lastOp {
// TODO: check sig is compatible instead?
return nil, fmt.Errorf("op changed from %s to %s", obj.lastOp, op)
}
if obj.fn == nil {
return nil, fmt.Errorf("func not found for operator `%s` with sig: `%+v`", op, obj.Type)
}
obj.lastOp = op
newArgs := []types.Value{}
for i, x := range args {
if i == 0 {
continue // skip over the operator
}
newArgs = append(newArgs, x)
}
return obj.fn(ctx, newArgs) // (Value, error)
}
// removeOperatorArg returns a copy of the input KindFunc type, without the
// operator arg which specifies which operator we're using. It *is* idempotent.
func removeOperatorArg(typ *types.Type) *types.Type {