github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/compile_effect.go (about)

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