From 4b91bff64170223f7f9af0f591aadbdec58b4a04 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Thu, 18 Apr 2024 20:53:25 +1000 Subject: [PATCH] Started working on map functions --- cmdlang/builtins.go | 40 +++++++++++++++++++++++++++++++++++++--- cmdlang/eval.go | 10 +++------- cmdlang/inst.go | 6 +++++- cmdlang/objs.go | 29 +++++++++++++++++++++++++++++ cmdlang/streams.go | 8 +++++--- 5 files changed, 79 insertions(+), 14 deletions(-) diff --git a/cmdlang/builtins.go b/cmdlang/builtins.go index 69d6207..b442b8a 100644 --- a/cmdlang/builtins.go +++ b/cmdlang/builtins.go @@ -52,16 +52,26 @@ func toUpperBuiltin(ctx context.Context, inStream stream, args invocationArgs) ( // Handle args return mapFilterStream{ in: inStream, - mapFn: func(x object) (object, bool) { + mapFn: func(x object) (object, bool, error) { s, ok := x.(strObject) if !ok { - return nil, false + return nil, false, nil } - return strObject(strings.ToUpper(string(s))), true + return strObject(strings.ToUpper(string(s))), true, nil }, }, nil } +func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) { + var sb strings.Builder + + for _, a := range args.args { + sb.WriteString(a.String()) + } + + return strObject(sb.String()), nil +} + func catBuiltin(ctx context.Context, args invocationArgs) (object, error) { if err := args.expectArgn(1); err != nil { return nil, err @@ -88,6 +98,30 @@ func callBuiltin(ctx context.Context, args invocationArgs) (object, error) { return inv.invoke(ctx, args.shift(1)) } +func mapBuiltin(ctx context.Context, inStream stream, args invocationArgs) (object, error) { + args, strm, err := args.streamableSource(inStream) + if err != nil { + return nil, err + } + + if err := args.expectArgn(1); err != nil { + return nil, err + } + + inv, ok := args.args[0].(invokable) + if !ok { + return nil, errors.New("expected invokable") + } + + 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 +} + type fileLinesStream struct { filename string f *os.File diff --git a/cmdlang/eval.go b/cmdlang/eval.go index 828b526..89437f3 100644 --- a/cmdlang/eval.go +++ b/cmdlang/eval.go @@ -3,9 +3,7 @@ package cmdlang import ( "context" "errors" - "fmt" "strconv" - "strings" ) type evaluator struct { @@ -218,17 +216,15 @@ func (e evaluator) evalSub(ctx context.Context, ec *evalCtx, n *astPipeline) (ob switch v := pipelineRes.(type) { case stream: - // TODO: use proper lists here, not a string join - sb := strings.Builder{} + list := listObject{} if err := forEach(v, func(o object, _ int) error { - // TODO: use o.String() - sb.WriteString(fmt.Sprint(o)) + list = append(list, o) return nil }); err != nil { return nil, err } - return strObject(sb.String()), nil + return list, nil } return pipelineRes, nil } diff --git a/cmdlang/inst.go b/cmdlang/inst.go index e333c1d..18c2f86 100644 --- a/cmdlang/inst.go +++ b/cmdlang/inst.go @@ -30,9 +30,13 @@ func New(opts ...InstOption) *Inst { rootEC.addCmd("echo", invokableFunc(echoBuiltin)) rootEC.addCmd("set", invokableFunc(setBuiltin)) rootEC.addCmd("toUpper", invokableStreamFunc(toUpperBuiltin)) - rootEC.addCmd("cat", invokableFunc(catBuiltin)) + //rootEC.addCmd("cat", invokableFunc(catBuiltin)) rootEC.addCmd("call", invokableFunc(callBuiltin)) + rootEC.addCmd("map", invokableStreamFunc(mapBuiltin)) + + rootEC.addCmd("cat", invokableFunc(concatBuiltin)) + rootEC.addMacro("if", macroFunc(ifBuiltin)) rootEC.addMacro("foreach", macroFunc(foreachBuiltin)) rootEC.addMacro("proc", macroFunc(procBuiltin)) diff --git a/cmdlang/objs.go b/cmdlang/objs.go index 77edbf6..3de6234 100644 --- a/cmdlang/objs.go +++ b/cmdlang/objs.go @@ -172,6 +172,25 @@ type invocationArgs struct { 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 { if len(ia.args) < x { return errors.New("expected at least " + strconv.Itoa(x) + " args") @@ -190,6 +209,16 @@ func (ia invocationArgs) stringArg(i int) (string, error) { return s.String(), nil } +func (ia invocationArgs) fork(currentStr stream, args []object) invocationArgs { + return invocationArgs{ + inst: ia.inst, + ec: ia.ec, + currentStream: currentStr, + args: args, + kwargs: make(map[string]*listObject), + } +} + func (ia invocationArgs) shift(i int) invocationArgs { return invocationArgs{ inst: ia.inst, diff --git a/cmdlang/streams.go b/cmdlang/streams.go index be460a3..69cfcbe 100644 --- a/cmdlang/streams.go +++ b/cmdlang/streams.go @@ -116,7 +116,7 @@ func (s *listIterStream) close() error { return nil } type mapFilterStream struct { in stream - mapFn func(x object) (object, bool) + mapFn func(x object) (object, bool, error) } func (ms mapFilterStream) String() string { @@ -134,8 +134,10 @@ func (ms mapFilterStream) next() (object, error) { return nil, err } - t, ok := ms.mapFn(u) - if ok { + t, ok, err := ms.mapFn(u) + if err != nil { + return nil, err + } else if ok { return t, nil } }