github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/compile_effect.go (about)

     1  package eval
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"sync"
     7  
     8  	"src.elv.sh/pkg/diag"
     9  	"src.elv.sh/pkg/eval/errs"
    10  	"src.elv.sh/pkg/eval/vals"
    11  	"src.elv.sh/pkg/eval/vars"
    12  	"src.elv.sh/pkg/fsutil"
    13  	"src.elv.sh/pkg/parse"
    14  	"src.elv.sh/pkg/parse/cmpd"
    15  )
    16  
    17  // An operation with some side effects.
    18  type effectOp interface{ exec(*Frame) Exception }
    19  
    20  func (cp *compiler) chunkOp(n *parse.Chunk) effectOp {
    21  	return chunkOp{n.Range(), cp.pipelineOps(n.Pipelines)}
    22  }
    23  
    24  type chunkOp struct {
    25  	diag.Ranging
    26  	subops []effectOp
    27  }
    28  
    29  func (op chunkOp) exec(fm *Frame) Exception {
    30  	for _, subop := range op.subops {
    31  		exc := subop.exec(fm)
    32  		if exc != nil {
    33  			return exc
    34  		}
    35  	}
    36  	// Check for interrupts after the chunk.
    37  	// We also check for interrupts before each pipeline, so there is no
    38  	// need to check it before the chunk or after each pipeline.
    39  	if fm.IsInterrupted() {
    40  		return fm.errorp(op, ErrInterrupted)
    41  	}
    42  	return nil
    43  }
    44  
    45  func (cp *compiler) pipelineOp(n *parse.Pipeline) effectOp {
    46  	formOps := cp.formOps(n.Forms)
    47  
    48  	return &pipelineOp{n.Range(), n.Background, parse.SourceText(n), formOps}
    49  }
    50  
    51  func (cp *compiler) pipelineOps(ns []*parse.Pipeline) []effectOp {
    52  	ops := make([]effectOp, len(ns))
    53  	for i, n := range ns {
    54  		ops[i] = cp.pipelineOp(n)
    55  	}
    56  	return ops
    57  }
    58  
    59  type pipelineOp struct {
    60  	diag.Ranging
    61  	bg     bool
    62  	source string
    63  	subops []effectOp
    64  }
    65  
    66  const pipelineChanBufferSize = 32
    67  
    68  func (op *pipelineOp) exec(fm *Frame) Exception {
    69  	if fm.IsInterrupted() {
    70  		return fm.errorp(op, ErrInterrupted)
    71  	}
    72  
    73  	if op.bg {
    74  		fm = fm.fork("background job" + op.source)
    75  		fm.intCh = nil
    76  		fm.background = true
    77  		fm.Evaler.addNumBgJobs(1)
    78  	}
    79  
    80  	nforms := len(op.subops)
    81  
    82  	var wg sync.WaitGroup
    83  	wg.Add(nforms)
    84  	excs := make([]Exception, nforms)
    85  
    86  	var nextIn *Port
    87  
    88  	// For each form, create a dedicated evalCtx and run asynchronously
    89  	for i, formOp := range op.subops {
    90  		hasChanInput := i > 0
    91  		newFm := fm.fork("[form op]")
    92  		if i > 0 {
    93  			newFm.ports[0] = nextIn
    94  		}
    95  		if i < nforms-1 {
    96  			// Each internal port pair consists of a (byte) pipe pair and a
    97  			// channel.
    98  			// os.Pipe sets O_CLOEXEC, which is what we want.
    99  			reader, writer, e := os.Pipe()
   100  			if e != nil {
   101  				return fm.errorpf(op, "failed to create pipe: %s", e)
   102  			}
   103  			ch := make(chan interface{}, pipelineChanBufferSize)
   104  			newFm.ports[1] = &Port{
   105  				File: writer, Chan: ch, closeFile: true, closeChan: true}
   106  			nextIn = &Port{
   107  				File: reader, Chan: ch, closeFile: true, closeChan: false}
   108  		}
   109  		thisOp := formOp
   110  		thisExc := &excs[i]
   111  		go func() {
   112  			exc := thisOp.exec(newFm)
   113  			newFm.Close()
   114  			if exc != nil {
   115  				*thisExc = exc
   116  			}
   117  			wg.Done()
   118  			if hasChanInput {
   119  				// If the command has channel input, drain it. This
   120  				// mitigates the effect of erroneous pipelines like
   121  				// "range 100 | cat"; without draining the pipeline will
   122  				// lock up.
   123  				for range newFm.InputChan() {
   124  				}
   125  			}
   126  		}()
   127  	}
   128  
   129  	if op.bg {
   130  		// Background job, wait for form termination asynchronously.
   131  		go func() {
   132  			wg.Wait()
   133  			fm.Evaler.addNumBgJobs(-1)
   134  			msg := "job " + op.source + " finished"
   135  			err := MakePipelineError(excs)
   136  			if err != nil {
   137  				msg += ", errors = " + err.Error()
   138  			}
   139  			if fm.Evaler.getNotifyBgJobSuccess() || err != nil {
   140  				fm.ErrorFile().WriteString(msg + "\n")
   141  			}
   142  		}()
   143  		return nil
   144  	}
   145  	wg.Wait()
   146  	return fm.errorp(op, MakePipelineError(excs))
   147  }
   148  
   149  func (cp *compiler) formOp(n *parse.Form) effectOp {
   150  	var tempLValues []lvalue
   151  	var assignmentOps []effectOp
   152  	if len(n.Assignments) > 0 {
   153  		assignmentOps = cp.assignmentOps(n.Assignments)
   154  		if n.Head == nil {
   155  			cp.errorpf(n, `using the syntax of temporary assignment for non-temporary assignment is no longer supported; use "var" or "set" instead`)
   156  			return nopOp{}
   157  		}
   158  		for _, a := range n.Assignments {
   159  			lvalues := cp.parseIndexingLValue(a.Left)
   160  			tempLValues = append(tempLValues, lvalues.lvalues...)
   161  		}
   162  		logger.Println("temporary assignment of", len(n.Assignments), "pairs")
   163  	}
   164  
   165  	redirOps := cp.redirOps(n.Redirs)
   166  	body := cp.formBody(n)
   167  
   168  	return &formOp{n.Range(), tempLValues, assignmentOps, redirOps, body}
   169  }
   170  
   171  func (cp *compiler) formBody(n *parse.Form) formBody {
   172  	if n.Head == nil {
   173  		// Compiling an incomplete form node, return an empty body.
   174  		return formBody{}
   175  	}
   176  
   177  	// Determine if this form is a special command.
   178  	if head, ok := cmpd.StringLiteral(n.Head); ok {
   179  		special, _ := resolveCmdHeadInternally(cp, head, n.Head)
   180  		if special != nil {
   181  			specialOp := special(cp, n)
   182  			return formBody{specialOp: specialOp}
   183  		}
   184  	}
   185  
   186  	// Determine if the form is a legacy assignment form, by looking for an
   187  	// argument whose source is a literal "=".
   188  	for i, arg := range n.Args {
   189  		if parse.SourceText(arg) == "=" {
   190  			lhsNodes := make([]*parse.Compound, i+1)
   191  			lhsNodes[0] = n.Head
   192  			copy(lhsNodes[1:], n.Args[:i])
   193  			lhs := cp.parseCompoundLValues(lhsNodes)
   194  
   195  			rhsOps := cp.compoundOps(n.Args[i+1:])
   196  			var rhsRange diag.Ranging
   197  			if len(rhsOps) > 0 {
   198  				rhsRange = diag.MixedRanging(rhsOps[0], rhsOps[len(rhsOps)-1])
   199  			} else {
   200  				rhsRange = diag.PointRanging(n.Range().To)
   201  			}
   202  			rhs := seqValuesOp{rhsRange, rhsOps}
   203  
   204  			return formBody{assignOp: &assignOp{n.Range(), lhs, rhs}}
   205  		}
   206  	}
   207  
   208  	var headOp valuesOp
   209  	if head, ok := cmpd.StringLiteral(n.Head); ok {
   210  		// Head is a literal string: resolve to function or external (special
   211  		// commands are already handled above).
   212  		if _, fnRef := resolveCmdHeadInternally(cp, head, n.Head); fnRef != nil {
   213  			headOp = variableOp{n.Head.Range(), false, head + FnSuffix, fnRef}
   214  		} else {
   215  			headOp = literalValues(n.Head, NewExternalCmd(head))
   216  		}
   217  	} else {
   218  		// Head is not a literal string: evaluate as a normal expression.
   219  		headOp = cp.compoundOp(n.Head)
   220  	}
   221  
   222  	argOps := cp.compoundOps(n.Args)
   223  	optsOp := cp.mapPairs(n.Opts)
   224  	return formBody{ordinaryCmd: ordinaryCmd{headOp, argOps, optsOp}}
   225  }
   226  
   227  func (cp *compiler) formOps(ns []*parse.Form) []effectOp {
   228  	ops := make([]effectOp, len(ns))
   229  	for i, n := range ns {
   230  		ops[i] = cp.formOp(n)
   231  	}
   232  	return ops
   233  }
   234  
   235  type formOp struct {
   236  	diag.Ranging
   237  	tempLValues   []lvalue
   238  	tempAssignOps []effectOp
   239  	redirOps      []effectOp
   240  	body          formBody
   241  }
   242  
   243  type formBody struct {
   244  	// Exactly one field will be populated.
   245  	specialOp   effectOp
   246  	assignOp    effectOp
   247  	ordinaryCmd ordinaryCmd
   248  }
   249  
   250  type ordinaryCmd struct {
   251  	headOp valuesOp
   252  	argOps []valuesOp
   253  	optsOp *mapPairsOp
   254  }
   255  
   256  func (op *formOp) exec(fm *Frame) (errRet Exception) {
   257  	// fm here is always a sub-frame created in compiler.pipeline, so it can
   258  	// be safely modified.
   259  
   260  	// Temporary assignment.
   261  	if len(op.tempLValues) > 0 {
   262  		// There is a temporary assignment.
   263  		// Save variables.
   264  		var saveVars []vars.Var
   265  		var saveVals []interface{}
   266  		for _, lv := range op.tempLValues {
   267  			variable, err := derefLValue(fm, lv)
   268  			if err != nil {
   269  				return fm.errorp(op, err)
   270  			}
   271  			saveVars = append(saveVars, variable)
   272  		}
   273  		for i, v := range saveVars {
   274  			// TODO(xiaq): If the variable to save is a elemVariable, save
   275  			// the outermost variable instead.
   276  			if u := vars.HeadOfElement(v); u != nil {
   277  				v = u
   278  				saveVars[i] = v
   279  			}
   280  			val := v.Get()
   281  			saveVals = append(saveVals, val)
   282  			logger.Printf("saved %s = %s", v, val)
   283  		}
   284  		// Do assignment.
   285  		for _, subop := range op.tempAssignOps {
   286  			exc := subop.exec(fm)
   287  			if exc != nil {
   288  				return exc
   289  			}
   290  		}
   291  		// Defer variable restoration. Will be executed even if an error
   292  		// occurs when evaling other part of the form.
   293  		defer func() {
   294  			for i, v := range saveVars {
   295  				val := saveVals[i]
   296  				if val == nil {
   297  					// TODO(xiaq): Old value is nonexistent. We should delete
   298  					// the variable. However, since the compiler now doesn't
   299  					// delete it, we don't delete it in the evaler either.
   300  					val = ""
   301  				}
   302  				err := v.Set(val)
   303  				if err != nil {
   304  					errRet = fm.errorp(op, err)
   305  				}
   306  				logger.Printf("restored %s = %s", v, val)
   307  			}
   308  		}()
   309  	}
   310  
   311  	// Redirections.
   312  	for _, redirOp := range op.redirOps {
   313  		exc := redirOp.exec(fm)
   314  		if exc != nil {
   315  			return exc
   316  		}
   317  	}
   318  
   319  	if op.body.specialOp != nil {
   320  		return op.body.specialOp.exec(fm)
   321  	}
   322  	if op.body.assignOp != nil {
   323  		return op.body.assignOp.exec(fm)
   324  	}
   325  
   326  	// Ordinary command: evaluate head, arguments and options.
   327  	cmd := op.body.ordinaryCmd
   328  
   329  	// Special case: evaluating an incomplete form node. Return directly.
   330  	if cmd.headOp == nil {
   331  		return nil
   332  	}
   333  
   334  	headFn, err := evalForCommand(fm, cmd.headOp, "command")
   335  	if err != nil {
   336  		return fm.errorp(cmd.headOp, err)
   337  	}
   338  
   339  	var args []interface{}
   340  	for _, argOp := range cmd.argOps {
   341  		moreArgs, exc := argOp.exec(fm)
   342  		if exc != nil {
   343  			return exc
   344  		}
   345  		args = append(args, moreArgs...)
   346  	}
   347  
   348  	// TODO(xiaq): This conversion should be avoided.
   349  	convertedOpts := make(map[string]interface{})
   350  	exc := cmd.optsOp.exec(fm, func(k, v interface{}) Exception {
   351  		if ks, ok := k.(string); ok {
   352  			convertedOpts[ks] = v
   353  			return nil
   354  		}
   355  		// TODO(xiaq): Point to the particular key.
   356  		return fm.errorp(op, errs.BadValue{
   357  			What: "option key", Valid: "string", Actual: vals.Kind(k)})
   358  	})
   359  	if exc != nil {
   360  		return exc
   361  	}
   362  
   363  	fm.traceback = fm.addTraceback(op)
   364  	err = headFn.Call(fm, args, convertedOpts)
   365  	if exc, ok := err.(Exception); ok {
   366  		return exc
   367  	}
   368  	return &exception{err, fm.traceback}
   369  }
   370  
   371  func evalForCommand(fm *Frame, op valuesOp, what string) (Callable, error) {
   372  	value, err := evalForValue(fm, op, what)
   373  	if err != nil {
   374  		return nil, err
   375  	}
   376  	switch value := value.(type) {
   377  	case Callable:
   378  		return value, nil
   379  	case string:
   380  		if fsutil.DontSearch(value) {
   381  			return NewExternalCmd(value), nil
   382  		}
   383  	}
   384  	return nil, fm.errorp(op, errs.BadValue{
   385  		What:   what,
   386  		Valid:  "callable or string containing slash",
   387  		Actual: vals.Kind(value)})
   388  }
   389  
   390  func allTrue(vs []interface{}) bool {
   391  	for _, v := range vs {
   392  		if !vals.Bool(v) {
   393  			return false
   394  		}
   395  	}
   396  	return true
   397  }
   398  
   399  func (cp *compiler) assignmentOp(n *parse.Assignment) effectOp {
   400  	lhs := cp.parseIndexingLValue(n.Left)
   401  	rhs := cp.compoundOp(n.Right)
   402  	return &assignOp{n.Range(), lhs, rhs}
   403  }
   404  
   405  func (cp *compiler) assignmentOps(ns []*parse.Assignment) []effectOp {
   406  	ops := make([]effectOp, len(ns))
   407  	for i, n := range ns {
   408  		ops[i] = cp.assignmentOp(n)
   409  	}
   410  	return ops
   411  }
   412  
   413  const defaultFileRedirPerm = 0644
   414  
   415  // redir compiles a Redir into a op.
   416  func (cp *compiler) redirOp(n *parse.Redir) effectOp {
   417  	var dstOp valuesOp
   418  	if n.Left != nil {
   419  		dstOp = cp.compoundOp(n.Left)
   420  	}
   421  	flag := makeFlag(n.Mode)
   422  	if flag == -1 {
   423  		// TODO: Record and get redirection sign position
   424  		cp.errorpf(n, "bad redirection sign")
   425  	}
   426  	return &redirOp{n.Range(), dstOp, cp.compoundOp(n.Right), n.RightIsFd, n.Mode, flag}
   427  }
   428  
   429  func (cp *compiler) redirOps(ns []*parse.Redir) []effectOp {
   430  	ops := make([]effectOp, len(ns))
   431  	for i, n := range ns {
   432  		ops[i] = cp.redirOp(n)
   433  	}
   434  	return ops
   435  }
   436  
   437  func makeFlag(m parse.RedirMode) int {
   438  	switch m {
   439  	case parse.Read:
   440  		return os.O_RDONLY
   441  	case parse.Write:
   442  		return os.O_WRONLY | os.O_CREATE | os.O_TRUNC
   443  	case parse.ReadWrite:
   444  		return os.O_RDWR | os.O_CREATE
   445  	case parse.Append:
   446  		return os.O_WRONLY | os.O_CREATE | os.O_APPEND
   447  	default:
   448  		return -1
   449  	}
   450  }
   451  
   452  type redirOp struct {
   453  	diag.Ranging
   454  	dstOp   valuesOp
   455  	srcOp   valuesOp
   456  	srcIsFd bool
   457  	mode    parse.RedirMode
   458  	flag    int
   459  }
   460  
   461  type invalidFD struct{ fd int }
   462  
   463  func (err invalidFD) Error() string { return fmt.Sprintf("invalid fd: %d", err.fd) }
   464  
   465  // Returns a suitable dummy value for the channel part of the port when
   466  // redirecting from or to a file, so that the read and write attempts fail
   467  // silently (instead of blocking or panicking).
   468  //
   469  // TODO: Instead of letting read and write attempts fail silently, consider
   470  // raising an exception instead.
   471  func chanForFileRedir(mode parse.RedirMode) chan interface{} {
   472  	if mode == parse.Read {
   473  		// ClosedChan produces no values when reading.
   474  		return ClosedChan
   475  	}
   476  	// BlackholeChan discards all values written to it.
   477  	return BlackholeChan
   478  }
   479  
   480  func (op *redirOp) exec(fm *Frame) Exception {
   481  	var dst int
   482  	if op.dstOp == nil {
   483  		// No explicit FD destination specified; use default destinations
   484  		switch op.mode {
   485  		case parse.Read:
   486  			dst = 0
   487  		case parse.Write, parse.ReadWrite, parse.Append:
   488  			dst = 1
   489  		default:
   490  			return fm.errorpf(op, "bad RedirMode; parser bug")
   491  		}
   492  	} else {
   493  		// An explicit FD destination specified, evaluate it.
   494  		var err error
   495  		dst, err = evalForFd(fm, op.dstOp, false, "redirection destination")
   496  		if err != nil {
   497  			return fm.errorp(op, err)
   498  		}
   499  	}
   500  
   501  	growPorts(&fm.ports, dst+1)
   502  	fm.ports[dst].close()
   503  
   504  	if op.srcIsFd {
   505  		src, err := evalForFd(fm, op.srcOp, true, "redirection source")
   506  		if err != nil {
   507  			return fm.errorp(op, err)
   508  		}
   509  		switch {
   510  		case src == -1:
   511  			// close
   512  			fm.ports[dst] = &Port{}
   513  		case src >= len(fm.ports) || fm.ports[src] == nil:
   514  			return fm.errorp(op, invalidFD{src})
   515  		default:
   516  			fm.ports[dst] = fm.ports[src].fork()
   517  		}
   518  		return nil
   519  	}
   520  	src, err := evalForValue(fm, op.srcOp, "redirection source")
   521  	if err != nil {
   522  		return fm.errorp(op, err)
   523  	}
   524  	switch src := src.(type) {
   525  	case string:
   526  		f, err := os.OpenFile(src, op.flag, defaultFileRedirPerm)
   527  		if err != nil {
   528  			return fm.errorpf(op, "failed to open file %s: %s", vals.Repr(src, vals.NoPretty), err)
   529  		}
   530  		fm.ports[dst] = &Port{File: f, closeFile: true, Chan: chanForFileRedir(op.mode)}
   531  	case vals.File:
   532  		fm.ports[dst] = &Port{File: src, closeFile: false, Chan: chanForFileRedir(op.mode)}
   533  	case vals.Pipe:
   534  		var f *os.File
   535  		switch op.mode {
   536  		case parse.Read:
   537  			f = src.ReadEnd
   538  		case parse.Write:
   539  			f = src.WriteEnd
   540  		default:
   541  			return fm.errorpf(op, "can only use < or > with pipes")
   542  		}
   543  		fm.ports[dst] = &Port{File: f, closeFile: false, Chan: chanForFileRedir(op.mode)}
   544  	default:
   545  		return fm.errorp(op.srcOp, errs.BadValue{
   546  			What:  "redirection source",
   547  			Valid: "string, file or pipe", Actual: vals.Kind(src)})
   548  	}
   549  	return nil
   550  }
   551  
   552  // Makes the size of *ports at least n, adding nil's if necessary.
   553  func growPorts(ports *[]*Port, n int) {
   554  	if len(*ports) >= n {
   555  		return
   556  	}
   557  	oldPorts := *ports
   558  	*ports = make([]*Port, n)
   559  	copy(*ports, oldPorts)
   560  }
   561  
   562  func evalForFd(fm *Frame, op valuesOp, closeOK bool, what string) (int, error) {
   563  	value, err := evalForValue(fm, op, what)
   564  	if err != nil {
   565  		return -1, err
   566  	}
   567  	switch value {
   568  	case "stdin":
   569  		return 0, nil
   570  	case "stdout":
   571  		return 1, nil
   572  	case "stderr":
   573  		return 2, nil
   574  	}
   575  	var fd int
   576  	if vals.ScanToGo(value, &fd) == nil {
   577  		return fd, nil
   578  	} else if value == "-" && closeOK {
   579  		return -1, nil
   580  	}
   581  	valid := "fd name or number"
   582  	if closeOK {
   583  		valid = "fd name or number or '-'"
   584  	}
   585  	return -1, fm.errorp(op, errs.BadValue{
   586  		What: what, Valid: valid, Actual: vals.Repr(value, vals.NoPretty)})
   587  }
   588  
   589  type seqOp struct{ subops []effectOp }
   590  
   591  func (op seqOp) exec(fm *Frame) Exception {
   592  	for _, subop := range op.subops {
   593  		exc := subop.exec(fm)
   594  		if exc != nil {
   595  			return exc
   596  		}
   597  	}
   598  	return nil
   599  }
   600  
   601  type nopOp struct{}
   602  
   603  func (nopOp) exec(fm *Frame) Exception { return nil }