/
shell_cmd.go
140 lines (113 loc) · 3.48 KB
/
shell_cmd.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//go:build !wasm
// +build !wasm
package shell
import (
"context"
"fmt"
"regexp"
"sort"
"strings"
"github.com/anz-bank/pkg/log"
"github.com/arr-ai/wbnf/parser"
"github.com/go-errors/errors"
"github.com/arr-ai/arrai/syntax"
)
var cmdRe = regexp.MustCompile(`^/[a-zA-Z_]\w*[ \t]*`)
func isCommand(line string) bool {
return cmdRe.MatchString(line)
}
func tryRunCommand(ctx context.Context, line string, shellData *shellInstance) error {
var name string
if name = cmdRe.FindString(line); name == "" {
return errors.Errorf("%s is not a command", line)
}
name = strings.TrimSpace(name[1:])
line = strings.TrimSpace(cmdRe.ReplaceAllString(line, ""))
cmd, isCmd := shellData.cmds[name]
if !isCmd {
return errors.Errorf("command %s not found", name)
}
return cmd.process(ctx, line, shellData)
}
type shellCmd interface {
names() []string
process(ctx context.Context, line string, shellData *shellInstance) error
}
type setCmd struct{}
func (sc *setCmd) names() []string {
return []string{"set"}
}
func (sc *setCmd) process(ctx context.Context, line string, shellData *shellInstance) error {
identRe := regexp.MustCompile(`^(?P<ident>\.|[$@A-Za-z_][0-9$@A-Za-z_]*)[ \t]+=`)
identMatches := identRe.FindStringSubmatch(line)
if len(identMatches) != 2 {
return errors.Errorf(`/set command error, usage: /set <name> = <expr>`)
}
name := identMatches[1]
expr := identRe.ReplaceAllString(line, "")
val, err := shellEval(ctx, expr, shellData.scope)
if err != nil {
return err
}
shellData.scope = shellData.scope.With(name, val)
return nil
}
type unsetCmd struct{}
func (uc *unsetCmd) names() []string {
return []string{"unset"}
}
func (uc *unsetCmd) process(ctx context.Context, line string, shellData *shellInstance) error {
ident := regexp.MustCompile(`^(\.|[$@A-Za-z_][0-9$@A-Za-z_]*)`).FindString(line)
if ident == "" {
return errors.Errorf(`/unset command error, usage: /unset <name>`)
}
shellData.scope = shellData.scope.Without(ident)
return nil
}
type exitError struct{}
func (exitError) Error() string {
return "exiting interactive shell"
}
type exitCommand struct{}
func (*exitCommand) names() []string {
return []string{"exit"}
}
func (ec *exitCommand) process(ctx context.Context, _ string, _ *shellInstance) error {
return exitError{}
}
type upFrameCmd struct{}
func (*upFrameCmd) names() []string {
return []string{"up", "u"}
}
func (*upFrameCmd) process(ctx context.Context, _ string, sh *shellInstance) error {
return changeFrame(ctx, sh.currentFrameIndex-1, sh)
}
type downFrameCmd struct{}
func (d *downFrameCmd) names() []string {
return []string{"down", "d"}
}
func (*downFrameCmd) process(ctx context.Context, _ string, sh *shellInstance) error {
return changeFrame(ctx, sh.currentFrameIndex+1, sh)
}
func changeFrame(ctx context.Context, i int, sh *shellInstance) error {
if i < 0 || i >= len(sh.frames) {
return fmt.Errorf("frame index out of range, frame length: %d", len(sh.frames))
}
sh.currentFrameIndex = i
log.Infof(ctx, "Stack: %d\n%s\n", i, sh.frames[i].GetSource().Context(parser.DefaultLimit))
sh.scope = syntax.StdScope().Update(sh.frames[i].GetScope())
return nil
}
func initCommands() (map[string]shellCmd, []string) {
cmds := []shellCmd{&setCmd{}, &unsetCmd{}, &exitCommand{}, &upFrameCmd{}, &downFrameCmd{}}
cmdMap := make(map[string]shellCmd)
var preds []string
for _, cmd := range cmds {
for _, n := range cmd.names() {
cmdMap[n] = cmd
preds = append(preds, fmt.Sprintf("/%s ", n))
}
}
sort.Strings(preds)
return cmdMap, preds
}