Initial version of prototype cmdlang

This commit is contained in:
Leon Mika 2024-04-10 20:45:58 +10:00
commit 781a761ead
10 changed files with 224 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.idea

30
cmd/cmsh/main.go Normal file
View file

@ -0,0 +1,30 @@
package main
import (
"context"
"github.com/chzyer/readline"
"github.com/lmika/cmdlang-proto/cmdlang"
"log"
)
func main() {
rl, err := readline.New("> ")
if err != nil {
panic(err)
}
defer rl.Close()
inst := cmdlang.New()
ctx := context.Background()
for {
line, err := rl.Readline()
if err != nil { // io.EOF
break
}
if err := inst.Eval(ctx, line); err != nil {
log.Printf("%T: %v", err, err)
}
}
}

26
cmdlang/ast.go Normal file
View file

@ -0,0 +1,26 @@
package cmdlang
import (
"github.com/alecthomas/participle/v2"
"io"
)
type astLiteral struct {
Str *string `parser:"@String"`
Ident *string `parser:" | @Ident"`
}
type astCmdArg struct {
Literal astLiteral `parser:"@@"`
}
type astCmd struct {
Name string `parser:"@Ident"`
Args []astCmdArg `parser:"@@*"`
}
var parser = participle.MustBuild[astCmd]()
func parse(r io.Reader) (*astCmd, error) {
return parser.Parse("test", r)
}

21
cmdlang/builtins.go Normal file
View file

@ -0,0 +1,21 @@
package cmdlang
import (
"context"
"log"
"strings"
)
func echoBuiltin(ctx context.Context, args invocationArgs) error {
if len(args.args) == 0 {
log.Print()
return nil
}
var line strings.Builder
for _, arg := range args.args {
line.WriteString(arg)
}
log.Print(line.String())
return nil
}

32
cmdlang/env.go Normal file
View file

@ -0,0 +1,32 @@
package cmdlang
import (
"errors"
)
type evalCtx struct {
parent *evalCtx
commands map[string]invokable
}
func (ec *evalCtx) addCmd(name string, inv invokable) {
if ec.commands == nil {
ec.commands = make(map[string]invokable)
}
ec.commands[name] = inv
}
func (ec *evalCtx) lookupCmd(name string) (invokable, error) {
for e := ec; e != nil; e = e.parent {
if ec.commands == nil {
continue
}
if cmd, ok := ec.commands[name]; ok {
return cmd, nil
}
}
return nil, errors.New("name " + name + " not found")
}

47
cmdlang/eval.go Normal file
View file

@ -0,0 +1,47 @@
package cmdlang
import (
"context"
"errors"
"github.com/lmika/gopkgs/fp/slices"
"strconv"
)
type evaluator struct {
}
func (e evaluator) evaluate(ctx context.Context, ec *evalCtx, ast *astCmd) error {
cmd, err := ec.lookupCmd(ast.Name)
if err != nil {
return err
}
args, err := slices.MapWithError(ast.Args, func(a astCmdArg) (string, error) {
return e.evalArg(ctx, ec, a)
})
if err != nil {
return err
}
return cmd.invoke(ctx, invocationArgs{
args: args,
})
}
func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (string, error) {
return e.evalLiteral(ctx, ec, n.Literal)
}
func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n astLiteral) (string, error) {
switch {
case n.Str != nil:
uq, err := strconv.Unquote(*n.Str)
if err != nil {
return "", err
}
return uq, nil
case n.Ident != nil:
return *n.Ident, nil
}
return "", errors.New("unhandled literal type")
}

30
cmdlang/inst.go Normal file
View file

@ -0,0 +1,30 @@
package cmdlang
import (
"context"
"strings"
)
type Inst struct {
rootEC *evalCtx
}
func New() *Inst {
rootEC := evalCtx{}
rootEC.addCmd("echo", invokableFunc(echoBuiltin))
return &Inst{
rootEC: &rootEC,
}
}
// TODO: return value?
func (inst *Inst) Eval(ctx context.Context, expr string) error {
ast, err := parse(strings.NewReader(expr))
if err != nil {
return err
}
eval := evaluator{}
return eval.evaluate(ctx, inst.rootEC, ast)
}

17
cmdlang/objs.go Normal file
View file

@ -0,0 +1,17 @@
package cmdlang
import "context"
type invocationArgs struct {
args []string
}
type invokable interface {
invoke(ctx context.Context, args invocationArgs) error
}
type invokableFunc func(ctx context.Context, args invocationArgs) error
func (i invokableFunc) invoke(ctx context.Context, args invocationArgs) error {
return i(ctx, args)
}

10
go.mod Normal file
View file

@ -0,0 +1,10 @@
module github.com/lmika/cmdlang-proto
go 1.21.1
require (
github.com/alecthomas/participle/v2 v2.1.1 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f // indirect
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
)

10
go.sum Normal file
View file

@ -0,0 +1,10 @@
github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8=
github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f h1:tz68Lhc1oR15HVz69IGbtdukdH0x70kBDEvvj5pTXyE=
github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f/go.mod h1:zHQvhjGXRro/Xp2C9dbC+ZUpE0gL4GYW75x1lk7hwzI=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=