Added macros and the if macro
This commit is contained in:
parent
730dc46095
commit
d6cc449b40
|
@ -8,13 +8,18 @@ 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 {
|
||||||
|
if ec == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
for e := ec; e != nil; e = e.parent {
|
for e := ec; e != nil; e = e.parent {
|
||||||
if e.commands == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd, ok := e.commands[name]; ok {
|
if cmd, ok := e.commands[name]; ok {
|
||||||
return cmd, nil
|
return cmd
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
return ec.parent.lookupInvokable(name)
|
||||||
return nil, errors.New("name " + name + " not found")
|
}
|
||||||
|
|
||||||
|
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 nil, fmt.Errorf("unknown variable %s", *n.Var)
|
|
||||||
}
|
|
||||||
return v, nil
|
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