First cut of procs

These capture the context, making them useful as lambdas
This commit is contained in:
Leon Mika 2024-04-18 20:26:02 +10:00
parent 5265efedc4
commit fe15730769
4 changed files with 101 additions and 3 deletions

View File

@ -75,6 +75,19 @@ func catBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return &fileLinesStream{filename: filename}, nil return &fileLinesStream{filename: filename}, nil
} }
func callBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 inv.invoke(ctx, args.shift(1))
}
type fileLinesStream struct { type fileLinesStream struct {
filename string filename string
f *os.File f *os.File
@ -187,3 +200,61 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
return last, nil return last, nil
} }
func procBuiltin(ctx context.Context, args macroArgs) (object, error) {
if args.nargs() < 1 {
return nil, errors.New("need at least one arguments")
}
var procName string
if args.nargs() == 2 {
name, ok := args.shiftIdent(ctx)
if !ok {
return nil, errors.New("malformed procedure: expected identifier as first argument")
}
procName = name
}
block, err := args.evalArg(ctx, 0)
if err != nil {
return nil, err
}
blockObj, ok := block.(blockObject)
if !ok {
return nil, fmt.Errorf("malformed procedure: expected block object, was %v", block.String())
}
obj := procObject{args.eval, args.ec, blockObj.block}
if procName != "" {
args.ec.addCmd(procName, obj)
}
return obj, nil
}
type procObject struct {
eval evaluator
ec *evalCtx
block *astBlock
}
func (b procObject) String() string {
return "(proc)"
}
func (b procObject) Truthy() bool {
return true
}
func (b procObject) invoke(ctx context.Context, args invocationArgs) (object, error) {
newEc := b.ec.fork()
for i, name := range b.block.Names {
if i < len(args.args) {
newEc.setVar(name, args.args[i])
} else {
newEc.setVar(name, nil)
}
}
return b.eval.evalBlock(ctx, newEc, b.block)
}

View File

@ -1,6 +1,7 @@
package cmdlang package cmdlang
type evalCtx struct { type evalCtx struct {
root *evalCtx
parent *evalCtx parent *evalCtx
commands map[string]invokable commands map[string]invokable
macros map[string]macroable macros map[string]macroable
@ -8,7 +9,7 @@ type evalCtx struct {
} }
func (ec *evalCtx) fork() *evalCtx { func (ec *evalCtx) fork() *evalCtx {
return &evalCtx{parent: ec} return &evalCtx{parent: ec, root: ec.root}
} }
func (ec *evalCtx) addCmd(name string, inv invokable) { func (ec *evalCtx) addCmd(name string, inv invokable) {

View File

@ -24,15 +24,18 @@ func WithOut(out io.Writer) InstOption {
} }
func New(opts ...InstOption) *Inst { func New(opts ...InstOption) *Inst {
rootEC := evalCtx{} rootEC := &evalCtx{}
rootEC.root = rootEC
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", invokableStreamFunc(toUpperBuiltin))
rootEC.addCmd("cat", invokableFunc(catBuiltin)) rootEC.addCmd("cat", invokableFunc(catBuiltin))
rootEC.addCmd("call", invokableFunc(callBuiltin))
rootEC.addMacro("if", macroFunc(ifBuiltin)) rootEC.addMacro("if", macroFunc(ifBuiltin))
rootEC.addMacro("foreach", macroFunc(foreachBuiltin)) rootEC.addMacro("foreach", macroFunc(foreachBuiltin))
rootEC.addMacro("proc", macroFunc(procBuiltin))
//rootEC.addCmd("testTimebomb", invokableStreamFunc(errorTestBuiltin)) //rootEC.addCmd("testTimebomb", invokableStreamFunc(errorTestBuiltin))
@ -40,7 +43,7 @@ func New(opts ...InstOption) *Inst {
inst := &Inst{ inst := &Inst{
out: os.Stdout, out: os.Stdout,
rootEC: &rootEC, rootEC: rootEC,
} }
for _, opt := range opts { for _, opt := range opts {

View File

@ -119,6 +119,19 @@ func (ma macroArgs) identIs(ctx context.Context, n int, expectedIdent string) bo
return *lit == expectedIdent return *lit == expectedIdent
} }
func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) {
if ma.argShift >= len(ma.ast.Args) {
return "", false
}
lit := ma.ast.Args[ma.argShift].Ident
if lit != nil {
ma.argShift += 1
return *lit, true
}
return "", false
}
func (ma macroArgs) evalArg(ctx context.Context, n int) (object, error) { func (ma macroArgs) evalArg(ctx context.Context, n int) (object, error) {
if n >= len(ma.ast.Args[ma.argShift:]) { if n >= len(ma.ast.Args[ma.argShift:]) {
return nil, errors.New("not enough arguments") // FIX return nil, errors.New("not enough arguments") // FIX
@ -177,6 +190,16 @@ func (ia invocationArgs) stringArg(i int) (string, error) {
return s.String(), nil return s.String(), nil
} }
func (ia invocationArgs) shift(i int) invocationArgs {
return invocationArgs{
inst: ia.inst,
ec: ia.ec,
currentStream: ia.currentStream,
args: ia.args[i:],
kwargs: ia.kwargs,
}
}
// invokable is an object that can be executed as a command // invokable is an object that can be executed as a command
type invokable interface { type invokable interface {
invoke(ctx context.Context, args invocationArgs) (object, error) invoke(ctx context.Context, args invocationArgs) (object, error)