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 }