Added macros and the if macro
This commit is contained in:
		
							parent
							
								
									730dc46095
								
							
						
					
					
						commit
						d6cc449b40
					
				| 
						 | 
					@ -7,14 +7,19 @@ import (
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type astLiteral struct {
 | 
					type astLiteral struct {
 | 
				
			||||||
	Str   *string `parser:"@String"`
 | 
						Str *string `parser:"@String"`
 | 
				
			||||||
	Ident *string `parser:" | @Ident"`
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type astBlock struct {
 | 
				
			||||||
 | 
						Statements []*astStatements `parser:"LC NL? @@ NL? RC"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type astCmdArg struct {
 | 
					type astCmdArg struct {
 | 
				
			||||||
	Literal *astLiteral  `parser:"@@"`
 | 
						Literal *astLiteral  `parser:"@@"`
 | 
				
			||||||
 | 
						Ident   *string      `parser:"| @Ident"`
 | 
				
			||||||
	Var     *string      `parser:"| DOLLAR @Ident"`
 | 
						Var     *string      `parser:"| DOLLAR @Ident"`
 | 
				
			||||||
	Sub     *astPipeline `parser:"| LP @@ RP"`
 | 
						Sub     *astPipeline `parser:"| LP @@ RP"`
 | 
				
			||||||
 | 
						Block   *astBlock    `parser:"| @@"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type astCmd struct {
 | 
					type astCmd struct {
 | 
				
			||||||
| 
						 | 
					@ -29,29 +34,30 @@ type astPipeline struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type astStatements struct {
 | 
					type astStatements struct {
 | 
				
			||||||
	First *astPipeline   `parser:"@@"`
 | 
						First *astPipeline   `parser:"@@"`
 | 
				
			||||||
	Rest  []*astPipeline `parser:"( (SEMICL | NL)+ @@ )*"` // TODO: also add support for newlines
 | 
						Rest  []*astPipeline `parser:"( NL+ @@ )*"` // TODO: also add support for newlines
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type astBlock struct {
 | 
					type astScript struct {
 | 
				
			||||||
	Statements *astStatements `parser:"'{'  "`
 | 
						Statements *astStatements `parser:"NL* @@ NL*"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var scanner = lexer.MustStateful(lexer.Rules{
 | 
					var scanner = lexer.MustStateful(lexer.Rules{
 | 
				
			||||||
	"Root": {
 | 
						"Root": {
 | 
				
			||||||
		{"Whitespace", `[ ]`, nil},
 | 
							{"Whitespace", `[ \t]+`, nil},
 | 
				
			||||||
		{"NL", `\n\s*`, nil},
 | 
					 | 
				
			||||||
		{"String", `"(\\"|[^"])*"`, nil},
 | 
							{"String", `"(\\"|[^"])*"`, nil},
 | 
				
			||||||
		{"DOLLAR", `\$`, nil},
 | 
							{"DOLLAR", `\$`, nil},
 | 
				
			||||||
		{"LP", `\(`, nil},
 | 
							{"LP", `\(`, nil},
 | 
				
			||||||
		{"RP", `\)`, nil},
 | 
							{"RP", `\)`, nil},
 | 
				
			||||||
		{"SEMICL", `;`, nil},
 | 
							{"LC", `\{`, nil},
 | 
				
			||||||
 | 
							{"RC", `\}`, nil},
 | 
				
			||||||
 | 
							{"NL", `[;\n][; \n\t]*`, nil},
 | 
				
			||||||
		{"PIPE", `\|`, nil},
 | 
							{"PIPE", `\|`, nil},
 | 
				
			||||||
		{"Ident", `\w+`, nil},
 | 
							{"Ident", `\w+`, nil},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
var parser = participle.MustBuild[astStatements](participle.Lexer(scanner),
 | 
					var parser = participle.MustBuild[astScript](participle.Lexer(scanner),
 | 
				
			||||||
	participle.Elide("Whitespace"))
 | 
						participle.Elide("Whitespace"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func parse(r io.Reader) (*astStatements, error) {
 | 
					func parse(r io.Reader) (*astScript, error) {
 | 
				
			||||||
	return parser.Parse("test", r)
 | 
						return parser.Parse("test", r)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ package cmdlang
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bufio"
 | 
						"bufio"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
| 
						 | 
					@ -14,6 +15,7 @@ func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) {
 | 
				
			||||||
		if _, err := fmt.Fprintln(args.inst.Out()); err != nil {
 | 
							if _, err := fmt.Fprintln(args.inst.Out()); err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							return nil, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var line strings.Builder
 | 
						var line strings.Builder
 | 
				
			||||||
| 
						 | 
					@ -83,6 +85,10 @@ func (f *fileLinesStream) String() string {
 | 
				
			||||||
	return fmt.Sprintf("fileLinesStream{file: %v}", f.filename)
 | 
						return fmt.Sprintf("fileLinesStream{file: %v}", f.filename)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f *fileLinesStream) Truthy() bool {
 | 
				
			||||||
 | 
						return true // ??
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (f *fileLinesStream) next() (object, error) {
 | 
					func (f *fileLinesStream) next() (object, error) {
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -111,25 +117,40 @@ func (f *fileLinesStream) close() error {
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					func ifBuiltin(ctx context.Context, args macroArgs) (object, error) {
 | 
				
			||||||
func errorTestBuiltin(ctx context.Context, inStream stream, args invocationArgs) (object, error) {
 | 
						if args.nargs() < 2 {
 | 
				
			||||||
	return &timeBombStream{inStream, 2}, nil
 | 
							return nil, errors.New("need at least 2 arguments")
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type timeBombStream struct {
 | 
					 | 
				
			||||||
	in stream
 | 
					 | 
				
			||||||
	x  int
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (ms *timeBombStream) next() (object, error) {
 | 
					 | 
				
			||||||
	if ms.x > 0 {
 | 
					 | 
				
			||||||
		ms.x--
 | 
					 | 
				
			||||||
		return ms.in.next()
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil, errors.New("BOOM")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ms *timeBombStream) close() error {
 | 
						if guard, err := args.evalArg(ctx, 0); err == nil && isTruthy(guard) {
 | 
				
			||||||
	return ms.in.close()
 | 
							return args.evalBlock(ctx, 1)
 | 
				
			||||||
 | 
						} else if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						args.shift(2)
 | 
				
			||||||
 | 
						for args.identIs(ctx, 0, "elif") {
 | 
				
			||||||
 | 
							args.shift(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if args.nargs() < 2 {
 | 
				
			||||||
 | 
								return nil, errors.New("need at least 2 arguments")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if guard, err := args.evalArg(ctx, 0); err == nil && isTruthy(guard) {
 | 
				
			||||||
 | 
								return args.evalBlock(ctx, 1)
 | 
				
			||||||
 | 
							} else if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							args.shift(2)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if args.identIs(ctx, 0, "else") && args.nargs() > 1 {
 | 
				
			||||||
 | 
							return args.evalBlock(ctx, 1)
 | 
				
			||||||
 | 
						} else if args.nargs() == 0 {
 | 
				
			||||||
 | 
							// no elif or else
 | 
				
			||||||
 | 
							return nil, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil, errors.New("malformed if-elif-else")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,9 @@
 | 
				
			||||||
package cmdlang
 | 
					package cmdlang
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"errors"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type evalCtx struct {
 | 
					type evalCtx struct {
 | 
				
			||||||
	parent   *evalCtx
 | 
						parent   *evalCtx
 | 
				
			||||||
	commands map[string]invokable
 | 
						commands map[string]invokable
 | 
				
			||||||
 | 
						macros   map[string]macroable
 | 
				
			||||||
	vars     map[string]object
 | 
						vars     map[string]object
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +15,14 @@ func (ec *evalCtx) addCmd(name string, inv invokable) {
 | 
				
			||||||
	ec.commands[name] = inv
 | 
						ec.commands[name] = inv
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ec *evalCtx) addMacro(name string, inv macroable) {
 | 
				
			||||||
 | 
						if ec.macros == nil {
 | 
				
			||||||
 | 
							ec.macros = make(map[string]macroable)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ec.macros[name] = inv
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ec *evalCtx) setVar(name string, val object) {
 | 
					func (ec *evalCtx) setVar(name string, val object) {
 | 
				
			||||||
	if ec.vars == nil {
 | 
						if ec.vars == nil {
 | 
				
			||||||
		ec.vars = make(map[string]object)
 | 
							ec.vars = make(map[string]object)
 | 
				
			||||||
| 
						 | 
					@ -39,16 +44,30 @@ func (ec *evalCtx) getVar(name string) (object, bool) {
 | 
				
			||||||
	return nil, false
 | 
						return nil, false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ec *evalCtx) lookupCmd(name string) (invokable, error) {
 | 
					func (ec *evalCtx) lookupInvokable(name string) invokable {
 | 
				
			||||||
	for e := ec; e != nil; e = e.parent {
 | 
						if ec == nil {
 | 
				
			||||||
		if e.commands == nil {
 | 
							return nil
 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if cmd, ok := e.commands[name]; ok {
 | 
					 | 
				
			||||||
			return cmd, nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil, errors.New("name " + name + " not found")
 | 
					
 | 
				
			||||||
 | 
						for e := ec; e != nil; e = e.parent {
 | 
				
			||||||
 | 
							if cmd, ok := e.commands[name]; ok {
 | 
				
			||||||
 | 
								return cmd
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ec.parent.lookupInvokable(name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ec *evalCtx) lookupMacro(name string) macroable {
 | 
				
			||||||
 | 
						if ec == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for e := ec; e != nil; e = e.parent {
 | 
				
			||||||
 | 
							if cmd, ok := e.macros[name]; ok {
 | 
				
			||||||
 | 
								return cmd
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ec.parent.lookupMacro(name)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,22 @@ type evaluator struct {
 | 
				
			||||||
	inst *Inst
 | 
						inst *Inst
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStatements) (object, error) {
 | 
					func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStatements) (object, error) {
 | 
				
			||||||
	res, err := e.evalPipeline(ctx, ec, n.First)
 | 
						res, err := e.evalPipeline(ctx, ec, n.First)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -60,11 +76,16 @@ func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentStream stream, ast *astCmd) (object, error) {
 | 
					func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentStream stream, ast *astCmd) (object, error) {
 | 
				
			||||||
	cmd, err := ec.lookupCmd(ast.Name)
 | 
						if cmd := ec.lookupInvokable(ast.Name); cmd != nil {
 | 
				
			||||||
	if err != nil {
 | 
							return e.evalInvokable(ctx, ec, currentStream, ast, cmd)
 | 
				
			||||||
		return nil, err
 | 
						} else if macro := ec.lookupMacro(ast.Name); macro != nil {
 | 
				
			||||||
 | 
							return e.evalMacro(ctx, ec, currentStream, ast, macro)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil, errors.New("unknown command")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentStream stream, ast *astCmd, cmd invokable) (object, error) {
 | 
				
			||||||
	args, err := slices.MapWithError(ast.Args, func(a astCmdArg) (object, error) {
 | 
						args, err := slices.MapWithError(ast.Args, func(a astCmdArg) (object, error) {
 | 
				
			||||||
		return e.evalArg(ctx, ec, a)
 | 
							return e.evalArg(ctx, ec, a)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
| 
						 | 
					@ -87,18 +108,30 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentStream strea
 | 
				
			||||||
	return cmd.invoke(ctx, invArgs)
 | 
						return cmd.invoke(ctx, invArgs)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (object, error) {
 | 
					func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (object, error) {
 | 
				
			||||||
	switch {
 | 
						switch {
 | 
				
			||||||
	case n.Literal != nil:
 | 
						case n.Literal != nil:
 | 
				
			||||||
		return e.evalLiteral(ctx, ec, n.Literal)
 | 
							return e.evalLiteral(ctx, ec, n.Literal)
 | 
				
			||||||
 | 
						case n.Ident != nil:
 | 
				
			||||||
 | 
							return strObject(*n.Ident), nil
 | 
				
			||||||
	case n.Var != nil:
 | 
						case n.Var != nil:
 | 
				
			||||||
		v, ok := ec.getVar(*n.Var)
 | 
							if v, ok := ec.getVar(*n.Var); ok {
 | 
				
			||||||
		if !ok {
 | 
								return v, nil
 | 
				
			||||||
			return nil, fmt.Errorf("unknown variable %s", *n.Var)
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return v, nil
 | 
							return nil, nil
 | 
				
			||||||
	case n.Sub != nil:
 | 
						case n.Sub != nil:
 | 
				
			||||||
		return e.evalSub(ctx, ec, n.Sub)
 | 
							return e.evalSub(ctx, ec, n.Sub)
 | 
				
			||||||
 | 
						case n.Block != nil:
 | 
				
			||||||
 | 
							return blockObject{block: n.Block}, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil, errors.New("unhandled arg type")
 | 
						return nil, errors.New("unhandled arg type")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -111,8 +144,6 @@ func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral)
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return strObject(uq), nil
 | 
							return strObject(uq), nil
 | 
				
			||||||
	case n.Ident != nil:
 | 
					 | 
				
			||||||
		return strObject(*n.Ident), nil
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil, errors.New("unhandled literal type")
 | 
						return nil, errors.New("unhandled literal type")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,8 @@ func New(opts ...InstOption) *Inst {
 | 
				
			||||||
	rootEC.addCmd("toUpper", invokableStreamFunc(toUpperBuiltin))
 | 
						rootEC.addCmd("toUpper", invokableStreamFunc(toUpperBuiltin))
 | 
				
			||||||
	rootEC.addCmd("cat", invokableFunc(catBuiltin))
 | 
						rootEC.addCmd("cat", invokableFunc(catBuiltin))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rootEC.addMacro("if", macroFunc(ifBuiltin))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	//rootEC.addCmd("testTimebomb", invokableStreamFunc(errorTestBuiltin))
 | 
						//rootEC.addCmd("testTimebomb", invokableStreamFunc(errorTestBuiltin))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rootEC.setVar("hello", strObject("world"))
 | 
						rootEC.setVar("hello", strObject("world"))
 | 
				
			||||||
| 
						 | 
					@ -76,7 +78,7 @@ func (inst *Inst) eval(ctx context.Context, expr string) (object, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	eval := evaluator{inst: inst}
 | 
						eval := evaluator{inst: inst}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return eval.evalStatement(ctx, inst.rootEC, ast)
 | 
						return eval.evalScript(ctx, inst.rootEC, ast)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (inst *Inst) EvalAndDisplay(ctx context.Context, expr string) error {
 | 
					func (inst *Inst) EvalAndDisplay(ctx context.Context, expr string) error {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,6 @@ import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"github.com/lmika/cmdlang-proto/cmdlang"
 | 
						"github.com/lmika/cmdlang-proto/cmdlang"
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,8 +37,6 @@ func TestInst_Eval(t *testing.T) {
 | 
				
			||||||
		{desc: "multi 1", expr: `firstarg "hello" ; firstarg "world"`, want: "world"},
 | 
							{desc: "multi 1", expr: `firstarg "hello" ; firstarg "world"`, want: "world"},
 | 
				
			||||||
		{desc: "multi 2", expr: `pipe "hello" | toUpper ; firstarg "world"`, want: "world"}, // TODO: assert for leaks
 | 
							{desc: "multi 2", expr: `pipe "hello" | toUpper ; firstarg "world"`, want: "world"}, // TODO: assert for leaks
 | 
				
			||||||
		{desc: "multi 3", expr: `set new "this is new" ; firstarg $new`, want: "this is new"},
 | 
							{desc: "multi 3", expr: `set new "this is new" ; firstarg $new`, want: "this is new"},
 | 
				
			||||||
 | 
					 | 
				
			||||||
		{desc: "multi-line 1", expr: "echo \"Hello\" \n echo \"world\"", want: "world"},
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, tt := range tests {
 | 
						for _, tt := range tests {
 | 
				
			||||||
| 
						 | 
					@ -56,7 +53,7 @@ func TestInst_Eval(t *testing.T) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestInst_Builtins_Echo(t *testing.T) {
 | 
					func TestBuiltins_Echo(t *testing.T) {
 | 
				
			||||||
	tests := []struct {
 | 
						tests := []struct {
 | 
				
			||||||
		desc string
 | 
							desc string
 | 
				
			||||||
		expr string
 | 
							expr string
 | 
				
			||||||
| 
						 | 
					@ -65,8 +62,98 @@ func TestInst_Builtins_Echo(t *testing.T) {
 | 
				
			||||||
		{desc: "no args", expr: `echo`, want: "\n"},
 | 
							{desc: "no args", expr: `echo`, want: "\n"},
 | 
				
			||||||
		{desc: "single arg", expr: `echo "hello"`, want: "hello\n"},
 | 
							{desc: "single arg", expr: `echo "hello"`, want: "hello\n"},
 | 
				
			||||||
		{desc: "dual args", expr: `echo "hello " "world"`, want: "hello world\n"},
 | 
							{desc: "dual args", expr: `echo "hello " "world"`, want: "hello world\n"},
 | 
				
			||||||
		{desc: "args to singleton stream", expr: `echo "aye" "bee" "see" | toUpper`, want: "AYEBEESEE\n"},
 | 
							{desc: "multi-line 1", expr: `
 | 
				
			||||||
		{desc: "multi-line 1", expr: joinLines(`echo "Hello"`, `echo "world"`), want: "Hello\nworld"},
 | 
								echo "Hello"
 | 
				
			||||||
 | 
								echo "world"
 | 
				
			||||||
 | 
							`, want: "Hello\nworld\n"},
 | 
				
			||||||
 | 
							{desc: "multi-line 2", expr: `
 | 
				
			||||||
 | 
								echo "Hello"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								echo "world"
 | 
				
			||||||
 | 
							`, want: "Hello\nworld\n"},
 | 
				
			||||||
 | 
							{desc: "multi-line 3", expr: `
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					;;;
 | 
				
			||||||
 | 
								echo "Hello"
 | 
				
			||||||
 | 
					;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								echo "world"
 | 
				
			||||||
 | 
					;
 | 
				
			||||||
 | 
							`, want: "Hello\nworld\n"},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.desc, func(t *testing.T) {
 | 
				
			||||||
 | 
								ctx := context.Background()
 | 
				
			||||||
 | 
								outW := bytes.NewBuffer(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								inst := cmdlang.New(cmdlang.WithOut(outW), cmdlang.WithTestBuiltin())
 | 
				
			||||||
 | 
								res, err := inst.Eval(ctx, tt.expr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								assert.NoError(t, err)
 | 
				
			||||||
 | 
								assert.Nil(t, res)
 | 
				
			||||||
 | 
								assert.Equal(t, tt.want, outW.String())
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestBuiltins_If(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							desc string
 | 
				
			||||||
 | 
							expr string
 | 
				
			||||||
 | 
							want string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{desc: "single then", expr: `
 | 
				
			||||||
 | 
								set x "Hello"
 | 
				
			||||||
 | 
								if $x {
 | 
				
			||||||
 | 
									echo "true"
 | 
				
			||||||
 | 
								}`, want: "true\n(nil)\n"},
 | 
				
			||||||
 | 
							{desc: "single then and else", expr: `
 | 
				
			||||||
 | 
								set x "Hello"
 | 
				
			||||||
 | 
								if $x {
 | 
				
			||||||
 | 
									echo "true"
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									echo "false"
 | 
				
			||||||
 | 
								}`, want: "true\n(nil)\n"},
 | 
				
			||||||
 | 
							{desc: "single then, elif and else", expr: `
 | 
				
			||||||
 | 
								set x "Hello"
 | 
				
			||||||
 | 
								if $y {
 | 
				
			||||||
 | 
									echo "y is true"
 | 
				
			||||||
 | 
								} elif $x {
 | 
				
			||||||
 | 
									echo "x is true"
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									echo "nothings x"
 | 
				
			||||||
 | 
								}`, want: "x is true\n(nil)\n"},
 | 
				
			||||||
 | 
							{desc: "single then and elif, no else", expr: `
 | 
				
			||||||
 | 
								set x "Hello"
 | 
				
			||||||
 | 
								if $y {
 | 
				
			||||||
 | 
									echo "y is true"
 | 
				
			||||||
 | 
								} elif $x {
 | 
				
			||||||
 | 
									echo "x is true"
 | 
				
			||||||
 | 
								}`, want: "x is true\n(nil)\n"},
 | 
				
			||||||
 | 
							{desc: "single then, two elif, and else", expr: `
 | 
				
			||||||
 | 
								set x "Hello"
 | 
				
			||||||
 | 
								if $z {
 | 
				
			||||||
 | 
									echo "z is true"
 | 
				
			||||||
 | 
								} elif $y {
 | 
				
			||||||
 | 
									echo "y is true"
 | 
				
			||||||
 | 
								} elif $x {
 | 
				
			||||||
 | 
									echo "x is true"
 | 
				
			||||||
 | 
								}`, want: "x is true\n(nil)\n"},
 | 
				
			||||||
 | 
							{desc: "single then, two elif, and else, expecting else", expr: `
 | 
				
			||||||
 | 
								if $z {
 | 
				
			||||||
 | 
									echo "z is true"
 | 
				
			||||||
 | 
								} elif $y {
 | 
				
			||||||
 | 
									echo "y is true"
 | 
				
			||||||
 | 
								} elif $x {
 | 
				
			||||||
 | 
									echo "x is true"
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									echo "none is true"
 | 
				
			||||||
 | 
								}`, want: "none is true\n(nil)\n"},
 | 
				
			||||||
 | 
							{desc: "compressed then", expr: `set x "Hello" ; if $x { echo "true" }`, want: "true\n(nil)\n"},
 | 
				
			||||||
 | 
							{desc: "compressed else", expr: `if $x { echo "true" } else { echo "false" }`, want: "false\n(nil)\n"},
 | 
				
			||||||
 | 
							{desc: "compressed if", expr: `if $x { echo "x" } elif $y { echo "y" } else { echo "false" }`, want: "false\n(nil)\n"},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, tt := range tests {
 | 
						for _, tt := range tests {
 | 
				
			||||||
| 
						 | 
					@ -82,7 +169,3 @@ func TestInst_Builtins_Echo(t *testing.T) {
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func joinLines(ls ...string) string {
 | 
					 | 
				
			||||||
	return strings.Join(ls, "\n")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type object interface {
 | 
					type object interface {
 | 
				
			||||||
	String() string
 | 
						String() string
 | 
				
			||||||
 | 
						Truthy() bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type strObject string
 | 
					type strObject string
 | 
				
			||||||
| 
						 | 
					@ -17,6 +18,10 @@ func (s strObject) String() string {
 | 
				
			||||||
	return string(s)
 | 
						return string(s)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s strObject) Truthy() bool {
 | 
				
			||||||
 | 
						return string(s) != ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func toGoValue(obj object) (interface{}, bool) {
 | 
					func toGoValue(obj object) (interface{}, bool) {
 | 
				
			||||||
	switch v := obj.(type) {
 | 
						switch v := obj.(type) {
 | 
				
			||||||
	case nil:
 | 
						case nil:
 | 
				
			||||||
| 
						 | 
					@ -28,6 +33,57 @@ func toGoValue(obj object) (interface{}, bool) {
 | 
				
			||||||
	return nil, false
 | 
						return nil, false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type macroArgs struct {
 | 
				
			||||||
 | 
						eval          evaluator
 | 
				
			||||||
 | 
						ec            *evalCtx
 | 
				
			||||||
 | 
						currentStream stream
 | 
				
			||||||
 | 
						ast           *astCmd
 | 
				
			||||||
 | 
						argShift      int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ma macroArgs) nargs() int {
 | 
				
			||||||
 | 
						return len(ma.ast.Args[ma.argShift:])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ma *macroArgs) shift(n int) {
 | 
				
			||||||
 | 
						ma.argShift += n
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ma macroArgs) identIs(ctx context.Context, n int, expectedIdent string) bool {
 | 
				
			||||||
 | 
						if n >= len(ma.ast.Args[ma.argShift:]) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lit := ma.ast.Args[ma.argShift+n].Ident
 | 
				
			||||||
 | 
						if lit == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return *lit == expectedIdent
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ma macroArgs) evalArg(ctx context.Context, n int) (object, error) {
 | 
				
			||||||
 | 
						if n >= len(ma.ast.Args[ma.argShift:]) {
 | 
				
			||||||
 | 
							return nil, errors.New("not enough arguments") // FIX
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ma.eval.evalArg(ctx, ma.ec, ma.ast.Args[ma.argShift+n])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ma macroArgs) evalBlock(ctx context.Context, n int) (object, error) {
 | 
				
			||||||
 | 
						obj, err := ma.evalArg(ctx, n)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						block, ok := obj.(blockObject)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, errors.New("not a block object")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ma.eval.evalBlock(ctx, ma.ec, block.block)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type invocationArgs struct {
 | 
					type invocationArgs struct {
 | 
				
			||||||
	inst          *Inst
 | 
						inst          *Inst
 | 
				
			||||||
	ec            *evalCtx
 | 
						ec            *evalCtx
 | 
				
			||||||
| 
						 | 
					@ -58,6 +114,10 @@ type invokable interface {
 | 
				
			||||||
	invoke(ctx context.Context, args invocationArgs) (object, error)
 | 
						invoke(ctx context.Context, args invocationArgs) (object, error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type macroable interface {
 | 
				
			||||||
 | 
						invokeMacro(ctx context.Context, args macroArgs) (object, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type streamInvokable interface {
 | 
					type streamInvokable interface {
 | 
				
			||||||
	invokable
 | 
						invokable
 | 
				
			||||||
	invokeWithStream(context.Context, stream, invocationArgs) (object, error)
 | 
						invokeWithStream(context.Context, stream, invocationArgs) (object, error)
 | 
				
			||||||
| 
						 | 
					@ -78,3 +138,28 @@ func (i invokableStreamFunc) invoke(ctx context.Context, args invocationArgs) (o
 | 
				
			||||||
func (i invokableStreamFunc) invokeWithStream(ctx context.Context, inStream stream, args invocationArgs) (object, error) {
 | 
					func (i invokableStreamFunc) invokeWithStream(ctx context.Context, inStream stream, args invocationArgs) (object, error) {
 | 
				
			||||||
	return i(ctx, inStream, args)
 | 
						return i(ctx, inStream, args)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type blockObject struct {
 | 
				
			||||||
 | 
						block *astBlock
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (bo blockObject) String() string {
 | 
				
			||||||
 | 
						return "block"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (bo blockObject) Truthy() bool {
 | 
				
			||||||
 | 
						return len(bo.block.Statements) > 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type macroFunc func(ctx context.Context, args macroArgs) (object, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (i macroFunc) invokeMacro(ctx context.Context, args macroArgs) (object, error) {
 | 
				
			||||||
 | 
						return i(ctx, args)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isTruthy(obj object) bool {
 | 
				
			||||||
 | 
						if obj == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return obj.Truthy()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,6 +74,10 @@ func (s *singletonStream) String() string {
 | 
				
			||||||
	return s.t.String()
 | 
						return s.t.String()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *singletonStream) Truthy() bool {
 | 
				
			||||||
 | 
						return !s.consumed
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *singletonStream) next() (object, error) {
 | 
					func (s *singletonStream) next() (object, error) {
 | 
				
			||||||
	if s.consumed {
 | 
						if s.consumed {
 | 
				
			||||||
		return nil, io.EOF
 | 
							return nil, io.EOF
 | 
				
			||||||
| 
						 | 
					@ -93,6 +97,10 @@ func (s *listIterStream) String() string {
 | 
				
			||||||
	return fmt.Sprintf("listIterStream{list: %v}", s.list)
 | 
						return fmt.Sprintf("listIterStream{list: %v}", s.list)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *listIterStream) Truthy() bool {
 | 
				
			||||||
 | 
						return len(s.list) > s.cusr
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *listIterStream) next() (o object, err error) {
 | 
					func (s *listIterStream) next() (o object, err error) {
 | 
				
			||||||
	if s.cusr >= len(s.list) {
 | 
						if s.cusr >= len(s.list) {
 | 
				
			||||||
		return nil, io.EOF
 | 
							return nil, io.EOF
 | 
				
			||||||
| 
						 | 
					@ -115,6 +123,10 @@ func (ms mapFilterStream) String() string {
 | 
				
			||||||
	return fmt.Sprintf("mapFilterStream{in: %v}", ms.in)
 | 
						return fmt.Sprintf("mapFilterStream{in: %v}", ms.in)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ms mapFilterStream) Truthy() bool {
 | 
				
			||||||
 | 
						return true // ???
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ms mapFilterStream) next() (object, error) {
 | 
					func (ms mapFilterStream) next() (object, error) {
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		u, err := ms.in.next()
 | 
							u, err := ms.in.next()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue