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
|
|
|
"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-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 {
|
|
|
|
// 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)
|
2024-04-10 11:58:06 +00:00
|
|
|
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)
|
2024-04-10 11:58:06 +00:00
|
|
|
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-13 11:46:50 +00:00
|
|
|
if cmd := ec.lookupInvokable(ast.Name); cmd != nil {
|
|
|
|
return e.evalInvokable(ctx, ec, currentStream, ast, cmd)
|
|
|
|
} else if macro := ec.lookupMacro(ast.Name); macro != nil {
|
|
|
|
return e.evalMacro(ctx, ec, currentStream, ast, macro)
|
2024-04-10 10:45:58 +00:00
|
|
|
}
|
|
|
|
|
2024-04-13 11:46:50 +00:00
|
|
|
return nil, errors.New("unknown command")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentStream stream, 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
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2024-04-17 10:43:25 +00:00
|
|
|
invArgs := invocationArgs{ec: ec, inst: e.inst, args: pargs, kwargs: kwargs, currentStream: currentStream}
|
2024-04-11 10:58:59 +00:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-11 10:58:59 +00:00
|
|
|
return cmd.invoke(ctx, invArgs)
|
2024-04-10 10:45:58 +00:00
|
|
|
}
|
|
|
|
|
2024-04-13 11:46:50 +00:00
|
|
|
func (e evaluator) evalMacro(ctx context.Context, ec *evalCtx, currentStream stream, ast *astCmd, cmd macroable) (object, error) {
|
|
|
|
return cmd.invokeMacro(ctx, macroArgs{
|
|
|
|
eval: e,
|
|
|
|
ec: ec,
|
|
|
|
currentStream: currentStream,
|
|
|
|
ast: ast,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
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
|
2024-04-11 10:58:59 +00:00
|
|
|
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-11 10:58:59 +00:00
|
|
|
}
|
2024-04-13 11:46:50 +00:00
|
|
|
return nil, nil
|
2024-04-10 12:19:11 +00:00
|
|
|
case n.Sub != nil:
|
|
|
|
return e.evalSub(ctx, ec, n.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-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
|
|
|
|
}
|