github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/builtin_fn_flow.go (about) 1 package eval 2 3 import ( 4 "sync" 5 "sync/atomic" 6 7 "src.elv.sh/pkg/diag" 8 "src.elv.sh/pkg/eval/vals" 9 ) 10 11 // Flow control. 12 13 // TODO(xiaq): Document "multi-error". 14 15 func init() { 16 addBuiltinFns(map[string]interface{}{ 17 "run-parallel": runParallel, 18 // Exception and control 19 "fail": fail, 20 "multi-error": multiErrorFn, 21 "return": returnFn, 22 "break": breakFn, 23 "continue": continueFn, 24 // Iterations. 25 "each": each, 26 "peach": peach, 27 }) 28 } 29 30 //elvdoc:fn run-parallel 31 // 32 // ```elvish 33 // run-parallel $callable ... 34 // ``` 35 // 36 // Run several callables in parallel, and wait for all of them to finish. 37 // 38 // If one or more callables throw exceptions, the other callables continue running, 39 // and a composite exception is thrown when all callables finish execution. 40 // 41 // The behavior of `run-parallel` is consistent with the behavior of pipelines, 42 // except that it does not perform any redirections. 43 // 44 // Here is an example that lets you pipe the stdout and stderr of a command to two 45 // different commands: 46 // 47 // ```elvish 48 // pout = (pipe) 49 // perr = (pipe) 50 // run-parallel { 51 // foo > $pout 2> $perr 52 // pwclose $pout 53 // pwclose $perr 54 // } { 55 // bar < $pout 56 // prclose $pout 57 // } { 58 // bar2 < $perr 59 // prclose $perr 60 // } 61 // ``` 62 // 63 // This command is intended for doing a fixed number of heterogeneous things in 64 // parallel. If you need homogeneous parallel processing of possibly unbound data, 65 // use `peach` instead. 66 // 67 // @cf peach 68 69 func runParallel(fm *Frame, functions ...Callable) error { 70 var wg sync.WaitGroup 71 wg.Add(len(functions)) 72 exceptions := make([]Exception, len(functions)) 73 for i, function := range functions { 74 go func(fm2 *Frame, function Callable, pexc *Exception) { 75 err := function.Call(fm2, NoArgs, NoOpts) 76 if err != nil { 77 *pexc = err.(Exception) 78 } 79 wg.Done() 80 }(fm.fork("[run-parallel function]"), function, &exceptions[i]) 81 } 82 83 wg.Wait() 84 return MakePipelineError(exceptions) 85 } 86 87 //elvdoc:fn each 88 // 89 // ```elvish 90 // each $f $input-list? 91 // ``` 92 // 93 // Call `$f` on all inputs. 94 // 95 // An exception raised from [`break`](#break) is caught by `each`, and will 96 // cause it to terminate early. 97 // 98 // An exception raised from [`continue`](#continue) is swallowed and can be used 99 // to terminate a single iteration early. 100 // 101 // Examples: 102 // 103 // ```elvish-transcript 104 // ~> range 5 8 | each [x]{ * $x $x } 105 // ▶ 25 106 // ▶ 36 107 // ▶ 49 108 // ~> each [x]{ put $x[:3] } [lorem ipsum] 109 // ▶ lor 110 // ▶ ips 111 // ``` 112 // 113 // @cf peach 114 // 115 // Etymology: Various languages, as `for each`. Happens to have the same name as 116 // the iteration construct of 117 // [Factor](http://docs.factorcode.org/content/word-each,sequences.html). 118 119 func each(fm *Frame, f Callable, inputs Inputs) error { 120 broken := false 121 var err error 122 inputs(func(v interface{}) { 123 if broken { 124 return 125 } 126 newFm := fm.fork("closure of each") 127 ex := f.Call(newFm, []interface{}{v}, NoOpts) 128 newFm.Close() 129 130 if ex != nil { 131 switch Reason(ex) { 132 case nil, Continue: 133 // nop 134 case Break: 135 broken = true 136 default: 137 broken = true 138 err = ex 139 } 140 } 141 }) 142 return err 143 } 144 145 //elvdoc:fn peach 146 // 147 // ```elvish 148 // peach $f $input-list? 149 // ``` 150 // 151 // Calls `$f` on all inputs, possibly in parallel. 152 // 153 // Like `each`, an exception raised from [`break`](#break) will cause `peach` 154 // to terminate early. However due to the parallel nature of `peach`, the exact 155 // time of termination is non-deterministic and not even guranteed. 156 // 157 // An exception raised from [`continue`](#continue) is swallowed and can be used 158 // to terminate a single iteration early. 159 // 160 // Example (your output will differ): 161 // 162 // ```elvish-transcript 163 // ~> range 1 10 | peach [x]{ + $x 10 } 164 // ▶ (num 12) 165 // ▶ (num 13) 166 // ▶ (num 11) 167 // ▶ (num 16) 168 // ▶ (num 18) 169 // ▶ (num 14) 170 // ▶ (num 17) 171 // ▶ (num 15) 172 // ▶ (num 19) 173 // ~> range 1 101 | 174 // peach [x]{ if (== 50 $x) { break } else { put $x } } | 175 // + (all) # 1+...+49 = 1225; 1+...+100 = 5050 176 // ▶ (num 1328) 177 // ``` 178 // 179 // This command is intended for homogeneous processing of possibly unbound data. If 180 // you need to do a fixed number of heterogeneous things in parallel, use 181 // `run-parallel`. 182 // 183 // @cf each run-parallel 184 185 func peach(fm *Frame, f Callable, inputs Inputs) error { 186 var wg sync.WaitGroup 187 var broken int32 188 var errMu sync.Mutex 189 var err error 190 191 inputs(func(v interface{}) { 192 if atomic.LoadInt32(&broken) != 0 { 193 return 194 } 195 wg.Add(1) 196 go func() { 197 newFm := fm.fork("closure of peach") 198 newFm.ports[0] = DummyInputPort 199 ex := f.Call(newFm, []interface{}{v}, NoOpts) 200 newFm.Close() 201 202 if ex != nil { 203 switch Reason(ex) { 204 case nil, Continue: 205 // nop 206 case Break: 207 atomic.StoreInt32(&broken, 1) 208 default: 209 errMu.Lock() 210 err = diag.Errors(err, ex) 211 defer errMu.Unlock() 212 atomic.StoreInt32(&broken, 1) 213 } 214 } 215 wg.Done() 216 }() 217 }) 218 wg.Wait() 219 return err 220 } 221 222 // FailError is an error returned by the "fail" command. 223 type FailError struct{ Content interface{} } 224 225 // Error returns the string representation of the cause. 226 func (e FailError) Error() string { return vals.ToString(e.Content) } 227 228 // Fields returns a structmap for accessing fields from Elvish. 229 func (e FailError) Fields() vals.StructMap { return failFields{e} } 230 231 type failFields struct{ e FailError } 232 233 func (failFields) IsStructMap() {} 234 235 func (f failFields) Type() string { return "fail" } 236 func (f failFields) Content() interface{} { return f.e.Content } 237 238 //elvdoc:fn fail 239 // 240 // ```elvish 241 // fail $v 242 // ``` 243 // 244 // Throws an exception; `$v` may be any type. If `$v` is already an exception, 245 // `fail` rethrows it. 246 // 247 // ```elvish-transcript 248 // ~> fail bad 249 // Exception: bad 250 // [tty 9], line 1: fail bad 251 // ~> put ?(fail bad) 252 // ▶ ?(fail bad) 253 // ~> fn f { fail bad } 254 // ~> fail ?(f) 255 // Exception: bad 256 // Traceback: 257 // [tty 7], line 1: 258 // fn f { fail bad } 259 // [tty 8], line 1: 260 // fail ?(f) 261 // ``` 262 263 func fail(v interface{}) error { 264 if e, ok := v.(error); ok { 265 // MAYBE TODO: if v is an exception, attach a "rethrown" stack trace, 266 // like Java 267 return e 268 } 269 return FailError{v} 270 } 271 272 func multiErrorFn(excs ...Exception) error { 273 return PipelineError{excs} 274 } 275 276 //elvdoc:fn return 277 // 278 // Raises the special "return" exception. When raised inside a named function 279 // (defined by the [`fn` keyword](../language.html#function-definition-fn)) it 280 // is captured by the function and causes the function to terminate. It is not 281 // captured by an anonymous function (aka [lambda](../language.html#lambda)). 282 // 283 // Because `return` raises an exception it can be caught by a 284 // [`try`](language.html#exception-control-try) block. If not caught, either 285 // implicitly by a named function or explicitly, it causes a failure like any 286 // other uncaught exception. 287 // 288 // See the discussion about [flow commands and 289 // exceptions](language.html#exception-and-flow-commands) 290 // 291 // **Note**: If you want to shadow the builtin `return` function with a local 292 // wrapper, do not define it with `fn` as `fn` swallows the special exception 293 // raised by return. Consider this example: 294 // 295 // ```elvish-transcript 296 // ~> use builtin 297 // ~> fn return { put return; builtin:return } 298 // ~> fn test-return { put before; return; put after } 299 // ~> test-return 300 // ▶ before 301 // ▶ return 302 // ▶ after 303 // ``` 304 // 305 // Instead, shadow the function by directly assigning to `return~`: 306 // 307 // ```elvish-transcript 308 // ~> use builtin 309 // ~> var return~ = { put return; builtin:return } 310 // ~> fn test-return { put before; return; put after } 311 // ~> test-return 312 // ▶ before 313 // ▶ return 314 // ``` 315 316 func returnFn() error { 317 return Return 318 } 319 320 //elvdoc:fn break 321 // 322 // Raises the special "break" exception. When raised inside a loop it is 323 // captured and causes the loop to terminate. 324 // 325 // Because `break` raises an exception it can be caught by a 326 // [`try`](language.html#exception-control-try) block. If not caught, either 327 // implicitly by a loop or explicitly, it causes a failure like any other 328 // uncaught exception. 329 // 330 // See the discussion about [flow commands and exceptions](language.html#exception-and-flow-commands) 331 // 332 // **Note**: You can create a `break` function and it will shadow the builtin 333 // command. If you do so you should explicitly invoke the builtin. For example: 334 // 335 // ```elvish-transcript 336 // ~> use builtin 337 // ~> fn break []{ put 'break'; builtin:break; put 'should not appear' } 338 // ~> for x [a b c] { put $x; break; put 'unexpected' } 339 // ▶ a 340 // ▶ break 341 // ``` 342 343 func breakFn() error { 344 return Break 345 } 346 347 //elvdoc:fn continue 348 // 349 // Raises the special "continue" exception. When raised inside a loop it is 350 // captured and causes the loop to begin its next iteration. 351 // 352 // Because `continue` raises an exception it can be caught by a 353 // [`try`](language.html#exception-control-try) block. If not caught, either 354 // implicitly by a loop or explicitly, it causes a failure like any other 355 // uncaught exception. 356 // 357 // See the discussion about [flow commands and exceptions](language.html#exception-and-flow-commands) 358 // 359 // **Note**: You can create a `continue` function and it will shadow the builtin 360 // command. If you do so you should explicitly invoke the builtin. For example: 361 // 362 // ```elvish-transcript 363 // ~> use builtin 364 // ~> fn continue []{ put 'continue'; builtin:continue; put 'should not appear' } 365 // ~> for x [a b c] { put $x; continue; put 'unexpected' } 366 // ▶ a 367 // ▶ continue 368 // ▶ b 369 // ▶ continue 370 // ▶ c 371 // ▶ continue 372 // ``` 373 374 func continueFn() error { 375 return Continue 376 }