ucl/cmdlang/eval.go

234 lines
5.3 KiB
Go
Raw Normal View History

2024-04-10 10:45:58 +00:00
package cmdlang
import (
"context"
"errors"
"strconv"
)
type evaluator struct {
2024-04-12 23:25:16 +00:00
inst *Inst
2024-04-10 10:45:58 +00:00
}
2024-04-13 11:46:50 +00:00
func (e evaluator) evalBlock(ctx context.Context, ec *evalCtx, n *astBlock) (lastRes object, err error) {
// TODO: push scope?
for _, s := range n.Statements {
lastRes, err = e.evalStatement(ctx, ec, s)
if err != nil {
return nil, err
}
}
return lastRes, nil
}
func (e evaluator) evalScript(ctx context.Context, ec *evalCtx, n *astScript) (lastRes object, err error) {
return e.evalStatement(ctx, ec, n.Statements)
}
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 {
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 {
out, err := e.evalCmd(ctx, ec, res, rest)
if err != nil {
return nil, err
}
res = out
}
return res, nil
}
func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object, ast *astCmd) (object, error) {
switch {
case ast.Name.Ident != nil:
name := *ast.Name.Ident
// Regular command
if cmd := ec.lookupInvokable(name); cmd != nil {
return e.evalInvokable(ctx, ec, currentPipe, ast, cmd)
} else if macro := ec.lookupMacro(name); macro != nil {
2024-04-24 10:12:39 +00:00
return e.evalMacro(ctx, ec, currentPipe != nil, currentPipe, ast, macro)
} else {
2024-04-18 12:24:19 +00:00
return nil, errors.New("unknown command: " + name)
}
case len(ast.Args) > 0:
nameElem, err := e.evalArg(ctx, ec, ast.Name)
if err != nil {
return nil, err
}
inv, ok := nameElem.(invokable)
if !ok {
return nil, errors.New("command is not invokable")
}
return e.evalInvokable(ctx, ec, currentPipe, ast, inv)
2024-04-10 10:45:58 +00:00
}
nameElem, err := e.evalArg(ctx, ec, ast.Name)
if err != nil {
return nil, err
}
return nameElem, nil
2024-04-13 11:46:50 +00:00
}
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe object, ast *astCmd, cmd invokable) (object, error) {
2024-04-17 10:43:25 +00:00
var (
pargs listObject
kwargs map[string]*listObject
argsPtr *listObject
)
argsPtr = &pargs
if currentPipe != nil {
argsPtr.Append(currentPipe)
}
2024-04-17 10:43:25 +00:00
for _, arg := range ast.Args {
if ident := arg.Ident; ident != nil && (*ident)[0] == '-' {
// Arg switch
if kwargs == nil {
kwargs = make(map[string]*listObject)
}
argsPtr = &listObject{}
kwargs[(*ident)[1:]] = argsPtr
} else {
ae, err := e.evalArg(ctx, ec, arg)
if err != nil {
return nil, err
}
argsPtr.Append(ae)
}
2024-04-10 10:45:58 +00:00
}
invArgs := invocationArgs{eval: e, ec: ec, inst: e.inst, args: pargs, kwargs: kwargs}
return cmd.invoke(ctx, invArgs)
2024-04-10 10:45:58 +00:00
}
2024-04-24 10:12:39 +00:00
func (e evaluator) evalMacro(ctx context.Context, ec *evalCtx, hasPipe bool, pipeArg object, ast *astCmd, cmd macroable) (object, error) {
2024-04-13 11:46:50 +00:00
return cmd.invokeMacro(ctx, macroArgs{
eval: e,
ec: ec,
2024-04-24 10:12:39 +00:00
hasPipe: hasPipe,
pipeArg: pipeArg,
ast: ast,
2024-04-13 11:46:50 +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)
2024-04-13 11:46:50 +00:00
case n.Ident != nil:
return strObject(*n.Ident), nil
case n.Var != nil:
2024-04-13 11:46:50 +00:00
if v, ok := ec.getVar(*n.Var); ok {
return v, nil
}
2024-04-13 11:46:50 +00:00
return nil, nil
case n.MaybeSub != nil:
sub := n.MaybeSub.Sub
if sub == nil {
return nil, nil
}
return e.evalSub(ctx, ec, sub)
2024-04-16 12:05:21 +00:00
case n.ListOrHash != nil:
return e.evalListOrHash(ctx, ec, n.ListOrHash)
2024-04-13 11:46:50 +00:00
case n.Block != nil:
return blockObject{block: n.Block}, nil
2024-04-10 12:19:11 +00:00
}
return nil, errors.New("unhandled arg type")
2024-04-10 10:45:58 +00:00
}
2024-04-16 12:05:21 +00:00
func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astListOrHash) (object, error) {
if loh.EmptyList {
return listObject{}, nil
} else if loh.EmptyHash {
return hashObject{}, nil
}
if firstIsHash := loh.Elements[0].Right != nil; firstIsHash {
h := hashObject{}
for _, el := range loh.Elements {
if el.Right == nil {
return nil, errors.New("miss-match of lists and hash")
}
n, err := e.evalArg(ctx, ec, el.Left)
if err != nil {
return nil, err
}
v, err := e.evalArg(ctx, ec, *el.Right)
if err != nil {
return nil, err
}
h[n.String()] = v
}
return h, nil
}
l := listObject{}
for _, el := range loh.Elements {
if el.Right != nil {
return nil, errors.New("miss-match of lists and hash")
}
v, err := e.evalArg(ctx, ec, el.Left)
if err != nil {
return nil, err
}
l = append(l, v)
}
return l, nil
}
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-24 10:12:39 +00:00
case n.Int != nil:
return intObject(*n.Int), 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
}
return pipelineRes, nil
}