dramatic simplification of command evaluation
- Removed the notion of streams - Modified pipes such that any object can be passed through pipe. Now, the first argument will be used if the command is invoked via a pipe
This commit is contained in:
parent
4c532e5005
commit
63762e633c
|
@ -1,12 +1,9 @@
|
||||||
package cmdlang
|
package cmdlang
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,18 +45,15 @@ func setBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return newVal, nil
|
return newVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toUpperBuiltin(ctx context.Context, inStream stream, args invocationArgs) (object, error) {
|
func toUpperBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
// Handle args
|
if err := args.expectArgn(1); err != nil {
|
||||||
return mapFilterStream{
|
return nil, err
|
||||||
in: inStream,
|
}
|
||||||
mapFn: func(x object) (object, bool, error) {
|
sarg, err := args.stringArg(0)
|
||||||
s, ok := x.(strObject)
|
if err != nil {
|
||||||
if !ok {
|
return nil, err
|
||||||
return nil, false, nil
|
}
|
||||||
}
|
return strObject(strings.ToUpper(sarg)), nil
|
||||||
return strObject(strings.ToUpper(string(s))), true, nil
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
@ -92,18 +86,19 @@ func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return strObject(sb.String()), nil
|
return strObject(sb.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func catBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
//
|
||||||
if err := args.expectArgn(1); err != nil {
|
//func catBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return nil, err
|
// if err := args.expectArgn(1); err != nil {
|
||||||
}
|
// return nil, err
|
||||||
|
// }
|
||||||
filename, err := args.stringArg(0)
|
//
|
||||||
if err != nil {
|
// filename, err := args.stringArg(0)
|
||||||
return nil, err
|
// if err != nil {
|
||||||
}
|
// return nil, err
|
||||||
|
// }
|
||||||
return &fileLinesStream{filename: filename}, nil
|
//
|
||||||
}
|
// return &fileLinesStream{filename: filename}, nil
|
||||||
|
//}
|
||||||
|
|
||||||
func callBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func callBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
if err := args.expectArgn(1); err != nil {
|
if err := args.expectArgn(1); err != nil {
|
||||||
|
@ -118,47 +113,49 @@ func callBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return inv.invoke(ctx, args.shift(1))
|
return inv.invoke(ctx, args.shift(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapBuiltin(ctx context.Context, inStream stream, args invocationArgs) (object, error) {
|
func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
args, strm, err := args.streamableSource(inStream)
|
if err := args.expectArgn(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inv, err := args.invokableArg(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch t := args.args[0].(type) {
|
||||||
|
case listable:
|
||||||
|
l := t.Len()
|
||||||
|
newList := listObject{}
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
v := t.Index(i)
|
||||||
|
m, err := inv.invoke(ctx, args.fork([]object{v}))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newList = append(newList, m)
|
||||||
|
}
|
||||||
|
return newList, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("expected listable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
if err := args.expectArgn(1); err != nil {
|
if err := args.expectArgn(1); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
inv, ok := args.args[0].(invokable)
|
switch t := args.args[0].(type) {
|
||||||
if !ok {
|
case listable:
|
||||||
return nil, errors.New("expected invokable")
|
if t.Len() == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return t.Index(0), nil
|
||||||
}
|
}
|
||||||
|
return nil, errors.New("expected listable")
|
||||||
return mapFilterStream{
|
|
||||||
in: strm,
|
|
||||||
mapFn: func(x object) (object, bool, error) {
|
|
||||||
y, err := inv.invoke(ctx, args.fork(nil, []object{x}))
|
|
||||||
return y, true, err
|
|
||||||
},
|
|
||||||
}, 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
|
||||||
|
@ -200,6 +197,7 @@ func (f *fileLinesStream) close() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func ifBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
func ifBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
||||||
if args.nargs() < 2 {
|
if args.nargs() < 2 {
|
||||||
|
@ -268,7 +266,6 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: streams
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return last, nil
|
return last, nil
|
||||||
|
|
|
@ -36,13 +36,6 @@ func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStateme
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rest := range n.Rest {
|
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)
|
out, err := e.evalPipeline(ctx, ec, rest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -63,7 +56,7 @@ func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline
|
||||||
|
|
||||||
// Command is a pipeline, so build it out
|
// Command is a pipeline, so build it out
|
||||||
for _, rest := range n.Rest {
|
for _, rest := range n.Rest {
|
||||||
out, err := e.evalCmd(ctx, ec, asStream(res), rest)
|
out, err := e.evalCmd(ctx, ec, res, rest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -72,16 +65,16 @@ func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentStream stream, ast *astCmd) (object, error) {
|
func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object, ast *astCmd) (object, error) {
|
||||||
switch {
|
switch {
|
||||||
case ast.Name.Ident != nil:
|
case ast.Name.Ident != nil:
|
||||||
name := *ast.Name.Ident
|
name := *ast.Name.Ident
|
||||||
|
|
||||||
// Regular command
|
// Regular command
|
||||||
if cmd := ec.lookupInvokable(name); cmd != nil {
|
if cmd := ec.lookupInvokable(name); cmd != nil {
|
||||||
return e.evalInvokable(ctx, ec, currentStream, ast, cmd)
|
return e.evalInvokable(ctx, ec, currentPipe, ast, cmd)
|
||||||
} 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, currentPipe, ast, macro)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("unknown command: " + name)
|
return nil, errors.New("unknown command: " + name)
|
||||||
}
|
}
|
||||||
|
@ -96,7 +89,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentStream strea
|
||||||
return nil, errors.New("command is not invokable")
|
return nil, errors.New("command is not invokable")
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.evalInvokable(ctx, ec, currentStream, ast, inv)
|
return e.evalInvokable(ctx, ec, currentPipe, ast, inv)
|
||||||
}
|
}
|
||||||
|
|
||||||
nameElem, err := e.evalArg(ctx, ec, ast.Name)
|
nameElem, err := e.evalArg(ctx, ec, ast.Name)
|
||||||
|
@ -106,7 +99,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentStream strea
|
||||||
return nameElem, nil
|
return nameElem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentStream stream, ast *astCmd, cmd invokable) (object, error) {
|
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe object, ast *astCmd, cmd invokable) (object, error) {
|
||||||
var (
|
var (
|
||||||
pargs listObject
|
pargs listObject
|
||||||
kwargs map[string]*listObject
|
kwargs map[string]*listObject
|
||||||
|
@ -114,6 +107,9 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentStream
|
||||||
)
|
)
|
||||||
|
|
||||||
argsPtr = &pargs
|
argsPtr = &pargs
|
||||||
|
if currentPipe != nil {
|
||||||
|
argsPtr.Append(currentPipe)
|
||||||
|
}
|
||||||
for _, arg := range ast.Args {
|
for _, arg := range ast.Args {
|
||||||
if ident := arg.Ident; ident != nil && (*ident)[0] == '-' {
|
if ident := arg.Ident; ident != nil && (*ident)[0] == '-' {
|
||||||
// Arg switch
|
// Arg switch
|
||||||
|
@ -132,27 +128,16 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
invArgs := invocationArgs{ec: ec, inst: e.inst, args: pargs, kwargs: kwargs, currentStream: currentStream}
|
invArgs := invocationArgs{eval: e, ec: ec, inst: e.inst, args: pargs, kwargs: kwargs}
|
||||||
|
|
||||||
if currentStream != nil {
|
|
||||||
if si, ok := cmd.(streamInvokable); ok {
|
|
||||||
return si.invokeWithStream(ctx, currentStream, invArgs)
|
|
||||||
} else {
|
|
||||||
if err := currentStream.close(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
func (e evaluator) evalMacro(ctx context.Context, ec *evalCtx, pipeArg object, ast *astCmd, cmd macroable) (object, error) {
|
||||||
return cmd.invokeMacro(ctx, macroArgs{
|
return cmd.invokeMacro(ctx, macroArgs{
|
||||||
eval: e,
|
eval: e,
|
||||||
ec: ec,
|
ec: ec,
|
||||||
currentStream: currentStream,
|
pipeArg: pipeArg,
|
||||||
ast: ast,
|
ast: ast,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,18 +226,5 @@ func (e evaluator) evalSub(ctx context.Context, ec *evalCtx, n *astPipeline) (ob
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := pipelineRes.(type) {
|
|
||||||
case stream:
|
|
||||||
list := listObject{}
|
|
||||||
if err := forEach(v, func(o object, _ int) error {
|
|
||||||
list = append(list, o)
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
return pipelineRes, nil
|
return pipelineRes, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,12 @@ func New(opts ...InstOption) *Inst {
|
||||||
|
|
||||||
rootEC.addCmd("echo", invokableFunc(echoBuiltin))
|
rootEC.addCmd("echo", invokableFunc(echoBuiltin))
|
||||||
rootEC.addCmd("set", invokableFunc(setBuiltin))
|
rootEC.addCmd("set", invokableFunc(setBuiltin))
|
||||||
rootEC.addCmd("toUpper", invokableStreamFunc(toUpperBuiltin))
|
rootEC.addCmd("toUpper", invokableFunc(toUpperBuiltin))
|
||||||
//rootEC.addCmd("cat", invokableFunc(catBuiltin))
|
//rootEC.addCmd("cat", invokableFunc(catBuiltin))
|
||||||
rootEC.addCmd("call", invokableFunc(callBuiltin))
|
rootEC.addCmd("call", invokableFunc(callBuiltin))
|
||||||
|
|
||||||
rootEC.addCmd("map", invokableStreamFunc(mapBuiltin))
|
rootEC.addCmd("map", invokableFunc(mapBuiltin))
|
||||||
rootEC.addCmd("head", invokableStreamFunc(firstBuiltin))
|
rootEC.addCmd("head", invokableFunc(firstBuiltin))
|
||||||
|
|
||||||
rootEC.addCmd("eq", invokableFunc(eqBuiltin))
|
rootEC.addCmd("eq", invokableFunc(eqBuiltin))
|
||||||
rootEC.addCmd("cat", invokableFunc(concatBuiltin))
|
rootEC.addCmd("cat", invokableFunc(concatBuiltin))
|
||||||
|
@ -107,8 +107,6 @@ func (inst *Inst) display(ctx context.Context, res object) (err error) {
|
||||||
if _, err = fmt.Fprintln(inst.out, "(nil)"); err != nil {
|
if _, err = fmt.Fprintln(inst.out, "(nil)"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case stream:
|
|
||||||
return forEach(v, func(o object, _ int) error { return inst.display(ctx, o) })
|
|
||||||
default:
|
default:
|
||||||
if _, err = fmt.Fprintln(inst.out, v.String()); err != nil {
|
if _, err = fmt.Fprintln(inst.out, v.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -29,20 +29,22 @@ func TestInst_Eval(t *testing.T) {
|
||||||
{desc: "var 3", expr: `firstarg (sjoin $bee " " $bee " " $bee)`, want: "buzz buzz buzz"},
|
{desc: "var 3", expr: `firstarg (sjoin $bee " " $bee " " $bee)`, want: "buzz buzz buzz"},
|
||||||
|
|
||||||
// Pipeline
|
// Pipeline
|
||||||
{desc: "pipe 1", expr: `pipe "aye" "bee" "see" | joinpipe`, want: "aye,bee,see"},
|
{desc: "pipe 1", expr: `list "aye" "bee" "see" | joinpipe`, want: "aye,bee,see"},
|
||||||
{desc: "pipe 2", expr: `pipe "aye" "bee" "see" | toUpper | joinpipe`, want: "AYE,BEE,SEE"},
|
{desc: "pipe 2", expr: `list "aye" "bee" "see" | map { |x| toUpper $x } | joinpipe`, want: "AYE,BEE,SEE"},
|
||||||
{desc: "pipe 3", expr: `firstarg "normal" | toUpper | joinpipe`, want: "NORMAL"},
|
{desc: "pipe 3", expr: `firstarg ["normal"] | map { |x| toUpper $x } | joinpipe`, want: "NORMAL"},
|
||||||
|
{desc: "pipe literal 1", expr: `"hello" | firstarg`, want: "hello"},
|
||||||
|
{desc: "pipe literal 2", expr: `["hello" "world"] | joinpipe`, want: "hello,world"},
|
||||||
|
|
||||||
{desc: "ignored pipe", expr: `pipe "aye" "bee" "see" | firstarg "ignore me"`, want: "ignore me"}, // TODO: check for leaks
|
{desc: "ignored pipe", expr: `(list "aye" | firstarg "ignore me") | joinpipe`, want: "aye"},
|
||||||
|
|
||||||
// Multi-statements
|
// Multi-statements
|
||||||
{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: `list "hello" | toUpper ; firstarg "world"`, want: "world"},
|
||||||
{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"},
|
||||||
|
|
||||||
// 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 | head) "three"]`, want: []any{"one", "TWO", "three"}},
|
{desc: "list 2", expr: `set one "one" ; firstarg [$one (list "two" | map { |x| toUpper $x } | 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 +54,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 | head):(firstarg "2" | toUpper | head)
|
(list "two" | map { |x| toUpper $x } | head):(list "2" | map { |x| toUpper $x } | 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{}},
|
||||||
|
|
115
cmdlang/objs.go
115
cmdlang/objs.go
|
@ -18,6 +18,11 @@ type listable interface {
|
||||||
Index(i int) object
|
Index(i int) object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type hashable interface {
|
||||||
|
Len() int
|
||||||
|
Each(func(k string, v object) error) error
|
||||||
|
}
|
||||||
|
|
||||||
type listObject []object
|
type listObject []object
|
||||||
|
|
||||||
func (lo *listObject) Append(o object) {
|
func (lo *listObject) Append(o object) {
|
||||||
|
@ -50,6 +55,19 @@ func (s hashObject) Truthy() bool {
|
||||||
return len(s) > 0
|
return len(s) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s hashObject) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s hashObject) Each(fn func(k string, v object) error) error {
|
||||||
|
for k, v := range s {
|
||||||
|
if err := fn(k, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type strObject string
|
type strObject string
|
||||||
|
|
||||||
func (s strObject) String() string {
|
func (s strObject) String() string {
|
||||||
|
@ -125,11 +143,11 @@ func fromGoValue(v any) (object, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type macroArgs struct {
|
type macroArgs struct {
|
||||||
eval evaluator
|
eval evaluator
|
||||||
ec *evalCtx
|
ec *evalCtx
|
||||||
currentStream stream
|
pipeArg object
|
||||||
ast *astCmd
|
ast *astCmd
|
||||||
argShift int
|
argShift int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ma macroArgs) nargs() int {
|
func (ma macroArgs) nargs() int {
|
||||||
|
@ -199,30 +217,11 @@ func (ma macroArgs) evalBlock(ctx context.Context, n int, args []object, pushSco
|
||||||
}
|
}
|
||||||
|
|
||||||
type invocationArgs struct {
|
type invocationArgs struct {
|
||||||
inst *Inst
|
eval evaluator
|
||||||
ec *evalCtx
|
inst *Inst
|
||||||
currentStream stream
|
ec *evalCtx
|
||||||
args []object
|
args []object
|
||||||
kwargs map[string]*listObject
|
kwargs map[string]*listObject
|
||||||
}
|
|
||||||
|
|
||||||
// streamableSource takes a stream. If the stream is set, the inStream and invocation arguments are consumed as is.
|
|
||||||
// If not, then the first argument is consumed and returned as a stream.
|
|
||||||
func (ia invocationArgs) streamableSource(inStream stream) (invocationArgs, stream, error) {
|
|
||||||
if inStream != nil {
|
|
||||||
return ia, inStream, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ia.args) < 1 {
|
|
||||||
return ia, nil, errors.New("expected at least 1 argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := ia.args[0].(type) {
|
|
||||||
case listObject:
|
|
||||||
return ia.shift(1), &listIterStream{list: v}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ia, nil, errors.New("expected arg 0 to be streamable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia invocationArgs) expectArgn(x int) error {
|
func (ia invocationArgs) expectArgn(x int) error {
|
||||||
|
@ -243,23 +242,35 @@ func (ia invocationArgs) stringArg(i int) (string, error) {
|
||||||
return s.String(), nil
|
return s.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia invocationArgs) fork(currentStr stream, args []object) invocationArgs {
|
func (ia invocationArgs) invokableArg(i int) (invokable, error) {
|
||||||
|
if len(ia.args) < i {
|
||||||
|
return nil, errors.New("expected at least " + strconv.Itoa(i) + " args")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := ia.args[i].(type) {
|
||||||
|
case invokable:
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("expected an invokable arg")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ia invocationArgs) fork(args []object) invocationArgs {
|
||||||
return invocationArgs{
|
return invocationArgs{
|
||||||
inst: ia.inst,
|
eval: ia.eval,
|
||||||
ec: ia.ec,
|
inst: ia.inst,
|
||||||
currentStream: currentStr,
|
ec: ia.ec,
|
||||||
args: args,
|
args: args,
|
||||||
kwargs: make(map[string]*listObject),
|
kwargs: make(map[string]*listObject),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia invocationArgs) shift(i int) invocationArgs {
|
func (ia invocationArgs) shift(i int) invocationArgs {
|
||||||
return invocationArgs{
|
return invocationArgs{
|
||||||
inst: ia.inst,
|
eval: ia.eval,
|
||||||
ec: ia.ec,
|
inst: ia.inst,
|
||||||
currentStream: ia.currentStream,
|
ec: ia.ec,
|
||||||
args: ia.args[i:],
|
args: ia.args[i:],
|
||||||
kwargs: ia.kwargs,
|
kwargs: ia.kwargs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,9 +283,8 @@ type macroable interface {
|
||||||
invokeMacro(ctx context.Context, args macroArgs) (object, error)
|
invokeMacro(ctx context.Context, args macroArgs) (object, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type streamInvokable interface {
|
type pipeInvokable interface {
|
||||||
invokable
|
invokable
|
||||||
invokeWithStream(context.Context, stream, invocationArgs) (object, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type invokableFunc func(ctx context.Context, args invocationArgs) (object, error)
|
type invokableFunc func(ctx context.Context, args invocationArgs) (object, error)
|
||||||
|
@ -283,16 +293,6 @@ func (i invokableFunc) invoke(ctx context.Context, args invocationArgs) (object,
|
||||||
return i(ctx, args)
|
return i(ctx, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
type invokableStreamFunc func(ctx context.Context, inStream stream, args invocationArgs) (object, error)
|
|
||||||
|
|
||||||
func (i invokableStreamFunc) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
|
||||||
return i(ctx, nil, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i invokableStreamFunc) invokeWithStream(ctx context.Context, inStream stream, args invocationArgs) (object, error) {
|
|
||||||
return i(ctx, inStream, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
type blockObject struct {
|
type blockObject struct {
|
||||||
block *astBlock
|
block *astBlock
|
||||||
}
|
}
|
||||||
|
@ -305,6 +305,17 @@ func (bo blockObject) Truthy() bool {
|
||||||
return len(bo.block.Statements) > 0
|
return len(bo.block.Statements) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
ec := args.ec.fork()
|
||||||
|
for i, n := range bo.block.Names {
|
||||||
|
if i < len(args.args) {
|
||||||
|
ec.setVar(n, args.args[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args.eval.evalBlock(ctx, ec, bo.block)
|
||||||
|
}
|
||||||
|
|
||||||
type macroFunc func(ctx context.Context, args macroArgs) (object, error)
|
type macroFunc func(ctx context.Context, args macroArgs) (object, error)
|
||||||
|
|
||||||
func (i macroFunc) invokeMacro(ctx context.Context, args macroArgs) (object, error) {
|
func (i macroFunc) invokeMacro(ctx context.Context, args macroArgs) (object, error) {
|
||||||
|
|
|
@ -1,152 +0,0 @@
|
||||||
package cmdlang
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// stream is an object which returns a collection of objects from a source.
|
|
||||||
// These are used to create pipelines
|
|
||||||
//
|
|
||||||
// The stream implementation can expect close to be called if at least one next() call is made. Otherwise
|
|
||||||
// closableStream cannot assume that close will be called (the pipe may be left unconsumed, for example).
|
|
||||||
//
|
|
||||||
// It is the job of the final iterator to call close. Any steam that consumes from another stream must
|
|
||||||
// implement this, and call close on the parent stream.
|
|
||||||
type stream interface {
|
|
||||||
object
|
|
||||||
|
|
||||||
// next pulls the next object from the stream. If an object is available, the result is the
|
|
||||||
// object and a nil error. If no more objects are available, error returns io.EOF.
|
|
||||||
// Otherwise, an error is returned.
|
|
||||||
next() (object, error)
|
|
||||||
|
|
||||||
close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// forEach will iterate over all the items of a stream. The iterating function can return an error, which will
|
|
||||||
// be returned as is. A stream that has consumed every item will return nil. The stream will automatically be closed.
|
|
||||||
func forEach(s stream, f func(object, int) error) (err error) {
|
|
||||||
defer s.close()
|
|
||||||
|
|
||||||
var sv object
|
|
||||||
i := 0
|
|
||||||
for sv, err = s.next(); err == nil; sv, err = s.next() {
|
|
||||||
if err := f(sv, i); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
if !errors.Is(err, io.EOF) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// asStream converts an object to a stream. If t is already a stream, it's returned as is.
|
|
||||||
// Otherwise, a singleton stream is returned.
|
|
||||||
func asStream(v object) stream {
|
|
||||||
switch s := v.(type) {
|
|
||||||
case stream:
|
|
||||||
return s
|
|
||||||
case listObject:
|
|
||||||
return &listIterStream{list: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &singletonStream{t: v}
|
|
||||||
}
|
|
||||||
|
|
||||||
type emptyStream struct{}
|
|
||||||
|
|
||||||
func (s *emptyStream) String() string {
|
|
||||||
return "(nil)"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s emptyStream) next() (object, error) {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s emptyStream) close() error { return nil }
|
|
||||||
|
|
||||||
type singletonStream struct {
|
|
||||||
t object
|
|
||||||
consumed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singletonStream) String() string {
|
|
||||||
return s.t.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singletonStream) Truthy() bool {
|
|
||||||
return !s.consumed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singletonStream) next() (object, error) {
|
|
||||||
if s.consumed {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
s.consumed = true
|
|
||||||
return s.t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singletonStream) close() error { return nil }
|
|
||||||
|
|
||||||
type listIterStream struct {
|
|
||||||
list []object
|
|
||||||
cusr int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *listIterStream) String() string {
|
|
||||||
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) {
|
|
||||||
if s.cusr >= len(s.list) {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
o = s.list[s.cusr]
|
|
||||||
s.cusr += 1
|
|
||||||
|
|
||||||
return o, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *listIterStream) close() error { return nil }
|
|
||||||
|
|
||||||
type mapFilterStream struct {
|
|
||||||
in stream
|
|
||||||
mapFn func(x object) (object, bool, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms mapFilterStream) String() string {
|
|
||||||
return fmt.Sprintf("mapFilterStream{in: %v}", ms.in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms mapFilterStream) Truthy() bool {
|
|
||||||
return true // ???
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms mapFilterStream) next() (object, error) {
|
|
||||||
for {
|
|
||||||
u, err := ms.in.next()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t, ok, err := ms.mapFn(u)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if ok {
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms mapFilterStream) close() error {
|
|
||||||
return ms.in.close()
|
|
||||||
}
|
|
|
@ -31,22 +31,24 @@ func WithTestBuiltin() InstOption {
|
||||||
return strObject(line.String()), nil
|
return strObject(line.String()), nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
i.rootEC.addCmd("pipe", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) {
|
i.rootEC.addCmd("list", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return &listIterStream{
|
return listObject(args.args), nil
|
||||||
list: args.args,
|
|
||||||
}, nil
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
i.rootEC.addCmd("joinpipe", invokableStreamFunc(func(ctx context.Context, inStream stream, args invocationArgs) (object, error) {
|
i.rootEC.addCmd("joinpipe", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
sb := strings.Builder{}
|
sb := strings.Builder{}
|
||||||
if err := forEach(inStream, func(o object, i int) error {
|
|
||||||
if i > 0 {
|
lst, ok := args.args[0].(listable)
|
||||||
|
if !ok {
|
||||||
|
return strObject(""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l := lst.Len()
|
||||||
|
for x := 0; x < l; x++ {
|
||||||
|
if x > 0 {
|
||||||
sb.WriteString(",")
|
sb.WriteString(",")
|
||||||
}
|
}
|
||||||
sb.WriteString(o.String())
|
sb.WriteString(lst.Index(x).String())
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return strObject(sb.String()), nil
|
return strObject(sb.String()), nil
|
||||||
}))
|
}))
|
||||||
|
@ -292,17 +294,20 @@ func TestBuiltins_Map(t *testing.T) {
|
||||||
proc makeUpper { |x| $x | toUpper }
|
proc makeUpper { |x| $x | toUpper }
|
||||||
|
|
||||||
map ["a" "b" "c"] (proc { |x| makeUpper $x })
|
map ["a" "b" "c"] (proc { |x| makeUpper $x })
|
||||||
`, want: "A\nB\nC\n"},
|
`, want: "[A B C]\n"},
|
||||||
{desc: "map list 2", expr: `
|
{desc: "map list 2", expr: `
|
||||||
set makeUpper (proc { |x| $x | toUpper })
|
set makeUpper (proc { |x| $x | toUpper })
|
||||||
|
|
||||||
map ["a" "b" "c"] $makeUpper
|
map ["a" "b" "c"] $makeUpper
|
||||||
`, want: "A\nB\nC\n"},
|
`, want: "[A B C]\n"},
|
||||||
{desc: "map list with stream", expr: `
|
{desc: "map list with pipe", expr: `
|
||||||
set makeUpper (proc { |x| $x | toUpper })
|
set makeUpper (proc { |x| $x | toUpper })
|
||||||
|
|
||||||
["a" "b" "c"] | map $makeUpper
|
["a" "b" "c"] | map $makeUpper
|
||||||
`, want: "A\nB\nC\n"},
|
`, want: "[A B C]\n"},
|
||||||
|
{desc: "map list with block", expr: `
|
||||||
|
map ["a" "b" "c"] { |x| toUpper $x }
|
||||||
|
`, want: "[A B C]\n"},
|
||||||
//{desc: "map list with stream", expr: `
|
//{desc: "map list with stream", expr: `
|
||||||
// set makeUpper (proc { |x| $x | toUpper })
|
// set makeUpper (proc { |x| $x | toUpper })
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in a new issue