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

     1  package eval
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/markusbkk/elvish/pkg/eval/errs"
     8  	"github.com/markusbkk/elvish/pkg/eval/vals"
     9  )
    10  
    11  // Stream manipulation.
    12  
    13  func init() {
    14  	addBuiltinFns(map[string]interface{}{
    15  		"all": all,
    16  		"one": one,
    17  
    18  		"take": take,
    19  		"drop": drop,
    20  
    21  		"count": count,
    22  
    23  		"order": order,
    24  	})
    25  }
    26  
    27  //elvdoc:fn all
    28  //
    29  // ```elvish
    30  // all $inputs?
    31  // ```
    32  //
    33  // Takes [value inputs](#value-inputs), and outputs those values unchanged.
    34  //
    35  // This is an [identity
    36  // function](https://en.wikipedia.org/wiki/Identity_function) for the value
    37  // channel; in other words, `a | all` is equivalent to just `a` if `a` only has
    38  // value output.
    39  //
    40  // This command can be used inside output capture (i.e. `(all)`) to turn value
    41  // inputs into arguments. For example:
    42  //
    43  // ```elvish-transcript
    44  // ~> echo '["foo","bar"] ["lorem","ipsum"]' | from-json
    45  // ▶ [foo bar]
    46  // ▶ [lorem ipsum]
    47  // ~> echo '["foo","bar"] ["lorem","ipsum"]' | from-json | put (all)[0]
    48  // ▶ foo
    49  // ▶ lorem
    50  // ```
    51  //
    52  // The latter pipeline is equivalent to the following:
    53  //
    54  // ```elvish-transcript
    55  // ~> put (echo '["foo","bar"] ["lorem","ipsum"]' | from-json)[0]
    56  // ▶ foo
    57  // ▶ lorem
    58  // ```
    59  //
    60  // In general, when `(all)` appears in the last command of a pipeline, it is
    61  // equivalent to just moving the previous commands of the pipeline into `()`.
    62  // The choice is a stylistic one; the `(all)` variant is longer overall, but can
    63  // be more readable since it allows you to avoid putting an excessively long
    64  // pipeline inside an output capture, and keeps the data flow within the
    65  // pipeline.
    66  //
    67  // Putting the value capture inside `[]` (i.e. `[(all)]`) is useful for storing
    68  // all value inputs in a list for further processing:
    69  //
    70  // ```elvish-transcript
    71  // ~> fn f { var inputs = [(all)]; put $inputs[1] }
    72  // ~> put foo bar baz | f
    73  // ▶ bar
    74  // ```
    75  //
    76  // The `all` command can also take "inputs" from an iterable argument. This can
    77  // be used to flatten lists or strings (although not recursively):
    78  //
    79  // ```elvish-transcript
    80  // ~> all [foo [lorem ipsum]]
    81  // ▶ foo
    82  // ▶ [lorem ipsum]
    83  // ~> all foo
    84  // ▶ f
    85  // ▶ o
    86  // ▶ o
    87  // ```
    88  //
    89  // This can be used together with `(one)` to turn a single iterable value in the
    90  // pipeline into its elements:
    91  //
    92  // ```elvish-transcript
    93  // ~> echo '["foo","bar","lorem"]' | from-json
    94  // ▶ [foo bar lorem]
    95  // ~> echo '["foo","bar","lorem"]' | from-json | all (one)
    96  // ▶ foo
    97  // ▶ bar
    98  // ▶ lorem
    99  // ```
   100  //
   101  // When given byte inputs, the `all` command currently functions like
   102  // [`from-lines`](#from-lines), although this behavior is subject to change:
   103  //
   104  // ```elvish-transcript
   105  // ~> print "foo\nbar\n" | all
   106  // ▶ foo
   107  // ▶ bar
   108  // ```
   109  //
   110  // @cf one
   111  
   112  func all(fm *Frame, inputs Inputs) error {
   113  	out := fm.ValueOutput()
   114  	var errOut error
   115  	inputs(func(v interface{}) {
   116  		if errOut != nil {
   117  			return
   118  		}
   119  		errOut = out.Put(v)
   120  	})
   121  	return errOut
   122  }
   123  
   124  //elvdoc:fn one
   125  //
   126  // ```elvish
   127  // one $inputs?
   128  // ```
   129  //
   130  // Takes exactly one [value input](#value-inputs) and outputs it. If there are
   131  // more than one value inputs, raises an exception.
   132  //
   133  // This function can be used in a similar way to [`all`](#all), but is a better
   134  // choice when you expect that there is exactly one output.
   135  //
   136  // @cf all
   137  
   138  func one(fm *Frame, inputs Inputs) error {
   139  	var val interface{}
   140  	n := 0
   141  	inputs(func(v interface{}) {
   142  		if n == 0 {
   143  			val = v
   144  		}
   145  		n++
   146  	})
   147  	if n == 1 {
   148  		return fm.ValueOutput().Put(val)
   149  	}
   150  	return errs.ArityMismatch{What: "values", ValidLow: 1, ValidHigh: 1, Actual: n}
   151  }
   152  
   153  //elvdoc:fn take
   154  //
   155  // ```elvish
   156  // take $n $inputs?
   157  // ```
   158  //
   159  // Outputs the first `$n` [value inputs](#value-inputs). If `$n` is larger than
   160  // the number of value inputs, outputs everything.
   161  //
   162  // Examples:
   163  //
   164  // ```elvish-transcript
   165  // ~> range 2 | take 10
   166  // ▶ 0
   167  // ▶ 1
   168  // ~> take 3 [a b c d e]
   169  // ▶ a
   170  // ▶ b
   171  // ▶ c
   172  // ~> use str
   173  // ~> str:split ' ' 'how are you?' | take 1
   174  // ▶ how
   175  // ```
   176  //
   177  // Etymology: Haskell.
   178  //
   179  // @cf drop
   180  
   181  func take(fm *Frame, n int, inputs Inputs) error {
   182  	out := fm.ValueOutput()
   183  	var errOut error
   184  	i := 0
   185  	inputs(func(v interface{}) {
   186  		if errOut != nil {
   187  			return
   188  		}
   189  		if i < n {
   190  			errOut = out.Put(v)
   191  		}
   192  		i++
   193  	})
   194  	return errOut
   195  }
   196  
   197  //elvdoc:fn drop
   198  //
   199  // ```elvish
   200  // drop $n $inputs?
   201  // ```
   202  //
   203  // Ignores the first `$n` [value inputs](#value-inputs) and outputs the rest.
   204  // If `$n` is larger than the number of value inputs, outputs nothing.
   205  //
   206  // Example:
   207  //
   208  // ```elvish-transcript
   209  // ~> range 10 | drop 8
   210  // ▶ (num 8)
   211  // ▶ (num 9)
   212  // ~> range 2 | drop 10
   213  // ~> drop 2 [a b c d e]
   214  // ▶ c
   215  // ▶ d
   216  // ▶ e
   217  // ~> use str
   218  // ~> str:split ' ' 'how are you?' | drop 1
   219  // ▶ are
   220  // ▶ 'you?'
   221  // ```
   222  //
   223  // Etymology: Haskell.
   224  //
   225  // @cf take
   226  
   227  func drop(fm *Frame, n int, inputs Inputs) error {
   228  	out := fm.ValueOutput()
   229  	var errOut error
   230  	i := 0
   231  	inputs(func(v interface{}) {
   232  		if errOut != nil {
   233  			return
   234  		}
   235  		if i >= n {
   236  			errOut = out.Put(v)
   237  		}
   238  		i++
   239  	})
   240  	return errOut
   241  }
   242  
   243  //elvdoc:fn count
   244  //
   245  // ```elvish
   246  // count $input-list?
   247  // ```
   248  //
   249  // Count the number of inputs.
   250  //
   251  // Examples:
   252  //
   253  // ```elvish-transcript
   254  // ~> count lorem # count bytes in a string
   255  // ▶ 5
   256  // ~> count [lorem ipsum]
   257  // ▶ 2
   258  // ~> range 100 | count
   259  // ▶ 100
   260  // ~> seq 100 | count
   261  // ▶ 100
   262  // ```
   263  
   264  // The count implementation uses a custom varargs based implementation rather
   265  // than the more common `Inputs` API (see pkg/eval/go_fn.go) because this
   266  // allows the implementation to be O(1) for the common cases rather than O(n).
   267  func count(fm *Frame, args ...interface{}) (int, error) {
   268  	var n int
   269  	switch nargs := len(args); nargs {
   270  	case 0:
   271  		// Count inputs.
   272  		fm.IterateInputs(func(interface{}) {
   273  			n++
   274  		})
   275  	case 1:
   276  		// Get length of argument.
   277  		v := args[0]
   278  		if len := vals.Len(v); len >= 0 {
   279  			n = len
   280  		} else {
   281  			err := vals.Iterate(v, func(interface{}) bool {
   282  				n++
   283  				return true
   284  			})
   285  			if err != nil {
   286  				return 0, fmt.Errorf("cannot get length of a %s", vals.Kind(v))
   287  			}
   288  		}
   289  	default:
   290  		// The error matches what would be returned if the `Inputs` API was
   291  		// used. See GoFn.Call().
   292  		return 0, errs.ArityMismatch{What: "arguments", ValidLow: 0, ValidHigh: 1, Actual: nargs}
   293  	}
   294  	return n, nil
   295  }
   296  
   297  //elvdoc:fn order
   298  //
   299  // ```elvish
   300  // order &reverse=$false $less-than=$nil $inputs?
   301  // ```
   302  //
   303  // Outputs the [value inputs](#value-inputs) sorted
   304  // in ascending order. The sorting process is guaranteed to be
   305  // [stable](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability).
   306  //
   307  // The `&reverse` option, if true, reverses the order of output.
   308  //
   309  // The `&less-than` option, if given, establishes the ordering of the elements.
   310  // Its value should be a function that takes two arguments and outputs a single
   311  // boolean indicating whether the first argument is less than the second
   312  // argument. If the function throws an exception, `order` rethrows the exception
   313  // without outputting any value.
   314  //
   315  // If `&less-than` has value `$nil` (the default if not set), it is equivalent
   316  // to `{|a b| eq -1 (compare $a $b) }`.
   317  //
   318  // Examples:
   319  //
   320  // ```elvish-transcript
   321  // ~> put foo bar ipsum | order
   322  // ▶ bar
   323  // ▶ foo
   324  // ▶ ipsum
   325  // ~> order [(float64 10) (float64 1) (float64 5)]
   326  // ▶ (float64 1)
   327  // ▶ (float64 5)
   328  // ▶ (float64 10)
   329  // ~> order [[a b] [a] [b b] [a c]]
   330  // ▶ [a]
   331  // ▶ [a b]
   332  // ▶ [a c]
   333  // ▶ [b b]
   334  // ~> order &reverse [a c b]
   335  // ▶ c
   336  // ▶ b
   337  // ▶ a
   338  // ~> order &less-than={|a b| eq $a x } [l x o r x e x m]
   339  // ▶ x
   340  // ▶ x
   341  // ▶ x
   342  // ▶ l
   343  // ▶ o
   344  // ▶ r
   345  // ▶ e
   346  // ▶ m
   347  // ```
   348  //
   349  // Beware that strings that look like numbers are treated as strings, not
   350  // numbers. To sort strings as numbers, use an explicit `&less-than` option:
   351  //
   352  // ```elvish-transcript
   353  // ~> order [5 1 10]
   354  // ▶ 1
   355  // ▶ 10
   356  // ▶ 5
   357  // ~> order &less-than={|a b| < $a $b } [5 1 10]
   358  // ▶ 1
   359  // ▶ 5
   360  // ▶ 10
   361  // ```
   362  //
   363  // @cf compare
   364  
   365  type orderOptions struct {
   366  	Reverse  bool
   367  	LessThan Callable
   368  }
   369  
   370  func (opt *orderOptions) SetDefaultOptions() {}
   371  
   372  func order(fm *Frame, opts orderOptions, inputs Inputs) error {
   373  	var values []interface{}
   374  	inputs(func(v interface{}) { values = append(values, v) })
   375  
   376  	var errSort error
   377  	var lessFn func(i, j int) bool
   378  	if opts.LessThan != nil {
   379  		lessFn = func(i, j int) bool {
   380  			if errSort != nil {
   381  				return true
   382  			}
   383  			var args []interface{}
   384  			if opts.Reverse {
   385  				args = []interface{}{values[j], values[i]}
   386  			} else {
   387  				args = []interface{}{values[i], values[j]}
   388  			}
   389  			outputs, err := fm.CaptureOutput(func(fm *Frame) error {
   390  				return opts.LessThan.Call(fm, args, NoOpts)
   391  			})
   392  			if err != nil {
   393  				errSort = err
   394  				return true
   395  			}
   396  			if len(outputs) != 1 {
   397  				errSort = errs.BadValue{
   398  					What:   "output of the &less-than callback",
   399  					Valid:  "a single boolean",
   400  					Actual: fmt.Sprintf("%d values", len(outputs))}
   401  				return true
   402  			}
   403  			if b, ok := outputs[0].(bool); ok {
   404  				return b
   405  			}
   406  			errSort = errs.BadValue{
   407  				What:  "output of the &less-than callback",
   408  				Valid: "boolean", Actual: vals.Kind(outputs[0])}
   409  			return true
   410  		}
   411  	} else {
   412  		// Use default comparison implemented by cmp.
   413  		lessFn = func(i, j int) bool {
   414  			if errSort != nil {
   415  				return true
   416  			}
   417  			o := cmp(values[i], values[j])
   418  			if o == uncomparable {
   419  				errSort = ErrUncomparable
   420  				return true
   421  			}
   422  			if opts.Reverse {
   423  				return o == more
   424  			}
   425  			return o == less
   426  		}
   427  	}
   428  
   429  	sort.SliceStable(values, lessFn)
   430  
   431  	if errSort != nil {
   432  		return errSort
   433  	}
   434  	out := fm.ValueOutput()
   435  	for _, v := range values {
   436  		err := out.Put(v)
   437  		if err != nil {
   438  			return err
   439  		}
   440  	}
   441  	return nil
   442  }