src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/builtin_fn_flow.go (about)

     1  package eval
     2  
     3  import (
     4  	"errors"
     5  	"math"
     6  	"math/big"
     7  	"sync"
     8  	"sync/atomic"
     9  
    10  	"golang.org/x/sync/semaphore"
    11  
    12  	"src.elv.sh/pkg/errutil"
    13  	"src.elv.sh/pkg/eval/errs"
    14  	"src.elv.sh/pkg/eval/vals"
    15  )
    16  
    17  // Flow control.
    18  
    19  // TODO(xiaq): Document "multi-error".
    20  
    21  func init() {
    22  	addBuiltinFns(map[string]any{
    23  		"run-parallel": runParallel,
    24  		// Exception and control
    25  		"fail":        fail,
    26  		"multi-error": multiErrorFn,
    27  		"return":      returnFn,
    28  		"break":       breakFn,
    29  		"continue":    continueFn,
    30  		"defer":       deferFn,
    31  		// Iterations.
    32  		"each":  each,
    33  		"peach": peach,
    34  	})
    35  }
    36  
    37  func runParallel(fm *Frame, functions ...Callable) error {
    38  	var wg sync.WaitGroup
    39  	wg.Add(len(functions))
    40  	exceptions := make([]Exception, len(functions))
    41  	for i, function := range functions {
    42  		go func(fm2 *Frame, function Callable, pexc *Exception) {
    43  			err := function.Call(fm2, NoArgs, NoOpts)
    44  			if err != nil {
    45  				*pexc = err.(Exception)
    46  			}
    47  			wg.Done()
    48  		}(fm.Fork("[run-parallel function]"), function, &exceptions[i])
    49  	}
    50  
    51  	wg.Wait()
    52  	return MakePipelineError(exceptions)
    53  }
    54  
    55  func each(fm *Frame, f Callable, inputs Inputs) error {
    56  	broken := false
    57  	var err error
    58  	inputs(func(v any) {
    59  		if broken {
    60  			return
    61  		}
    62  		newFm := fm.Fork("closure of each")
    63  		ex := f.Call(newFm, []any{v}, NoOpts)
    64  		newFm.Close()
    65  
    66  		if ex != nil {
    67  			switch Reason(ex) {
    68  			case nil, Continue:
    69  				// nop
    70  			case Break:
    71  				broken = true
    72  			default:
    73  				broken = true
    74  				err = ex
    75  			}
    76  		}
    77  	})
    78  	return err
    79  }
    80  
    81  type peachOpt struct{ NumWorkers vals.Num }
    82  
    83  func (o *peachOpt) SetDefaultOptions() { o.NumWorkers = math.Inf(1) }
    84  
    85  func peach(fm *Frame, opts peachOpt, f Callable, inputs Inputs) error {
    86  	var wg sync.WaitGroup
    87  	var broken int32
    88  	var errMu sync.Mutex
    89  	var err error
    90  
    91  	var workerSema *semaphore.Weighted
    92  	numWorkers, limited, err := parseNumWorkers(opts.NumWorkers)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	if limited {
    97  		workerSema = semaphore.NewWeighted(int64(numWorkers))
    98  	}
    99  
   100  	ctx := fm.Context()
   101  
   102  	inputs(func(v any) {
   103  		if atomic.LoadInt32(&broken) != 0 {
   104  			return
   105  		}
   106  		if workerSema != nil {
   107  			workerSema.Acquire(ctx, 1)
   108  		}
   109  		wg.Add(1)
   110  		go func() {
   111  			newFm := fm.Fork("closure of peach")
   112  			newFm.ports[0] = DummyInputPort
   113  			ex := f.Call(newFm, []any{v}, NoOpts)
   114  			newFm.Close()
   115  
   116  			if ex != nil {
   117  				switch Reason(ex) {
   118  				case nil, Continue:
   119  					// nop
   120  				case Break:
   121  					atomic.StoreInt32(&broken, 1)
   122  				default:
   123  					errMu.Lock()
   124  					err = errutil.Multi(err, ex)
   125  					defer errMu.Unlock()
   126  					atomic.StoreInt32(&broken, 1)
   127  				}
   128  			}
   129  			wg.Done()
   130  			if workerSema != nil {
   131  				workerSema.Release(1)
   132  			}
   133  		}()
   134  	})
   135  	wg.Wait()
   136  	return err
   137  }
   138  
   139  func parseNumWorkers(n vals.Num) (int, bool, error) {
   140  	switch n := n.(type) {
   141  	case int:
   142  		if n >= 1 {
   143  			return n, true, nil
   144  		}
   145  	case *big.Int:
   146  		// A limit larger than MaxInt is equivalent to no limit.
   147  		return 0, false, nil
   148  	case float64:
   149  		if math.IsInf(n, 1) {
   150  			return 0, false, nil
   151  		}
   152  	}
   153  	return 0, false, errs.BadValue{
   154  		What:   "peach &num-workers",
   155  		Valid:  "exact positive integer or +inf",
   156  		Actual: vals.ToString(n),
   157  	}
   158  }
   159  
   160  // FailError is an error returned by the "fail" command.
   161  type FailError struct{ Content any }
   162  
   163  var _ vals.PseudoMap = FailError{}
   164  
   165  // Error returns the string representation of the cause.
   166  func (e FailError) Error() string { return vals.ToString(e.Content) }
   167  
   168  // Kind returns "fail-error".
   169  func (FailError) Kind() string { return "fail-error" }
   170  
   171  // Fields returns a structmap for accessing fields from Elvish.
   172  func (e FailError) Fields() vals.StructMap { return failFields{e} }
   173  
   174  type failFields struct{ e FailError }
   175  
   176  func (failFields) IsStructMap() {}
   177  
   178  func (f failFields) Type() string { return "fail" }
   179  func (f failFields) Content() any { return f.e.Content }
   180  
   181  func fail(v any) error {
   182  	if e, ok := v.(error); ok {
   183  		// MAYBE TODO: if v is an exception, attach a "rethrown" stack trace,
   184  		// like Java
   185  		return e
   186  	}
   187  	return FailError{v}
   188  }
   189  
   190  func multiErrorFn(excs ...Exception) error {
   191  	return PipelineError{excs}
   192  }
   193  
   194  func returnFn() error {
   195  	return Return
   196  }
   197  
   198  func breakFn() error {
   199  	return Break
   200  }
   201  
   202  func continueFn() error {
   203  	return Continue
   204  }
   205  
   206  var errDeferNotInClosure = errors.New("defer must be called from within a closure")
   207  
   208  func deferFn(fm *Frame, fn Callable) error {
   209  	if fm.defers == nil {
   210  		return errDeferNotInClosure
   211  	}
   212  	deferTraceback := fm.traceback
   213  	fm.addDefer(func(fm *Frame) Exception {
   214  		err := fn.Call(fm, NoArgs, NoOpts)
   215  		if exc, ok := err.(Exception); ok {
   216  			return exc
   217  		}
   218  		return &exception{err, deferTraceback}
   219  	})
   220  	return nil
   221  }