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
|
}, 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) {
|
func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
|
||||||
|
@ -122,6 +139,23 @@ func mapBuiltin(ctx context.Context, inStream stream, args invocationArgs) (obje
|
||||||
}, nil
|
}, 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 {
|
type fileLinesStream struct {
|
||||||
filename string
|
filename string
|
||||||
f *os.File
|
f *os.File
|
||||||
|
|
|
@ -8,24 +8,30 @@ type evalCtx struct {
|
||||||
vars map[string]object
|
vars map[string]object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *evalCtx) forkAndIsolate() *evalCtx {
|
||||||
|
newEc := &evalCtx{parent: ec}
|
||||||
|
newEc.root = newEc
|
||||||
|
return newEc
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *evalCtx) fork() *evalCtx {
|
func (ec *evalCtx) fork() *evalCtx {
|
||||||
return &evalCtx{parent: ec, root: ec.root}
|
return &evalCtx{parent: ec, root: ec.root}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *evalCtx) addCmd(name string, inv invokable) {
|
func (ec *evalCtx) addCmd(name string, inv invokable) {
|
||||||
if ec.commands == nil {
|
if ec.root.commands == nil {
|
||||||
ec.commands = make(map[string]invokable)
|
ec.root.commands = make(map[string]invokable)
|
||||||
}
|
}
|
||||||
|
|
||||||
ec.commands[name] = inv
|
ec.root.commands[name] = inv
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *evalCtx) addMacro(name string, inv macroable) {
|
func (ec *evalCtx) addMacro(name string, inv macroable) {
|
||||||
if ec.macros == nil {
|
if ec.root.macros == nil {
|
||||||
ec.macros = make(map[string]macroable)
|
ec.root.macros = make(map[string]macroable)
|
||||||
}
|
}
|
||||||
|
|
||||||
ec.macros[name] = inv
|
ec.root.macros[name] = inv
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *evalCtx) setVar(name string, val object) {
|
func (ec *evalCtx) setVar(name string, val object) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cmdlang
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -76,6 +77,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentStream strea
|
||||||
switch {
|
switch {
|
||||||
case ast.Name.Ident != nil:
|
case ast.Name.Ident != nil:
|
||||||
name := *ast.Name.Ident
|
name := *ast.Name.Ident
|
||||||
|
log.Printf("--> invoking: %v", name)
|
||||||
|
|
||||||
// Regular command
|
// Regular command
|
||||||
if cmd := ec.lookupInvokable(name); cmd != nil {
|
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 {
|
} else if macro := ec.lookupMacro(name); macro != nil {
|
||||||
return e.evalMacro(ctx, ec, currentStream, ast, macro)
|
return e.evalMacro(ctx, ec, currentStream, ast, macro)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("unknown command")
|
return nil, errors.New("unknown command: " + name)
|
||||||
}
|
}
|
||||||
case len(ast.Args) > 0:
|
case len(ast.Args) > 0:
|
||||||
nameElem, err := e.evalArg(ctx, ec, ast.Name)
|
nameElem, err := e.evalArg(ctx, ec, ast.Name)
|
||||||
|
|
|
@ -34,7 +34,9 @@ func New(opts ...InstOption) *Inst {
|
||||||
rootEC.addCmd("call", invokableFunc(callBuiltin))
|
rootEC.addCmd("call", invokableFunc(callBuiltin))
|
||||||
|
|
||||||
rootEC.addCmd("map", invokableStreamFunc(mapBuiltin))
|
rootEC.addCmd("map", invokableStreamFunc(mapBuiltin))
|
||||||
|
rootEC.addCmd("head", invokableStreamFunc(firstBuiltin))
|
||||||
|
|
||||||
|
rootEC.addCmd("eq", invokableFunc(eqBuiltin))
|
||||||
rootEC.addCmd("cat", invokableFunc(concatBuiltin))
|
rootEC.addCmd("cat", invokableFunc(concatBuiltin))
|
||||||
|
|
||||||
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
||||||
|
@ -86,6 +88,7 @@ func (inst *Inst) eval(ctx context.Context, expr string) (object, error) {
|
||||||
|
|
||||||
eval := evaluator{inst: inst}
|
eval := evaluator{inst: inst}
|
||||||
|
|
||||||
|
// TODO: this should be a separate forkAndIsolate() session
|
||||||
return eval.evalScript(ctx, inst.rootEC, ast)
|
return eval.evalScript(ctx, inst.rootEC, ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ func TestInst_Eval(t *testing.T) {
|
||||||
|
|
||||||
// Lists
|
// Lists
|
||||||
{desc: "list 1", expr: `firstarg ["1" "2" "3"]`, want: []any{"1", "2", "3"}},
|
{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{}},
|
{desc: "list 3", expr: `firstarg []`, want: []any{}},
|
||||||
|
|
||||||
// Maps
|
// Maps
|
||||||
|
@ -52,7 +52,7 @@ func TestInst_Eval(t *testing.T) {
|
||||||
set one "one" ; set n1 "1"
|
set one "one" ; set n1 "1"
|
||||||
firstarg [
|
firstarg [
|
||||||
$one:$n1
|
$one:$n1
|
||||||
(firstarg "two" | toUpper):(firstarg "2" | toUpper)
|
(firstarg "two" | toUpper | head):(firstarg "2" | toUpper | head)
|
||||||
three:"3"
|
three:"3"
|
||||||
]`, want: map[string]any{"one": "1", "TWO": "2", "three": "3"}},
|
]`, want: map[string]any{"one": "1", "TWO": "2", "three": "3"}},
|
||||||
{desc: "map 4", expr: `firstarg [:]`, want: map[string]any{}},
|
{desc: "map 4", expr: `firstarg [:]`, want: map[string]any{}},
|
||||||
|
|
|
@ -46,6 +46,19 @@ func (s strObject) Truthy() bool {
|
||||||
return string(s) != ""
|
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) {
|
func toGoValue(obj object) (interface{}, bool) {
|
||||||
switch v := obj.(type) {
|
switch v := obj.(type) {
|
||||||
case nil:
|
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