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  }