2018-01-01 23:12:34 +08:00
|
|
|
// Package eval handles evaluation of parsed Elvish code and provides runtime
|
|
|
|
// facilities.
|
2013-09-18 22:51:48 +08:00
|
|
|
package eval
|
|
|
|
|
|
|
|
import (
|
2014-01-27 18:55:45 +08:00
|
|
|
"fmt"
|
2020-04-26 20:14:51 +08:00
|
|
|
"io"
|
2014-01-22 22:45:33 +08:00
|
|
|
"os"
|
2018-10-10 00:39:24 +08:00
|
|
|
"strconv"
|
2014-02-10 11:33:53 +08:00
|
|
|
|
2019-12-24 04:00:59 +08:00
|
|
|
"github.com/elves/elvish/pkg/daemon"
|
2021-01-03 23:20:31 +08:00
|
|
|
"github.com/elves/elvish/pkg/diag"
|
2020-09-03 11:29:07 +08:00
|
|
|
"github.com/elves/elvish/pkg/eval/mods/bundled"
|
2019-12-24 04:00:59 +08:00
|
|
|
"github.com/elves/elvish/pkg/eval/vals"
|
|
|
|
"github.com/elves/elvish/pkg/eval/vars"
|
2020-09-03 12:27:18 +08:00
|
|
|
"github.com/elves/elvish/pkg/logutil"
|
2019-12-24 04:00:59 +08:00
|
|
|
"github.com/elves/elvish/pkg/parse"
|
2018-01-09 08:12:44 +08:00
|
|
|
"github.com/xiaq/persistent/vector"
|
2013-09-18 22:51:48 +08:00
|
|
|
)
|
|
|
|
|
2020-09-03 12:27:18 +08:00
|
|
|
var logger = logutil.GetLogger("[eval] ")
|
2016-02-08 03:39:03 +08:00
|
|
|
|
2017-12-24 07:51:32 +08:00
|
|
|
const (
|
|
|
|
// FnSuffix is the suffix for the variable names of functions. Defining a
|
|
|
|
// function "foo" is equivalent to setting a variable named "foo~", and vice
|
|
|
|
// versa.
|
|
|
|
FnSuffix = "~"
|
|
|
|
// NsSuffix is the suffix for the variable names of namespaces. Defining a
|
|
|
|
// namespace foo is equivalent to setting a variable named "foo:", and vice
|
|
|
|
// versa.
|
|
|
|
NsSuffix = ":"
|
|
|
|
)
|
2015-02-24 17:21:27 +08:00
|
|
|
|
2017-10-01 09:05:13 +08:00
|
|
|
const (
|
2018-06-02 02:06:34 +08:00
|
|
|
defaultValuePrefix = "▶ "
|
|
|
|
defaultNotifyBgJobSuccess = true
|
|
|
|
initIndent = vals.NoPretty
|
2017-10-01 09:05:13 +08:00
|
|
|
)
|
|
|
|
|
2015-02-25 19:21:06 +08:00
|
|
|
// Evaler is used to evaluate elvish sources. It maintains runtime context
|
|
|
|
// shared among all evalCtx instances.
|
2015-02-25 07:37:18 +08:00
|
|
|
type Evaler struct {
|
2017-12-18 23:22:59 +08:00
|
|
|
evalerScopes
|
2018-09-29 01:54:36 +08:00
|
|
|
|
|
|
|
state state
|
|
|
|
|
2018-09-29 03:09:02 +08:00
|
|
|
// Chdir hooks.
|
|
|
|
beforeChdir []func(string)
|
|
|
|
afterChdir []func(string)
|
|
|
|
|
|
|
|
// State of the module system.
|
2017-12-23 07:45:47 +08:00
|
|
|
libDir string
|
2018-09-29 03:09:02 +08:00
|
|
|
bundled map[string]string
|
2020-01-11 10:27:43 +08:00
|
|
|
// Internal modules are indexed by use specs. External modules are indexed by
|
|
|
|
// absolute paths.
|
2020-12-25 01:39:51 +08:00
|
|
|
modules map[string]*Ns
|
2018-09-29 03:09:02 +08:00
|
|
|
|
2020-04-26 08:13:05 +08:00
|
|
|
deprecations deprecationRegistry
|
|
|
|
|
2018-09-29 03:09:02 +08:00
|
|
|
// Dependencies.
|
|
|
|
//
|
|
|
|
// TODO: Remove these dependency by providing more general extension points.
|
2019-12-27 01:23:18 +08:00
|
|
|
DaemonClient daemon.Client
|
2018-09-29 03:09:02 +08:00
|
|
|
Editor Editor
|
2013-10-17 16:45:26 +08:00
|
|
|
}
|
2013-09-18 22:51:48 +08:00
|
|
|
|
2020-04-16 07:11:23 +08:00
|
|
|
// EvalCfg keeps configuration for the (*Evaler).Eval method.
|
|
|
|
type EvalCfg struct {
|
2021-01-02 08:10:26 +08:00
|
|
|
// Ports to use in evaluation. If nil, equivalent to specifying
|
|
|
|
// DevNullPorts[:]. If not nil, must contain at least 3 elements.
|
2020-04-16 07:11:23 +08:00
|
|
|
Ports []*Port
|
|
|
|
// Callback to get a channel of interrupt signals and a function to call
|
|
|
|
// when the channel is no longer needed.
|
|
|
|
Interrupt func() (<-chan struct{}, func())
|
|
|
|
// Whether the Eval method should try to put the Elvish in the foreground
|
2021-01-02 08:10:26 +08:00
|
|
|
// after the code is executed.
|
2020-04-16 07:11:23 +08:00
|
|
|
PutInFg bool
|
|
|
|
}
|
|
|
|
|
2017-12-18 23:22:59 +08:00
|
|
|
type evalerScopes struct {
|
2020-12-25 01:39:51 +08:00
|
|
|
Global *Ns
|
|
|
|
Builtin *Ns
|
2017-12-18 23:22:59 +08:00
|
|
|
}
|
|
|
|
|
2020-01-18 21:12:50 +08:00
|
|
|
//elvdoc:var after-chdir
|
|
|
|
//
|
|
|
|
// A list of functions to run after changing directory. These functions are always
|
|
|
|
// called with directory to change it, which might be a relative path. The
|
|
|
|
// following example also shows `$before-chdir`:
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> before-chdir = [[dir]{ echo "Going to change to "$dir", pwd is "$pwd }]
|
|
|
|
// ~> after-chdir = [[dir]{ echo "Changed to "$dir", pwd is "$pwd }]
|
|
|
|
// ~> cd /usr
|
|
|
|
// Going to change to /usr, pwd is /Users/xiaq
|
|
|
|
// Changed to /usr, pwd is /usr
|
|
|
|
// /usr> cd local
|
|
|
|
// Going to change to local, pwd is /usr
|
|
|
|
// Changed to local, pwd is /usr/local
|
|
|
|
// /usr/local>
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// @cf before-chdir
|
|
|
|
|
|
|
|
//elvdoc:var before-chdir
|
|
|
|
//
|
|
|
|
// A list of functions to run before changing directory. These functions are always
|
|
|
|
// called with the new working directory.
|
|
|
|
//
|
|
|
|
// @cf after-chdir
|
|
|
|
|
|
|
|
//elvdoc:var num-bg-jobs
|
|
|
|
//
|
|
|
|
// Number of background jobs.
|
|
|
|
|
|
|
|
//elvdoc:var notify-bg-job-success
|
|
|
|
//
|
|
|
|
// Whether to notify success of background jobs, defaulting to `$true`.
|
|
|
|
//
|
|
|
|
// Failures of background jobs are always notified.
|
|
|
|
|
|
|
|
//elvdoc:var value-out-indicator
|
|
|
|
//
|
|
|
|
// A string put before value outputs (such as those of of `put`). Defaults to
|
|
|
|
// `'▶ '`. Example:
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> put lorem ipsum
|
|
|
|
// ▶ lorem
|
|
|
|
// ▶ ipsum
|
|
|
|
// ~> value-out-indicator = 'val> '
|
|
|
|
// ~> put lorem ipsum
|
|
|
|
// val> lorem
|
|
|
|
// val> ipsum
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// Note that you almost always want some trailing whitespace for readability.
|
|
|
|
|
2015-02-25 19:21:06 +08:00
|
|
|
// NewEvaler creates a new Evaler.
|
2017-12-23 07:45:47 +08:00
|
|
|
func NewEvaler() *Evaler {
|
2020-12-25 01:39:51 +08:00
|
|
|
builtin := builtinNs.Ns()
|
2017-06-19 18:58:58 +08:00
|
|
|
|
2017-10-01 09:05:13 +08:00
|
|
|
ev := &Evaler{
|
2018-09-29 01:54:36 +08:00
|
|
|
state: state{
|
|
|
|
valuePrefix: defaultValuePrefix,
|
|
|
|
notifyBgJobSuccess: defaultNotifyBgJobSuccess,
|
|
|
|
numBgJobs: 0,
|
|
|
|
},
|
2017-12-18 23:22:59 +08:00
|
|
|
evalerScopes: evalerScopes{
|
2020-12-25 01:39:51 +08:00
|
|
|
Global: new(Ns),
|
2018-03-22 04:51:08 +08:00
|
|
|
Builtin: builtin,
|
2017-12-18 23:22:59 +08:00
|
|
|
},
|
2020-12-25 01:39:51 +08:00
|
|
|
modules: map[string]*Ns{
|
2017-12-24 07:51:32 +08:00
|
|
|
"builtin": builtin,
|
2017-12-23 07:45:47 +08:00
|
|
|
},
|
2018-01-01 04:07:26 +08:00
|
|
|
bundled: bundled.Get(),
|
2020-04-26 08:13:05 +08:00
|
|
|
|
|
|
|
deprecations: newDeprecationRegistry(),
|
2017-06-25 00:08:47 +08:00
|
|
|
}
|
2017-12-28 04:43:43 +08:00
|
|
|
|
2018-03-22 04:51:08 +08:00
|
|
|
beforeChdirElvish, afterChdirElvish := vector.Empty, vector.Empty
|
|
|
|
ev.beforeChdir = append(ev.beforeChdir,
|
|
|
|
adaptChdirHook("before-chdir", ev, &beforeChdirElvish))
|
|
|
|
ev.afterChdir = append(ev.afterChdir,
|
|
|
|
adaptChdirHook("after-chdir", ev, &afterChdirElvish))
|
|
|
|
|
2020-12-25 01:39:51 +08:00
|
|
|
moreBuiltinsBuilder := make(NsBuilder)
|
|
|
|
moreBuiltinsBuilder["before-chdir"] = vars.FromPtr(&beforeChdirElvish)
|
|
|
|
moreBuiltinsBuilder["after-chdir"] = vars.FromPtr(&afterChdirElvish)
|
|
|
|
|
|
|
|
moreBuiltinsBuilder["value-out-indicator"] = vars.FromPtrWithMutex(
|
2018-09-29 01:54:36 +08:00
|
|
|
&ev.state.valuePrefix, &ev.state.mutex)
|
2020-12-25 01:39:51 +08:00
|
|
|
moreBuiltinsBuilder["notify-bg-job-success"] = vars.FromPtrWithMutex(
|
2018-09-29 01:54:36 +08:00
|
|
|
&ev.state.notifyBgJobSuccess, &ev.state.mutex)
|
2020-12-25 01:39:51 +08:00
|
|
|
moreBuiltinsBuilder["num-bg-jobs"] = vars.FromGet(func() interface{} {
|
2018-10-10 00:39:24 +08:00
|
|
|
return strconv.Itoa(ev.state.getNumBgJobs())
|
2018-09-29 01:54:36 +08:00
|
|
|
})
|
2020-12-25 01:39:51 +08:00
|
|
|
moreBuiltinsBuilder["pwd"] = NewPwdVar(ev)
|
|
|
|
|
|
|
|
moreBuiltins := moreBuiltinsBuilder.Ns()
|
|
|
|
builtin.slots = append(builtin.slots, moreBuiltins.slots...)
|
|
|
|
builtin.names = append(builtin.names, moreBuiltins.names...)
|
2018-03-01 10:35:32 +08:00
|
|
|
|
2017-10-01 09:05:13 +08:00
|
|
|
return ev
|
2016-09-12 00:14:56 +08:00
|
|
|
}
|
|
|
|
|
2018-03-22 04:51:08 +08:00
|
|
|
func adaptChdirHook(name string, ev *Evaler, pfns *vector.Vector) func(string) {
|
|
|
|
return func(path string) {
|
2020-04-16 07:11:23 +08:00
|
|
|
ports, cleanup := portsFromFiles(
|
|
|
|
[3]*os.File{os.Stdin, os.Stdout, os.Stderr}, ev.state.getValuePrefix())
|
|
|
|
defer cleanup()
|
2018-03-22 04:51:08 +08:00
|
|
|
for it := (*pfns).Iterator(); it.HasElem(); it.Next() {
|
|
|
|
fn, ok := it.Elem().(Callable)
|
|
|
|
if !ok {
|
|
|
|
fmt.Fprintln(os.Stderr, name, "hook must be callable")
|
|
|
|
continue
|
|
|
|
}
|
2020-04-26 02:22:38 +08:00
|
|
|
fm := NewTopFrame(ev, parse.Source{Name: "[hook " + name + "]"}, ports[:])
|
2020-04-10 04:01:14 +08:00
|
|
|
err := fn.Call(fm, []interface{}{path}, NoOpts)
|
2018-03-22 04:51:08 +08:00
|
|
|
if err != nil {
|
|
|
|
// TODO: Stack trace
|
|
|
|
fmt.Fprintln(os.Stderr, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-01 10:35:32 +08:00
|
|
|
// Close releases resources allocated when creating this Evaler. Currently this
|
|
|
|
// does nothing and always returns a nil error.
|
|
|
|
func (ev *Evaler) Close() error {
|
|
|
|
return nil
|
2017-12-28 04:43:43 +08:00
|
|
|
}
|
|
|
|
|
2018-03-22 04:51:08 +08:00
|
|
|
// AddBeforeChdir adds a function to run before changing directory.
|
|
|
|
func (ev *Evaler) AddBeforeChdir(f func(string)) {
|
|
|
|
ev.beforeChdir = append(ev.beforeChdir, f)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddAfterChdir adds a function to run after changing directory.
|
|
|
|
func (ev *Evaler) AddAfterChdir(f func(string)) {
|
|
|
|
ev.afterChdir = append(ev.afterChdir, f)
|
|
|
|
}
|
|
|
|
|
2018-01-03 08:56:19 +08:00
|
|
|
// InstallDaemonClient installs a daemon client to the Evaler.
|
2019-12-27 01:23:18 +08:00
|
|
|
func (ev *Evaler) InstallDaemonClient(client daemon.Client) {
|
2018-01-03 08:56:19 +08:00
|
|
|
ev.DaemonClient = client
|
2017-12-23 07:45:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// InstallModule installs a module to the Evaler so that it can be used with
|
|
|
|
// "use $name" from script.
|
2020-12-25 01:39:51 +08:00
|
|
|
func (ev *Evaler) InstallModule(name string, mod *Ns) {
|
2018-01-04 05:49:44 +08:00
|
|
|
ev.modules[name] = mod
|
2017-12-23 07:45:47 +08:00
|
|
|
}
|
|
|
|
|
2018-09-29 03:09:02 +08:00
|
|
|
// SetArgs replaces the $args builtin variable with a vector built from the
|
|
|
|
// argument.
|
2018-01-09 08:12:44 +08:00
|
|
|
func (ev *Evaler) SetArgs(args []string) {
|
|
|
|
v := vector.Empty
|
|
|
|
for _, arg := range args {
|
2018-01-25 09:40:15 +08:00
|
|
|
v = v.Cons(arg)
|
2018-01-09 08:12:44 +08:00
|
|
|
}
|
2020-12-25 01:39:51 +08:00
|
|
|
// TODO: Avoid creating the variable dynamically; instead, always create the
|
|
|
|
// variable, and set its value here.
|
|
|
|
ev.Builtin.slots = append(ev.Builtin.slots, vars.NewReadOnly(v))
|
|
|
|
ev.Builtin.names = append(ev.Builtin.names, "args")
|
2018-01-09 08:12:44 +08:00
|
|
|
}
|
|
|
|
|
2018-01-01 23:12:34 +08:00
|
|
|
// SetLibDir sets the library directory, in which external modules are to be
|
|
|
|
// found.
|
2017-12-23 07:45:47 +08:00
|
|
|
func (ev *Evaler) SetLibDir(libDir string) {
|
|
|
|
ev.libDir = libDir
|
|
|
|
}
|
|
|
|
|
2015-02-25 19:21:06 +08:00
|
|
|
// growPorts makes the size of ec.ports at least n, adding nil's if necessary.
|
2018-03-01 10:17:56 +08:00
|
|
|
func (fm *Frame) growPorts(n int) {
|
|
|
|
if len(fm.ports) >= n {
|
2014-05-24 03:29:40 +08:00
|
|
|
return
|
|
|
|
}
|
2018-03-01 10:17:56 +08:00
|
|
|
ports := fm.ports
|
|
|
|
fm.ports = make([]*Port, n)
|
|
|
|
copy(fm.ports, ports)
|
2014-05-24 03:29:40 +08:00
|
|
|
}
|
|
|
|
|
2021-01-02 08:10:26 +08:00
|
|
|
// Eval evaluates a piece of source code with the given configuration. The
|
|
|
|
// returned error may be a parse error, compilation error or exception.
|
|
|
|
func (ev *Evaler) Eval(src parse.Source, cfg EvalCfg) error {
|
2021-01-02 10:22:44 +08:00
|
|
|
cfg.Ports = fillPorts(cfg.Ports)
|
2021-01-02 10:41:36 +08:00
|
|
|
op, err := ev.parseAndCompile(src, cfg.Ports[2].File)
|
2021-01-02 08:10:26 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-03 23:20:31 +08:00
|
|
|
return ev.execOp(op, cfg)
|
2021-01-02 08:10:26 +08:00
|
|
|
}
|
|
|
|
|
2021-01-02 10:22:44 +08:00
|
|
|
func fillPorts(ports []*Port) []*Port {
|
|
|
|
if len(ports) < 3 {
|
|
|
|
ports = append(ports, make([]*Port, 3-len(ports))...)
|
|
|
|
}
|
|
|
|
if ports[0] == nil {
|
|
|
|
ports[0] = DevNullClosedChan
|
|
|
|
}
|
|
|
|
if ports[1] == nil {
|
|
|
|
ports[1] = DevNullBlackholeChan
|
|
|
|
}
|
|
|
|
if ports[2] == nil {
|
|
|
|
ports[2] = DevNullBlackholeChan
|
|
|
|
}
|
|
|
|
return ports
|
|
|
|
}
|
|
|
|
|
2021-01-02 08:10:26 +08:00
|
|
|
// Executes an Op with the specified configuration.
|
|
|
|
func (ev *Evaler) execOp(op Op, cfg EvalCfg) error {
|
2020-04-16 07:11:23 +08:00
|
|
|
fm := NewTopFrame(ev, op.Src, cfg.Ports)
|
|
|
|
if cfg.Interrupt != nil {
|
|
|
|
intCh, cleanup := cfg.Interrupt()
|
|
|
|
defer cleanup()
|
|
|
|
fm.intCh = intCh
|
|
|
|
}
|
|
|
|
if cfg.PutInFg {
|
|
|
|
defer func() {
|
|
|
|
err := putSelfInFg()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintln(cfg.Ports[2].File,
|
|
|
|
"failed to put myself in foreground:", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
2020-04-10 04:01:14 +08:00
|
|
|
return op.Inner.exec(fm)
|
2018-03-01 10:50:27 +08:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:20:31 +08:00
|
|
|
// Parses and compiles a Source. Aborts if parse fails.
|
2021-01-02 10:41:36 +08:00
|
|
|
func (ev *Evaler) parseAndCompile(src parse.Source, w io.Writer) (Op, error) {
|
2020-07-02 04:39:06 +08:00
|
|
|
tree, err := parse.ParseWithDeprecation(src, w)
|
2020-04-15 06:54:45 +08:00
|
|
|
if err != nil {
|
|
|
|
return Op{}, err
|
|
|
|
}
|
2020-04-26 20:14:51 +08:00
|
|
|
return ev.Compile(tree, w)
|
2020-04-15 06:54:45 +08:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:20:31 +08:00
|
|
|
// Check checks the given source code and returns any parse error and
|
|
|
|
// compilation error in it. It always tries to compile the code even if there is
|
|
|
|
// a parse error; both return values may be non-nil. If the io.Writer argument
|
|
|
|
// is not nil, deprecation messages are written to it.
|
|
|
|
func (ev *Evaler) Check(src parse.Source, w io.Writer) (*parse.Error, *diag.Error) {
|
|
|
|
tree, parseErr := parse.ParseWithDeprecation(src, w)
|
|
|
|
_, compileErr := ev.Compile(tree, w)
|
|
|
|
return parse.GetError(parseErr), GetCompilationError(compileErr)
|
|
|
|
}
|
|
|
|
|
2018-03-01 11:14:04 +08:00
|
|
|
// Compile compiles Elvish code in the global scope. If the error is not nil, it
|
2019-12-21 18:24:22 +08:00
|
|
|
// can be passed to GetCompilationError to retrieve more details.
|
2020-04-26 20:14:51 +08:00
|
|
|
func (ev *Evaler) Compile(tree parse.Tree, w io.Writer) (Op, error) {
|
|
|
|
return ev.CompileWithGlobal(tree, ev.Global, w)
|
2018-10-10 21:07:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// CompileWithGlobal compiles Elvish code in an alternative global scope. If the
|
2019-12-21 18:24:22 +08:00
|
|
|
// error is not nil, it can be passed to GetCompilationError to retrieve more
|
|
|
|
// details.
|
2018-10-10 21:07:42 +08:00
|
|
|
//
|
|
|
|
// TODO(xiaq): To use the Op created, the caller must create a Frame and mutate
|
|
|
|
// its local scope manually. Consider restructuring the API to make that
|
|
|
|
// unnecessary.
|
2020-12-25 01:39:51 +08:00
|
|
|
func (ev *Evaler) CompileWithGlobal(tree parse.Tree, g *Ns, w io.Writer) (Op, error) {
|
2020-04-26 20:14:51 +08:00
|
|
|
return compile(ev.Builtin.static(), g.static(), tree, w)
|
2016-02-06 07:25:13 +08:00
|
|
|
}
|