github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/cmds/core/elvish/eval/eval.go (about)

     1  // Package eval handles evaluation of parsed Elvish code and provides runtime
     2  // facilities.
     3  package eval
     4  
     5  import (
     6  	"fmt"
     7  	"os"
     8  	"os/signal"
     9  	"strings"
    10  	"syscall"
    11  
    12  	"github.com/u-root/u-root/cmds/core/elvish/eval/bundled"
    13  	"github.com/u-root/u-root/cmds/core/elvish/eval/vals"
    14  	"github.com/u-root/u-root/cmds/core/elvish/eval/vars"
    15  	"github.com/u-root/u-root/cmds/core/elvish/hashmap"
    16  	"github.com/u-root/u-root/cmds/core/elvish/parse"
    17  	"github.com/u-root/u-root/cmds/core/elvish/sys"
    18  	"github.com/u-root/u-root/cmds/core/elvish/util"
    19  	"github.com/u-root/u-root/cmds/core/elvish/vector"
    20  )
    21  
    22  var logger = util.GetLogger("[eval] ")
    23  
    24  const (
    25  	// FnSuffix is the suffix for the variable names of functions. Defining a
    26  	// function "foo" is equivalent to setting a variable named "foo~", and vice
    27  	// versa.
    28  	FnSuffix = "~"
    29  	// NsSuffix is the suffix for the variable names of namespaces. Defining a
    30  	// namespace foo is equivalent to setting a variable named "foo:", and vice
    31  	// versa.
    32  	NsSuffix = ":"
    33  )
    34  
    35  const (
    36  	defaultValuePrefix = "▶ "
    37  	initIndent         = vals.NoPretty
    38  )
    39  
    40  // Evaler is used to evaluate elvish sources. It maintains runtime context
    41  // shared among all evalCtx instances.
    42  type Evaler struct {
    43  	evalerScopes
    44  	valuePrefix string
    45  	beforeChdir []func(string)
    46  	afterChdir  []func(string)
    47  	modules     map[string]Ns
    48  	// bundled modules
    49  	bundled map[string]string
    50  	Editor  Editor
    51  	libDir  string
    52  	intCh   chan struct{}
    53  }
    54  
    55  type evalerScopes struct {
    56  	Global  Ns
    57  	Builtin Ns
    58  }
    59  
    60  // NewEvaler creates a new Evaler.
    61  func NewEvaler() *Evaler {
    62  	builtin := builtinNs.Clone()
    63  
    64  	ev := &Evaler{
    65  		valuePrefix: defaultValuePrefix,
    66  		evalerScopes: evalerScopes{
    67  			Global:  make(Ns),
    68  			Builtin: builtin,
    69  		},
    70  		modules: map[string]Ns{
    71  			"builtin": builtin,
    72  		},
    73  		bundled: bundled.Get(),
    74  		Editor:  nil,
    75  		intCh:   nil,
    76  	}
    77  
    78  	beforeChdirElvish, afterChdirElvish := vector.Empty, vector.Empty
    79  	ev.beforeChdir = append(ev.beforeChdir,
    80  		adaptChdirHook("before-chdir", ev, &beforeChdirElvish))
    81  	ev.afterChdir = append(ev.afterChdir,
    82  		adaptChdirHook("after-chdir", ev, &afterChdirElvish))
    83  	builtin["before-chdir"] = vars.FromPtr(&beforeChdirElvish)
    84  	builtin["after-chdir"] = vars.FromPtr(&afterChdirElvish)
    85  
    86  	builtin["value-out-indicator"] = vars.FromPtr(&ev.valuePrefix)
    87  	builtin["pwd"] = PwdVariable{ev}
    88  
    89  	return ev
    90  }
    91  
    92  func adaptChdirHook(name string, ev *Evaler, pfns *vector.Vector) func(string) {
    93  	return func(path string) {
    94  		stdPorts := newStdPorts(os.Stdin, os.Stdout, os.Stderr, ev.valuePrefix)
    95  		defer stdPorts.close()
    96  		for it := (*pfns).Iterator(); it.HasElem(); it.Next() {
    97  			fn, ok := it.Elem().(Callable)
    98  			if !ok {
    99  				fmt.Fprintln(os.Stderr, name, "hook must be callable")
   100  				continue
   101  			}
   102  			fm := NewTopFrame(ev,
   103  				NewInternalSource("["+name+" hook]"), stdPorts.ports[:])
   104  			err := fm.Call(fn, []interface{}{path}, NoOpts)
   105  			if err != nil {
   106  				// TODO: Stack trace
   107  				fmt.Fprintln(os.Stderr, err)
   108  			}
   109  		}
   110  	}
   111  }
   112  
   113  // Close releases resources allocated when creating this Evaler. Currently this
   114  // does nothing and always returns a nil error.
   115  func (ev *Evaler) Close() error {
   116  	return nil
   117  }
   118  
   119  // AddBeforeChdir adds a function to run before changing directory.
   120  func (ev *Evaler) AddBeforeChdir(f func(string)) {
   121  	ev.beforeChdir = append(ev.beforeChdir, f)
   122  }
   123  
   124  // AddAfterChdir adds a function to run after changing directory.
   125  func (ev *Evaler) AddAfterChdir(f func(string)) {
   126  	ev.afterChdir = append(ev.afterChdir, f)
   127  }
   128  
   129  // InstallModule installs a module to the Evaler so that it can be used with
   130  // "use $name" from script.
   131  func (ev *Evaler) InstallModule(name string, mod Ns) {
   132  	ev.modules[name] = mod
   133  }
   134  
   135  // InstallBundled installs a bundled module to the Evaler.
   136  func (ev *Evaler) InstallBundled(name, src string) {
   137  	ev.bundled[name] = src
   138  }
   139  
   140  // SetArgs sets the $args builtin variable.
   141  func (ev *Evaler) SetArgs(args []string) {
   142  	v := vector.Empty
   143  	for _, arg := range args {
   144  		v = v.Cons(arg)
   145  	}
   146  	ev.Builtin["args"] = vars.NewRo(v)
   147  }
   148  
   149  // SetLibDir sets the library directory, in which external modules are to be
   150  // found.
   151  func (ev *Evaler) SetLibDir(libDir string) {
   152  	ev.libDir = libDir
   153  }
   154  
   155  func searchPaths() []string {
   156  	return strings.Split(os.Getenv("PATH"), ":")
   157  }
   158  
   159  // growPorts makes the size of ec.ports at least n, adding nil's if necessary.
   160  func (fm *Frame) growPorts(n int) {
   161  	if len(fm.ports) >= n {
   162  		return
   163  	}
   164  	ports := fm.ports
   165  	fm.ports = make([]*Port, n)
   166  	copy(fm.ports, ports)
   167  }
   168  
   169  // EvalWithStdPorts sets up the Evaler with standard ports and evaluates an Op.
   170  // The supplied name and text are used in diagnostic messages.
   171  func (ev *Evaler) EvalWithStdPorts(op Op, src *Source) error {
   172  	stdPorts := newStdPorts(os.Stdin, os.Stdout, os.Stderr, ev.valuePrefix)
   173  	defer stdPorts.close()
   174  	return ev.Eval(op, stdPorts.ports[:], src)
   175  }
   176  
   177  // Eval sets up the Evaler with the given ports and evaluates an Op.
   178  // The supplied name and text are used in diagnostic messages.
   179  func (ev *Evaler) Eval(op Op, ports []*Port, src *Source) error {
   180  	// Ignore TTOU.
   181  	//
   182  	// When a subprocess in its own process group puts itself in the foreground,
   183  	// Elvish will be put in the background. When the code finishes execution,
   184  	// Elvish will attempt to move itself back to the foreground by calling
   185  	// tcsetpgrp. However, whenever a background process calls tcsetpgrp (or
   186  	// otherwise attempts to modify the terminal configuration), TTOU will be
   187  	// sent, whose default handler is to stop the process. Or, if the process
   188  	// lives in an orphaned process group (which is often the case for Elvish),
   189  	// the call will outright fail. Therefore, for Elvish to be able to move
   190  	// itself back to the foreground later, we need to ignore TTOU now.
   191  	ignoreTTOU()
   192  	defer unignoreTTOU()
   193  
   194  	stopSigGoroutine := make(chan struct{})
   195  	sigGoRoutineDone := make(chan struct{})
   196  	// Set up intCh.
   197  	ev.intCh = make(chan struct{})
   198  	sigCh := make(chan os.Signal)
   199  	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGQUIT)
   200  	go func() {
   201  		closedIntCh := false
   202  	loop:
   203  		for {
   204  			select {
   205  			case <-sigCh:
   206  				if !closedIntCh {
   207  					close(ev.intCh)
   208  					closedIntCh = true
   209  				}
   210  			case <-stopSigGoroutine:
   211  				break loop
   212  			}
   213  		}
   214  		ev.intCh = nil
   215  		signal.Stop(sigCh)
   216  		close(sigGoRoutineDone)
   217  	}()
   218  
   219  	err := ev.eval(op, ports, src)
   220  
   221  	close(stopSigGoroutine)
   222  	<-sigGoRoutineDone
   223  
   224  	// Put myself in foreground, in case some command has put me in background.
   225  	// XXX Should probably use fd of /dev/tty instead of 0.
   226  	if sys.IsATTY(os.Stdin) {
   227  		err := putSelfInFg()
   228  		if err != nil {
   229  			fmt.Println("failed to put myself in foreground:", err)
   230  		}
   231  	}
   232  
   233  	return err
   234  }
   235  
   236  // eval evaluates a chunk node n. The supplied name and text are used in
   237  // diagnostic messages.
   238  func (ev *Evaler) eval(op Op, ports []*Port, src *Source) error {
   239  	ec := NewTopFrame(ev, src, ports)
   240  	return ec.Eval(op)
   241  }
   242  
   243  // Compile compiles Elvish code in the global scope. If the error is not nil, it
   244  // always has type CompilationError.
   245  func (ev *Evaler) Compile(n *parse.Chunk, src *Source) (Op, error) {
   246  	return compile(ev.Builtin.static(), ev.Global.static(), n, src)
   247  }
   248  
   249  // EvalSource evaluates a chunk of Elvish source.
   250  func (ev *Evaler) EvalSource(src *Source) error {
   251  	n, err := parse.Parse(src.name, src.code)
   252  	if err != nil {
   253  		return err
   254  	}
   255  	op, err := ev.Compile(n, src)
   256  	if err != nil {
   257  		return err
   258  	}
   259  	return ev.EvalWithStdPorts(op, src)
   260  }
   261  
   262  // SourceRC evaluates a rc.elv file. It evaluates the file in the global
   263  // namespace. If the file defines a $-exports- map variable, the variable is
   264  // removed and its content is poured into the global namespace, not overriding
   265  // existing variables.
   266  func (ev *Evaler) SourceRC(src *Source) error {
   267  	n, err := parse.Parse(src.name, src.code)
   268  	if err != nil {
   269  		return err
   270  	}
   271  	op, err := ev.Compile(n, src)
   272  	if err != nil {
   273  		return err
   274  	}
   275  	errEval := ev.EvalWithStdPorts(op, src)
   276  	var errExports error
   277  	if ev.Global.HasName("-exports-") {
   278  		exports := ev.Global.PopName("-exports-").Get()
   279  		switch exports := exports.(type) {
   280  		case hashmap.Map:
   281  			for it := exports.Iterator(); it.HasElem(); it.Next() {
   282  				k, v := it.Elem()
   283  				if name, ok := k.(string); ok {
   284  					if !ev.Global.HasName(name) {
   285  						ev.Global.Add(name, vars.NewAnyWithInit(v))
   286  					}
   287  				} else {
   288  					errKey := fmt.Errorf("non-string key in $-exports-: %s",
   289  						vals.Repr(k, vals.NoPretty))
   290  					errExports = util.Errors(errExports, errKey)
   291  				}
   292  			}
   293  		default:
   294  			errExports = fmt.Errorf(
   295  				"$-exports- should be a map, got %s", vals.Kind(exports))
   296  		}
   297  	}
   298  	return util.Errors(errEval, errExports)
   299  }