-
Notifications
You must be signed in to change notification settings - Fork 589
/
Copy pathrepl.go
157 lines (128 loc) · 3.58 KB
/
repl.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Package repl implements a REPL (read-eval-print loop) for otto.
package repl
import (
"errors"
"fmt"
"io"
"strings"
"sync/atomic"
"github.com/robertkrimen/otto"
"gopkg.in/readline.v1"
)
var counter uint32
// DebuggerHandler implements otto's debugger handler signature, providing a
// simple drop-in debugger implementation.
func DebuggerHandler(vm *otto.Otto) {
i := atomic.AddUint32(&counter, 1)
// purposefully ignoring the error here - we can't do anything useful with
// it except panicking, and that'd be pretty rude. it'd be easy enough for a
// consumer to define an equivalent function that _does_ panic if desired.
_ = RunWithPrompt(vm, fmt.Sprintf("DEBUGGER[%d]> ", i))
}
// Run creates a REPL with the default prompt and no prelude.
func Run(vm *otto.Otto) error {
return RunWithOptions(vm, Options{})
}
// RunWithPrompt runs a REPL with the given prompt and no prelude.
func RunWithPrompt(vm *otto.Otto, prompt string) error {
return RunWithOptions(vm, Options{
Prompt: prompt,
})
}
// RunWithPrelude runs a REPL with the default prompt and the given prelude.
func RunWithPrelude(vm *otto.Otto, prelude string) error {
return RunWithOptions(vm, Options{
Prelude: prelude,
})
}
// RunWithPromptAndPrelude runs a REPL with the given prompt and prelude.
func RunWithPromptAndPrelude(vm *otto.Otto, prompt, prelude string) error {
return RunWithOptions(vm, Options{
Prompt: prompt,
Prelude: prelude,
})
}
// Options contains parameters for configuring a REPL session.
type Options struct {
// Prompt controls what's shown at the beginning of the line. If not
// specified, this defaults to "> "
Prompt string
// Prelude is some text that's printed before control is given to the user.
// By default this is empty. If specified, this will be printed with a
// newline following it.
Prelude string
// Autocomplete controls whether this REPL session has autocompletion
// enabled. The way autocomplete is implemented can incur a performance
// penalty, so it's turned off by default.
Autocomplete bool
}
// RunWithOptions runs a REPL with the given options.
func RunWithOptions(vm *otto.Otto, options Options) error {
prompt := options.Prompt
if prompt == "" {
prompt = "> "
}
c := &readline.Config{
Prompt: prompt,
}
if options.Autocomplete {
c.AutoComplete = &autoCompleter{vm}
}
rl, err := readline.NewEx(c)
if err != nil {
return err
}
prelude := options.Prelude
if prelude != "" {
if _, err = io.Copy(rl.Stderr(), strings.NewReader(prelude+"\n")); err != nil {
return err
}
rl.Refresh()
}
var d []string
for {
l, errRL := rl.Readline()
if errRL != nil {
if errors.Is(errRL, readline.ErrInterrupt) {
if d != nil {
d = nil
rl.SetPrompt(prompt)
rl.Refresh()
continue
}
break
}
return errRL
}
if l == "" {
continue
}
d = append(d, l)
s, errRL := vm.Compile("repl", strings.Join(d, "\n"))
if errRL != nil {
rl.SetPrompt(strings.Repeat(" ", len(prompt)))
} else {
rl.SetPrompt(prompt)
d = nil
v, errEval := vm.Eval(s)
if errEval != nil {
var oerr *otto.Error
if errors.As(errEval, &oerr) {
if _, err = io.Copy(rl.Stdout(), strings.NewReader(oerr.String())); err != nil {
return fmt.Errorf("write out: %w", err)
}
} else {
if _, err = io.Copy(rl.Stdout(), strings.NewReader(errEval.Error())); err != nil {
return fmt.Errorf("write out: %w", err)
}
}
} else {
if _, err = rl.Stdout().Write([]byte(v.String() + "\n")); err != nil {
return fmt.Errorf("write out: %w", err)
}
}
}
rl.Refresh()
}
return rl.Close()
}