ucl/cmdlang/eval.go

142 lines
3.0 KiB
Go
Raw Normal View History

2024-04-10 10:45:58 +00:00
package cmdlang
import (
"context"
"errors"
2024-04-10 12:19:11 +00:00
"fmt"
2024-04-10 10:45:58 +00:00
"github.com/lmika/gopkgs/fp/slices"
"strconv"
2024-04-10 12:19:11 +00:00
"strings"
2024-04-10 10:45:58 +00:00
)
type evaluator struct {
2024-04-12 23:25:16 +00:00
inst *Inst
2024-04-10 10:45:58 +00:00
}
2024-04-11 12:05:05 +00:00
func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStatements) (object, error) {
res, err := e.evalPipeline(ctx, ec, n.First)
if err != nil {
return nil, err
}
if len(n.Rest) == 0 {
return res, nil
}
for _, rest := range n.Rest {
// Discard and close unused streams
if s, isStream := res.(stream); isStream {
if err := s.close(); err != nil {
return nil, err
}
}
out, err := e.evalPipeline(ctx, ec, rest)
if err != nil {
return nil, err
}
res = out
}
return res, nil
}
func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline) (object, error) {
2024-04-12 23:25:16 +00:00
res, err := e.evalCmd(ctx, ec, nil, n.First)
if err != nil {
return nil, err
}
if len(n.Rest) == 0 {
return res, nil
}
// Command is a pipeline, so build it out
for _, rest := range n.Rest {
2024-04-12 23:25:16 +00:00
out, err := e.evalCmd(ctx, ec, asStream(res), rest)
if err != nil {
return nil, err
}
res = out
}
return res, nil
}
2024-04-12 23:25:16 +00:00
func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentStream stream, ast *astCmd) (object, error) {
2024-04-10 10:45:58 +00:00
cmd, err := ec.lookupCmd(ast.Name)
if err != nil {
return nil, err
2024-04-10 10:45:58 +00:00
}
2024-04-10 12:19:11 +00:00
args, err := slices.MapWithError(ast.Args, func(a astCmdArg) (object, error) {
2024-04-10 10:45:58 +00:00
return e.evalArg(ctx, ec, a)
})
if err != nil {
return nil, err
2024-04-10 10:45:58 +00:00
}
2024-04-12 23:25:16 +00:00
invArgs := invocationArgs{ec: ec, inst: e.inst, args: args, currentStream: currentStream}
2024-04-12 23:25:16 +00:00
if currentStream != nil {
2024-04-11 10:47:59 +00:00
if si, ok := cmd.(streamInvokable); ok {
2024-04-12 23:25:16 +00:00
return si.invokeWithStream(ctx, currentStream, invArgs)
2024-04-11 10:47:59 +00:00
} else {
2024-04-12 23:25:16 +00:00
if err := currentStream.close(); err != nil {
2024-04-11 10:47:59 +00:00
return nil, err
}
}
}
return cmd.invoke(ctx, invArgs)
2024-04-10 10:45:58 +00:00
}
2024-04-10 12:19:11 +00:00
func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (object, error) {
switch {
case n.Literal != nil:
return e.evalLiteral(ctx, ec, n.Literal)
case n.Var != nil:
v, ok := ec.getVar(*n.Var)
if !ok {
return nil, fmt.Errorf("unknown variable %s", *n.Var)
}
return v, nil
2024-04-10 12:19:11 +00:00
case n.Sub != nil:
return e.evalSub(ctx, ec, n.Sub)
}
return nil, errors.New("unhandled arg type")
2024-04-10 10:45:58 +00:00
}
2024-04-10 12:19:11 +00:00
func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) (object, error) {
2024-04-10 10:45:58 +00:00
switch {
case n.Str != nil:
uq, err := strconv.Unquote(*n.Str)
if err != nil {
2024-04-11 12:05:05 +00:00
return nil, err
2024-04-10 10:45:58 +00:00
}
2024-04-10 12:19:11 +00:00
return strObject(uq), nil
2024-04-10 10:45:58 +00:00
case n.Ident != nil:
2024-04-10 12:19:11 +00:00
return strObject(*n.Ident), nil
2024-04-10 10:45:58 +00:00
}
2024-04-11 12:05:05 +00:00
return nil, errors.New("unhandled literal type")
2024-04-10 10:45:58 +00:00
}
2024-04-10 12:19:11 +00:00
func (e evaluator) evalSub(ctx context.Context, ec *evalCtx, n *astPipeline) (object, error) {
2024-04-11 12:05:05 +00:00
pipelineRes, err := e.evalPipeline(ctx, ec, n)
2024-04-10 12:19:11 +00:00
if err != nil {
2024-04-11 12:05:05 +00:00
return nil, err
2024-04-10 12:19:11 +00:00
}
switch v := pipelineRes.(type) {
case stream:
// TODO: use proper lists here, not a string join
sb := strings.Builder{}
2024-04-11 12:05:05 +00:00
if err := forEach(v, func(o object, _ int) error {
2024-04-10 12:19:11 +00:00
// TODO: use o.String()
sb.WriteString(fmt.Sprint(o))
return nil
}); err != nil {
2024-04-11 12:05:05 +00:00
return nil, err
2024-04-10 12:19:11 +00:00
}
return strObject(sb.String()), nil
}
return pipelineRes, nil
}