Working out the tests
This commit is contained in:
parent
1cf10478e2
commit
7e06f38802
|
@ -62,6 +62,23 @@ func toUpperBuiltin(ctx context.Context, inStream stream, args invocationArgs) (
|
|||
}, nil
|
||||
}
|
||||
|
||||
func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||
if err := args.expectArgn(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := args.args[0]
|
||||
r := args.args[1]
|
||||
|
||||
switch lv := l.(type) {
|
||||
case strObject:
|
||||
if rv, ok := r.(strObject); ok {
|
||||
return boolObject(lv == rv), nil
|
||||
}
|
||||
}
|
||||
return boolObject(false), nil
|
||||
}
|
||||
|
||||
func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||
var sb strings.Builder
|
||||
|
||||
|
@ -122,6 +139,23 @@ func mapBuiltin(ctx context.Context, inStream stream, args invocationArgs) (obje
|
|||
}, nil
|
||||
}
|
||||
|
||||
func firstBuiltin(ctx context.Context, inStream stream, args invocationArgs) (object, error) {
|
||||
args, strm, err := args.streamableSource(inStream)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer strm.close()
|
||||
|
||||
x, err := strm.next()
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return x, nil
|
||||
}
|
||||
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type fileLinesStream struct {
|
||||
filename string
|
||||
f *os.File
|
||||
|
|
|
@ -8,24 +8,30 @@ type evalCtx struct {
|
|||
vars map[string]object
|
||||
}
|
||||
|
||||
func (ec *evalCtx) forkAndIsolate() *evalCtx {
|
||||
newEc := &evalCtx{parent: ec}
|
||||
newEc.root = newEc
|
||||
return newEc
|
||||
}
|
||||
|
||||
func (ec *evalCtx) fork() *evalCtx {
|
||||
return &evalCtx{parent: ec, root: ec.root}
|
||||
}
|
||||
|
||||
func (ec *evalCtx) addCmd(name string, inv invokable) {
|
||||
if ec.commands == nil {
|
||||
ec.commands = make(map[string]invokable)
|
||||
if ec.root.commands == nil {
|
||||
ec.root.commands = make(map[string]invokable)
|
||||
}
|
||||
|
||||
ec.commands[name] = inv
|
||||
ec.root.commands[name] = inv
|
||||
}
|
||||
|
||||
func (ec *evalCtx) addMacro(name string, inv macroable) {
|
||||
if ec.macros == nil {
|
||||
ec.macros = make(map[string]macroable)
|
||||
if ec.root.macros == nil {
|
||||
ec.root.macros = make(map[string]macroable)
|
||||
}
|
||||
|
||||
ec.macros[name] = inv
|
||||
ec.root.macros[name] = inv
|
||||
}
|
||||
|
||||
func (ec *evalCtx) setVar(name string, val object) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package cmdlang
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
|
@ -76,6 +77,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentStream strea
|
|||
switch {
|
||||
case ast.Name.Ident != nil:
|
||||
name := *ast.Name.Ident
|
||||
log.Printf("--> invoking: %v", name)
|
||||
|
||||
// Regular command
|
||||
if cmd := ec.lookupInvokable(name); cmd != nil {
|
||||
|
@ -83,7 +85,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentStream strea
|
|||
} else if macro := ec.lookupMacro(name); macro != nil {
|
||||
return e.evalMacro(ctx, ec, currentStream, ast, macro)
|
||||
} else {
|
||||
return nil, errors.New("unknown command")
|
||||
return nil, errors.New("unknown command: " + name)
|
||||
}
|
||||
case len(ast.Args) > 0:
|
||||
nameElem, err := e.evalArg(ctx, ec, ast.Name)
|
||||
|
|
|
@ -34,7 +34,9 @@ func New(opts ...InstOption) *Inst {
|
|||
rootEC.addCmd("call", invokableFunc(callBuiltin))
|
||||
|
||||
rootEC.addCmd("map", invokableStreamFunc(mapBuiltin))
|
||||
rootEC.addCmd("head", invokableStreamFunc(firstBuiltin))
|
||||
|
||||
rootEC.addCmd("eq", invokableFunc(eqBuiltin))
|
||||
rootEC.addCmd("cat", invokableFunc(concatBuiltin))
|
||||
|
||||
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
||||
|
@ -86,6 +88,7 @@ func (inst *Inst) eval(ctx context.Context, expr string) (object, error) {
|
|||
|
||||
eval := evaluator{inst: inst}
|
||||
|
||||
// TODO: this should be a separate forkAndIsolate() session
|
||||
return eval.evalScript(ctx, inst.rootEC, ast)
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ func TestInst_Eval(t *testing.T) {
|
|||
|
||||
// Lists
|
||||
{desc: "list 1", expr: `firstarg ["1" "2" "3"]`, want: []any{"1", "2", "3"}},
|
||||
{desc: "list 2", expr: `set one "one" ; firstarg [$one (pipe "two" | toUpper) "three"]`, want: []any{"one", "TWO", "three"}},
|
||||
{desc: "list 2", expr: `set one "one" ; firstarg [$one (pipe "two" | toUpper | head) "three"]`, want: []any{"one", "TWO", "three"}},
|
||||
{desc: "list 3", expr: `firstarg []`, want: []any{}},
|
||||
|
||||
// Maps
|
||||
|
@ -52,7 +52,7 @@ func TestInst_Eval(t *testing.T) {
|
|||
set one "one" ; set n1 "1"
|
||||
firstarg [
|
||||
$one:$n1
|
||||
(firstarg "two" | toUpper):(firstarg "2" | toUpper)
|
||||
(firstarg "two" | toUpper | head):(firstarg "2" | toUpper | head)
|
||||
three:"3"
|
||||
]`, want: map[string]any{"one": "1", "TWO": "2", "three": "3"}},
|
||||
{desc: "map 4", expr: `firstarg [:]`, want: map[string]any{}},
|
||||
|
|
|
@ -46,6 +46,19 @@ func (s strObject) Truthy() bool {
|
|||
return string(s) != ""
|
||||
}
|
||||
|
||||
type boolObject bool
|
||||
|
||||
func (b boolObject) String() string {
|
||||
if b {
|
||||
return "(true)"
|
||||
}
|
||||
return "(false))"
|
||||
}
|
||||
|
||||
func (b boolObject) Truthy() bool {
|
||||
return bool(b)
|
||||
}
|
||||
|
||||
func toGoValue(obj object) (interface{}, bool) {
|
||||
switch v := obj.(type) {
|
||||
case nil:
|
||||
|
|
|
@ -201,3 +201,125 @@ func TestBuiltins_ForEach(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltins_Procs(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
expr string
|
||||
want string
|
||||
}{
|
||||
{desc: "simple procs", expr: `
|
||||
proc greet {
|
||||
echo "Hello, world"
|
||||
}
|
||||
|
||||
greet
|
||||
greet`, want: "Hello, world\nHello, world\n(nil)\n"},
|
||||
{desc: "multiple procs", expr: `
|
||||
proc greet { |what|
|
||||
echo "Hello, " $what
|
||||
}
|
||||
proc greetWorld { greet "world" }
|
||||
proc greetMoon { greet "moon" }
|
||||
proc greetTheThing { |what| greet (cat "the " $what) }
|
||||
|
||||
greetWorld
|
||||
greetMoon
|
||||
greetTheThing "sun"
|
||||
`, want: "Hello, world\nHello, moon\nHello, the sun\n(nil)\n"},
|
||||
{desc: "recursive procs", expr: `
|
||||
proc four4 { |xs|
|
||||
if (eq $xs "xxxx") {
|
||||
$xs
|
||||
}
|
||||
four4 (cat $xs "x")
|
||||
}
|
||||
|
||||
four4
|
||||
`, want: "xxxx\n"},
|
||||
{desc: "closures", expr: `
|
||||
proc makeGreeter { |greeting|
|
||||
proc { |what|
|
||||
echo $greeting ", " $what
|
||||
}
|
||||
}
|
||||
|
||||
set helloGreater (makeGreeter "Hello")
|
||||
$helloGreater "world"
|
||||
|
||||
set goodbye (makeGreeter "Goodbye cruel")
|
||||
$goodbye "world"
|
||||
|
||||
call (makeGreeter "Quick") "call me"
|
||||
|
||||
`, want: "Hello, world\nGoodbye cruel, world\nQuick, call me\n(nil)\n"},
|
||||
{desc: "modifying closed over variables", expr: `
|
||||
proc makeSetter {
|
||||
set bla "X"
|
||||
proc appendToBla { |x|
|
||||
set bla (cat $bla $x)
|
||||
}
|
||||
}
|
||||
|
||||
set er (makeSetter)
|
||||
call $er "xxx"
|
||||
call $er "yyy"
|
||||
`, want: "Xxxx\nXxxxyyy(nil)\n"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
outW := bytes.NewBuffer(nil)
|
||||
|
||||
inst := New(WithOut(outW), WithTestBuiltin())
|
||||
err := inst.EvalAndDisplay(ctx, tt.expr)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, outW.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltins_Map(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
expr string
|
||||
want string
|
||||
}{
|
||||
{desc: "map list", expr: `
|
||||
proc makeUpper { |x| $x | toUpper }
|
||||
|
||||
map ["a" "b" "c"] (proc { |x| makeUpper $x })
|
||||
`, want: "A\nB\nC\n"},
|
||||
{desc: "map list 2", expr: `
|
||||
set makeUpper (proc { |x| $x | toUpper })
|
||||
|
||||
map ["a" "b" "c"] $makeUpper
|
||||
`, want: "A\nB\nC\n"},
|
||||
{desc: "map list with stream", expr: `
|
||||
set makeUpper (proc { |x| $x | toUpper })
|
||||
|
||||
["a" "b" "c"] | map $makeUpper
|
||||
`, want: "A\nB\nC\n"},
|
||||
{desc: "map list with stream", expr: `
|
||||
set makeUpper (proc { |x| $x | toUpper })
|
||||
|
||||
set l (["a" "b" "c"] | map $makeUpper)
|
||||
echo $l
|
||||
`, want: "[A B C]\n"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
outW := bytes.NewBuffer(nil)
|
||||
|
||||
inst := New(WithOut(outW), WithTestBuiltin())
|
||||
err := inst.EvalAndDisplay(ctx, tt.expr)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, outW.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue