diff --git a/cmdlang/ast.go b/cmdlang/ast.go index 5fe41be..a305692 100644 --- a/cmdlang/ast.go +++ b/cmdlang/ast.go @@ -12,6 +12,7 @@ type astLiteral struct { type astCmdArg struct { Literal *astLiteral `parser:"@@"` + Var *string `parser:"| '$' @Ident"` Sub *astPipeline `parser:"| '(' @@ ')'"` } diff --git a/cmdlang/builtins.go b/cmdlang/builtins.go index f98dc4c..404622a 100644 --- a/cmdlang/builtins.go +++ b/cmdlang/builtins.go @@ -25,6 +25,23 @@ func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) { return asStream(line.String()), nil } +func setBuiltin(ctx context.Context, args invocationArgs) (object, error) { + if err := args.expectArgn(2); err != nil { + return nil, err + } + + name, err := args.stringArg(0) + if err != nil { + return nil, err + } + + newVal := args.args[1] + + // TODO: if the value is a stream, consume the stream and save it as a list + args.ec.setVar(name, newVal) + return newVal, nil +} + func toUpperBuiltin(ctx context.Context, inStream stream, args invocationArgs) (object, error) { // Handle args return mapFilterStream{ diff --git a/cmdlang/env.go b/cmdlang/env.go index 3e78f9a..9861e0a 100644 --- a/cmdlang/env.go +++ b/cmdlang/env.go @@ -8,6 +8,7 @@ type evalCtx struct { parent *evalCtx currentStream stream commands map[string]invokable + vars map[string]object } func (ec *evalCtx) withCurrentStream(s stream) *evalCtx { @@ -25,6 +26,27 @@ func (ec *evalCtx) addCmd(name string, inv invokable) { ec.commands[name] = inv } +func (ec *evalCtx) setVar(name string, val object) { + if ec.vars == nil { + ec.vars = make(map[string]object) + } + ec.vars[name] = val +} + +func (ec *evalCtx) getVar(name string) (object, bool) { + if ec.vars == nil { + return nil, false + } + + if v, ok := ec.vars[name]; ok { + return v, true + } else if ec.parent != nil { + return ec.parent.getVar(name) + } + + return nil, false +} + func (ec *evalCtx) lookupCmd(name string) (invokable, error) { for e := ec; e != nil; e = e.parent { if e.commands == nil { diff --git a/cmdlang/eval.go b/cmdlang/eval.go index b00026e..9658508 100644 --- a/cmdlang/eval.go +++ b/cmdlang/eval.go @@ -45,9 +45,11 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, ast *astCmd) (objec return nil, err } + invArgs := invocationArgs{ec: ec, args: args} + if ec.currentStream != nil { if si, ok := cmd.(streamInvokable); ok { - return si.invokeWithStream(ctx, ec.currentStream, invocationArgs{args: args}) + return si.invokeWithStream(ctx, ec.currentStream, invArgs) } else { if err := ec.currentStream.close(); err != nil { return nil, err @@ -55,13 +57,19 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, ast *astCmd) (objec } } - return cmd.invoke(ctx, invocationArgs{args: args}) + return cmd.invoke(ctx, invArgs) } func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (object, error) { switch { case n.Literal != nil: return e.evalLiteral(ctx, ec, n.Literal) + case n.Var != nil: + v, ok := ec.getVar(*n.Var) + if !ok { + return nil, fmt.Errorf("unknown variable %s", *n.Var) + } + return v, nil case n.Sub != nil: return e.evalSub(ctx, ec, n.Sub) } diff --git a/cmdlang/inst.go b/cmdlang/inst.go index 88c9d1f..3d15844 100644 --- a/cmdlang/inst.go +++ b/cmdlang/inst.go @@ -13,11 +13,14 @@ type Inst struct { func New() *Inst { rootEC := evalCtx{} rootEC.addCmd("echo", invokableFunc(echoBuiltin)) + rootEC.addCmd("set", invokableFunc(setBuiltin)) rootEC.addCmd("toUpper", invokableStreamFunc(toUpperBuiltin)) rootEC.addCmd("cat", invokableFunc(catBuiltin)) rootEC.addCmd("testTimebomb", invokableStreamFunc(errorTestBuiltin)) + rootEC.setVar("hello", strObject("world")) + return &Inst{ rootEC: &rootEC, } diff --git a/cmdlang/objs.go b/cmdlang/objs.go index 8f372e7..1faa5a1 100644 --- a/cmdlang/objs.go +++ b/cmdlang/objs.go @@ -17,6 +17,7 @@ func (s strObject) String() string { } type invocationArgs struct { + ec *evalCtx args []object }