github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/eval/eval.go (about)

     1  // Package eval handles evaluation of nodes and consists the runtime of the
     2  // shell.
     3  package eval
     4  
     5  //go:generate ./gen-embedded-modules
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"os"
    15  	"os/signal"
    16  	"strconv"
    17  	"strings"
    18  	"sync"
    19  	"syscall"
    20  	"unicode/utf8"
    21  
    22  	"github.com/elves/elvish/daemon"
    23  	"github.com/elves/elvish/daemon/api"
    24  	"github.com/elves/elvish/parse"
    25  	"github.com/elves/elvish/sys"
    26  	"github.com/elves/elvish/util"
    27  )
    28  
    29  var logger = util.GetLogger("[eval] ")
    30  
    31  // FnPrefix is the prefix for the variable names of functions. Defining a
    32  // function "foo" is equivalent to setting a variable named FnPrefix + "foo".
    33  const FnPrefix = "&"
    34  
    35  // Namespace is a map from name to variables.
    36  type Namespace map[string]Variable
    37  
    38  // Evaler is used to evaluate elvish sources. It maintains runtime context
    39  // shared among all evalCtx instances.
    40  type Evaler struct {
    41  	Builtin Namespace
    42  	Global  Namespace
    43  	Modules map[string]Namespace
    44  	Daemon  *api.Client
    45  	ToSpawn *daemon.Daemon
    46  	Editor  Editor
    47  	DataDir string
    48  	intCh   chan struct{}
    49  }
    50  
    51  // EvalCtx maintains an Evaler along with its runtime context. After creation
    52  // an EvalCtx is seldom modified, and new instances are created when needed.
    53  type EvalCtx struct {
    54  	*Evaler
    55  	name, srcName, src string
    56  
    57  	local, up   Namespace
    58  	ports       []*Port
    59  	positionals []Value
    60  
    61  	begin, end int
    62  	traceback  *util.SourceContext
    63  
    64  	background bool
    65  }
    66  
    67  // NewEvaler creates a new Evaler.
    68  func NewEvaler(daemon *api.Client, toSpawn *daemon.Daemon,
    69  	dataDir string, extraModules map[string]Namespace) *Evaler {
    70  
    71  	// TODO(xiaq): Create daemon namespace asynchronously.
    72  	modules := map[string]Namespace{
    73  		"daemon": makeDaemonNamespace(daemon),
    74  	}
    75  	for name, mod := range extraModules {
    76  		modules[name] = mod
    77  	}
    78  
    79  	return &Evaler{
    80  		Builtin: makeBuiltinNamespace(daemon),
    81  		Global:  Namespace{},
    82  		Modules: modules,
    83  		Daemon:  daemon,
    84  		ToSpawn: toSpawn,
    85  		Editor:  nil,
    86  		DataDir: dataDir,
    87  		intCh:   nil,
    88  	}
    89  }
    90  
    91  func (ev *Evaler) searchPaths() []string {
    92  	return ev.Builtin["paths"].(*EnvPathList).get()
    93  }
    94  
    95  const (
    96  	outChanSize    = 32
    97  	outChanLeader  = "▶ "
    98  	falseIndicator = "✗"
    99  	initIndent     = NoPretty
   100  )
   101  
   102  // NewTopEvalCtx creates a top-level evalCtx.
   103  func NewTopEvalCtx(ev *Evaler, name, text string, ports []*Port) *EvalCtx {
   104  	return &EvalCtx{
   105  		ev, "top",
   106  		name, text,
   107  		ev.Global, Namespace{},
   108  		ports, nil,
   109  		0, len(text), nil, false,
   110  	}
   111  }
   112  
   113  // fork returns a modified copy of ec. The ports are forked, and the name is
   114  // changed to the given value. Other fields are copied shallowly.
   115  func (ec *EvalCtx) fork(name string) *EvalCtx {
   116  	newPorts := make([]*Port, len(ec.ports))
   117  	for i, p := range ec.ports {
   118  		newPorts[i] = p.Fork()
   119  	}
   120  	return &EvalCtx{
   121  		ec.Evaler, name,
   122  		ec.srcName, ec.src,
   123  		ec.local, ec.up,
   124  		newPorts, ec.positionals,
   125  		ec.begin, ec.end, ec.traceback, ec.background,
   126  	}
   127  }
   128  
   129  // port returns ec.ports[i] or nil if i is out of range. This makes it possible
   130  // to treat ec.ports as if it has an infinite tail of nil's.
   131  func (ec *EvalCtx) port(i int) *Port {
   132  	if i >= len(ec.ports) {
   133  		return nil
   134  	}
   135  	return ec.ports[i]
   136  }
   137  
   138  // growPorts makes the size of ec.ports at least n, adding nil's if necessary.
   139  func (ec *EvalCtx) growPorts(n int) {
   140  	if len(ec.ports) >= n {
   141  		return
   142  	}
   143  	ports := ec.ports
   144  	ec.ports = make([]*Port, n)
   145  	copy(ec.ports, ports)
   146  }
   147  
   148  func makeScope(s Namespace) scope {
   149  	sc := scope{}
   150  	for name := range s {
   151  		sc[name] = true
   152  	}
   153  	return sc
   154  }
   155  
   156  // eval evaluates a chunk node n. The supplied name and text are used in
   157  // diagnostic messages.
   158  func (ev *Evaler) eval(op Op, ports []*Port, name, text string) error {
   159  	ec := NewTopEvalCtx(ev, name, text, ports)
   160  	return ec.PEval(op)
   161  }
   162  
   163  func (ec *EvalCtx) Interrupts() <-chan struct{} {
   164  	return ec.intCh
   165  }
   166  
   167  // Eval sets up the Evaler with standard ports and evaluates an Op. The supplied
   168  // name and text are used in diagnostic messages.
   169  func (ev *Evaler) Eval(op Op, name, text string) error {
   170  	inCh := make(chan Value)
   171  	close(inCh)
   172  
   173  	outCh := make(chan Value, outChanSize)
   174  	outDone := make(chan struct{})
   175  	go func() {
   176  		for v := range outCh {
   177  			fmt.Println(outChanLeader + v.Repr(initIndent))
   178  		}
   179  		close(outDone)
   180  	}()
   181  	defer func() {
   182  		close(outCh)
   183  		<-outDone
   184  	}()
   185  
   186  	ports := []*Port{
   187  		{File: os.Stdin, Chan: inCh},
   188  		{File: os.Stdout, Chan: outCh},
   189  		{File: os.Stderr, Chan: BlackholeChan},
   190  	}
   191  
   192  	return ev.EvalWithPorts(ports, op, name, text)
   193  }
   194  
   195  // EvalWithPorts sets up the Evaler with the given ports and evaluates an Op.
   196  // The supplied name and text are used in diagnostic messages.
   197  func (ev *Evaler) EvalWithPorts(ports []*Port, op Op, name, text string) error {
   198  	// signal.Ignore(syscall.SIGTTIN)
   199  
   200  	// Ignore TTOU.
   201  	// When a subprocess in its own process group puts itself in the foreground,
   202  	// the elvish will be in the background. In that case, elvish will move
   203  	// itself back to the foreground by calling tcsetpgrp. However, whenever a
   204  	// background process calls tcsetpgrp (or otherwise attempts to modify the
   205  	// terminal configuration), TTOU will be sent, whose default handler is to
   206  	// stop the process. When the process lives in an orphaned process group
   207  	// (most likely for elvish), the call will outright fail. Therefore, for
   208  	// elvish to be able to move itself back to the foreground, we need to
   209  	// ignore TTOU.
   210  	signal.Ignore(syscall.SIGTTOU)
   211  	stopSigGoroutine := make(chan struct{})
   212  	sigGoRoutineDone := make(chan struct{})
   213  	// Set up intCh.
   214  	ev.intCh = make(chan struct{})
   215  	sigCh := make(chan os.Signal)
   216  	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGQUIT)
   217  	go func() {
   218  		closedIntCh := false
   219  	loop:
   220  		for {
   221  			select {
   222  			case <-sigCh:
   223  				if !closedIntCh {
   224  					close(ev.intCh)
   225  					closedIntCh = true
   226  				}
   227  			case <-stopSigGoroutine:
   228  				break loop
   229  			}
   230  		}
   231  		ev.intCh = nil
   232  		signal.Stop(sigCh)
   233  		close(sigGoRoutineDone)
   234  	}()
   235  
   236  	err := ev.eval(op, ports, name, text)
   237  
   238  	close(stopSigGoroutine)
   239  	<-sigGoRoutineDone
   240  
   241  	// Put myself in foreground, in case some command has put me in background.
   242  	// XXX Should probably use fd of /dev/tty instead of 0.
   243  	if sys.IsATTY(0) {
   244  		err := sys.Tcsetpgrp(0, syscall.Getpgrp())
   245  		if err != nil {
   246  			fmt.Println("failed to put myself in foreground:", err)
   247  		}
   248  	}
   249  
   250  	// Un-ignore TTOU.
   251  	signal.Reset(syscall.SIGTTOU)
   252  
   253  	return err
   254  }
   255  
   256  func summarize(text string) string {
   257  	// TODO Make a proper summary.
   258  	if len(text) < 32 {
   259  		return text
   260  	}
   261  	var b bytes.Buffer
   262  	for i, r := range text {
   263  		if i+len(string(r)) >= 32 {
   264  			break
   265  		}
   266  		b.WriteRune(r)
   267  	}
   268  	return b.String()
   269  }
   270  
   271  // Compile compiles elvish code in the global scope. If the error is not nil, it
   272  // always has type CompilationError.
   273  func (ev *Evaler) Compile(n *parse.Chunk, name, text string) (Op, error) {
   274  	return compile(makeScope(ev.Builtin), makeScope(ev.Global), n, name, text)
   275  }
   276  
   277  // PEval evaluates an op in a protected environment so that calls to errorf are
   278  // wrapped in an Error.
   279  func (ec *EvalCtx) PEval(op Op) (err error) {
   280  	defer catch(&err, ec)
   281  	op.Exec(ec)
   282  	return nil
   283  }
   284  
   285  func (ec *EvalCtx) PCall(f Callable, args []Value, opts map[string]Value) (err error) {
   286  	defer catch(&err, ec)
   287  	f.Call(ec, args, opts)
   288  	return nil
   289  }
   290  
   291  func (ec *EvalCtx) PCaptureOutput(f Callable, args []Value, opts map[string]Value) (vs []Value, err error) {
   292  	// XXX There is no source.
   293  	return pcaptureOutput(ec, Op{
   294  		func(newec *EvalCtx) { f.Call(newec, args, opts) }, -1, -1})
   295  }
   296  
   297  func catch(perr *error, ec *EvalCtx) {
   298  	// NOTE: We have to duplicate instead of calling util.Catch here, since
   299  	// recover can only catch a panic when called directly from a deferred
   300  	// function.
   301  	r := recover()
   302  	if r == nil {
   303  		return
   304  	}
   305  	if exc, ok := r.(util.Thrown); ok {
   306  		err := exc.Error
   307  		if _, ok := err.(*Exception); !ok {
   308  			err = ec.makeException(err)
   309  		}
   310  		*perr = err
   311  	} else if r != nil {
   312  		panic(r)
   313  	}
   314  }
   315  
   316  // makeException turns an error into an Exception by adding traceback.
   317  func (ec *EvalCtx) makeException(e error) *Exception {
   318  	return &Exception{e, ec.addTraceback()}
   319  }
   320  
   321  func (ec *EvalCtx) addTraceback() *util.SourceContext {
   322  	return &util.SourceContext{
   323  		Name: ec.srcName, Source: ec.src,
   324  		Begin: ec.begin, End: ec.end, Next: ec.traceback,
   325  	}
   326  }
   327  
   328  // errorpf stops the ec.eval immediately by panicking with a diagnostic message.
   329  // The panic is supposed to be caught by ec.eval.
   330  func (ec *EvalCtx) errorpf(begin, end int, format string, args ...interface{}) {
   331  	ec.begin, ec.end = begin, end
   332  	throwf(format, args...)
   333  }
   334  
   335  // SourceText evaluates a chunk of elvish source.
   336  func (ev *Evaler) SourceText(name, src string) error {
   337  	n, err := parse.Parse(name, src)
   338  	if err != nil {
   339  		return err
   340  	}
   341  	op, err := ev.Compile(n, name, src)
   342  	if err != nil {
   343  		return err
   344  	}
   345  	return ev.Eval(op, name, src)
   346  }
   347  
   348  func readFileUTF8(fname string) (string, error) {
   349  	bytes, err := ioutil.ReadFile(fname)
   350  	if err != nil {
   351  		return "", err
   352  	}
   353  	if !utf8.Valid(bytes) {
   354  		return "", fmt.Errorf("%s: source is not valid UTF-8", fname)
   355  	}
   356  	return string(bytes), nil
   357  }
   358  
   359  // Source evaluates the content of a file.
   360  func (ev *Evaler) Source(fname string) error {
   361  	src, err := readFileUTF8(fname)
   362  	if err != nil {
   363  		return err
   364  	}
   365  	return ev.SourceText(fname, src)
   366  }
   367  
   368  // ErrStoreUnconnected is thrown by ResolveVar when a shared: variable needs to
   369  // be resolved but the store is not connected.
   370  var ErrStoreUnconnected = errors.New("store unconnected")
   371  
   372  // ResolveVar resolves a variable. When the variable cannot be found, nil is
   373  // returned.
   374  func (ec *EvalCtx) ResolveVar(ns, name string) Variable {
   375  	switch ns {
   376  	case "local":
   377  		return ec.getLocal(name)
   378  	case "up":
   379  		return ec.up[name]
   380  	case "builtin":
   381  		return ec.Builtin[name]
   382  	case "":
   383  		if v := ec.getLocal(name); v != nil {
   384  			return v
   385  		}
   386  		if v, ok := ec.up[name]; ok {
   387  			return v
   388  		}
   389  		return ec.Builtin[name]
   390  	case "e":
   391  		if strings.HasPrefix(name, FnPrefix) {
   392  			return NewRoVariable(ExternalCmd{name[len(FnPrefix):]})
   393  		}
   394  	case "E":
   395  		return envVariable{name}
   396  	case "shared":
   397  		if ec.Daemon == nil {
   398  			throw(ErrStoreUnconnected)
   399  		}
   400  		return sharedVariable{ec.Daemon, name}
   401  	default:
   402  		if ns, ok := ec.Modules[ns]; ok {
   403  			return ns[name]
   404  		}
   405  	}
   406  	return nil
   407  }
   408  
   409  // getLocal finds the named local variable.
   410  func (ec *EvalCtx) getLocal(name string) Variable {
   411  	i, err := strconv.Atoi(name)
   412  	if err == nil {
   413  		// Logger.Println("positional variable", i)
   414  		// Logger.Printf("EvalCtx=%p, args=%v", ec, ec.positionals)
   415  		if i < 0 {
   416  			i += len(ec.positionals)
   417  		}
   418  		if i < 0 || i >= len(ec.positionals) {
   419  			// Logger.Print("out of range")
   420  			return nil
   421  		}
   422  		// Logger.Print("found")
   423  		return NewRoVariable(ec.positionals[i])
   424  	}
   425  	return ec.local[name]
   426  }
   427  
   428  var ErrMoreThanOneRest = errors.New("more than one @ lvalue")
   429  
   430  // IterateInputs calls the passed function for each input element.
   431  func (ec *EvalCtx) IterateInputs(f func(Value)) {
   432  	var w sync.WaitGroup
   433  	inputs := make(chan Value)
   434  
   435  	w.Add(2)
   436  	go func() {
   437  		linesToChan(ec.ports[0].File, inputs)
   438  		w.Done()
   439  	}()
   440  	go func() {
   441  		for v := range ec.ports[0].Chan {
   442  			inputs <- v
   443  		}
   444  		w.Done()
   445  	}()
   446  	go func() {
   447  		w.Wait()
   448  		close(inputs)
   449  	}()
   450  
   451  	for v := range inputs {
   452  		f(v)
   453  	}
   454  }
   455  
   456  func linesToChan(r io.Reader, ch chan<- Value) {
   457  	filein := bufio.NewReader(r)
   458  	for {
   459  		line, err := filein.ReadString('\n')
   460  		if line != "" {
   461  			ch <- String(strings.TrimSuffix(line, "\n"))
   462  		}
   463  		if err != nil {
   464  			if err != io.EOF {
   465  				logger.Println("error on reading:", err)
   466  			}
   467  			break
   468  		}
   469  	}
   470  }
   471  
   472  // OutputChan returns a channel onto which output can be written.
   473  func (ec *EvalCtx) OutputChan() chan<- Value {
   474  	return ec.ports[1].Chan
   475  }
   476  
   477  // OutputFile returns a file onto which output can be written.
   478  func (ec *EvalCtx) OutputFile() *os.File {
   479  	return ec.ports[1].File
   480  }