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
|
// Package repl implements a REPL (read-eval-print loop) for otto.
package repl
import (
"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, err := rl.Readline()
if err != nil {
if err == readline.ErrInterrupt {
if d != nil {
d = nil
rl.SetPrompt(prompt)
rl.Refresh()
continue
}
break
}
return err
}
if l == "" {
continue
}
d = append(d, l)
s, err := vm.Compile("repl", strings.Join(d, "\n"))
if err != nil {
rl.SetPrompt(strings.Repeat(" ", len(prompt)))
} else {
rl.SetPrompt(prompt)
d = nil
v, err := vm.Eval(s)
if err != nil {
if oerr, ok := err.(*otto.Error); ok {
io.Copy(rl.Stdout(), strings.NewReader(oerr.String()))
} else {
io.Copy(rl.Stdout(), strings.NewReader(err.Error()))
}
} else {
rl.Stdout().Write([]byte(v.String() + "\n"))
}
}
rl.Refresh()
}
return rl.Close()
}
|