elvish/pkg/eval/builtin_fn_container.go

629 lines
12 KiB
Go
Raw Normal View History

2017-12-17 13:20:03 +08:00
package eval
import (
"errors"
"fmt"
"github.com/elves/elvish/pkg/eval/errs"
2019-12-24 04:00:59 +08:00
"github.com/elves/elvish/pkg/eval/vals"
"github.com/elves/elvish/pkg/eval/vars"
"github.com/xiaq/persistent/hashmap"
2017-12-17 13:20:03 +08:00
)
// Sequence, list and maps.
// TODO(xiaq): Document "ns".
//elvdoc:fn range
//
// ```elvish
// range &step=1 $low? $high
// ```
//
// Output `$low`, `$low` + `$step`, ..., proceeding as long as smaller than
// `$high`. If not given, `$low` defaults to 0.
//
// Examples:
//
// ```elvish-transcript
// ~> range 4
// ▶ 0
// ▶ 1
// ▶ 2
// ▶ 3
// ~> range 1 6 &step=2
// ▶ 1
// ▶ 3
// ▶ 5
// ```
//
// Beware floating point oddities:
//
// ```elvish-transcript
// ~> range 0 0.8 &step=.1
// ▶ 0
// ▶ 0.1
// ▶ 0.2
// ▶ 0.30000000000000004
// ▶ 0.4
// ▶ 0.5
// ▶ 0.6
// ▶ 0.7
// ▶ 0.7999999999999999
// ```
//
// Etymology:
// [Python](https://docs.python.org/3/library/functions.html#func-range).
//elvdoc:fn repeat
//
// ```elvish
// repeat $n $value
// ```
//
// Output `$value` for `$n` times. Example:
//
// ```elvish-transcript
// ~> repeat 0 lorem
// ~> repeat 4 NAN
// ▶ NAN
// ▶ NAN
// ▶ NAN
// ▶ NAN
// ```
//
// Etymology: [Clojure](https://clojuredocs.org/clojure.core/repeat).
//elvdoc:fn explode
//
// ```elvish
// explode $iterable
// ```
//
// Put all elements of `$iterable` on the structured stdout. Like `flatten` in
// functional languages. Equivalent to `[li]{ put $@li }`.
//
// Example:
//
// ```elvish-transcript
// ~> explode [a b [x]]
// ▶ a
// ▶ b
// ▶ [x]
// ```
//
// Etymology: [PHP](http://php.net/manual/en/function.explode.php). PHP's `explode`
// is actually equivalent to Elvish's `splits`, but the author liked the name too
// much to not use it.
//elvdoc:fn assoc
//
// ```elvish
// assoc $container $k $v
// ```
//
// Output a slightly modified version of `$container`, such that its value at `$k`
// is `$v`. Applies to both lists and to maps.
//
// When `$container` is a list, `$k` may be a negative index. However, slice is not
// yet supported.
//
// ```elvish-transcript
// ~> assoc [foo bar quux] 0 lorem
// ▶ [lorem bar quux]
// ~> assoc [foo bar quux] -1 ipsum
// ▶ [foo bar ipsum]
// ~> assoc [&k=v] k v2
// ▶ [&k=v2]
// ~> assoc [&k=v] k2 v2
// ▶ [&k2=v2 &k=v]
// ```
//
// Etymology: [Clojure](https://clojuredocs.org/clojure.core/assoc).
//
// @cf dissoc
//elvdoc:fn dissoc
//
// ```elvish
// dissoc $map $k
// ```
//
// Output a slightly modified version of `$map`, with the key `$k` removed. If
// `$map` does not contain `$k` as a key, the same map is returned.
//
// ```elvish-transcript
// ~> dissoc [&foo=bar &lorem=ipsum] foo
// ▶ [&lorem=ipsum]
// ~> dissoc [&foo=bar &lorem=ipsum] k
// ▶ [&lorem=ipsum &foo=bar]
// ```
//
// @cf assoc
//elvdoc:fn all
//
// ```elvish
// all $input-list?
// ```
//
// Passes inputs to the output as is. Byte inputs into values, one per line.
//
// This is an identity function for commands with value outputs: `a | all` is
// equivalent to `a` if it only outputs values.
//
// This function is useful for turning inputs into arguments, like:
//
// ```elvish-transcript
// ~> put 'lorem,ipsum' | splits , (all)
// ▶ lorem
// ▶ ipsum
// ```
//
// Or capturing all inputs in a variable:
//
// ```elvish-transcript
// ~> x = [(all)]
// foo
// bar
// (Press ^D)
// ~> put $x
// ▶ [foo bar]
// ```
//
// When given a list, it outputs all elements of the list:
//
// ```elvish-transcript
// ~> all [foo bar]
// ▶ foo
// ▶ bar
// ```
//
// @cf one
//elvdoc:fn one
//
// ```elvish
// one $input-list?
// ```
//
// Passes inputs to outputs, if there is only a single one. Otherwise raises an
// exception.
//
// This function can be used in a similar way to [`all`](#all), but is a better
// choice when you expect that there is exactly one output:
//
// @cf all
//elvdoc:fn has-key
//
// ```elvish
// has-key $container $key
// ```
//
// Determine whether `$key` is a key in `$container`. A key could be a map key or
// an index on a list or string. This includes a range of indexes.
//
// Examples, maps:
//
// ```elvish-transcript
// ~> has-key [&k1=v1 &k2=v2] k1
// ▶ $true
// ~> has-key [&k1=v1 &k2=v2] v1
// ▶ $false
// ```
//
// Examples, lists:
//
// ```elvish-transcript
// ~> has-key [v1 v2] 0
// ▶ $true
// ~> has-key [v1 v2] 1
// ▶ $true
// ~> has-key [v1 v2] 2
// ▶ $false
// ~> has-key [v1 v2] 0:2
// ▶ $true
// ~> has-key [v1 v2] 0:3
// ▶ $false
// ```
//
// Examples, strings:
//
// ```elvish-transcript
// ~> has-key ab 0
// ▶ $true
// ~> has-key ab 1
// ▶ $true
// ~> has-key ab 2
// ▶ $false
// ~> has-key ab 0:2
// ▶ $true
// ~> has-key ab 0:3
// ▶ $false
// ```
//elvdoc:fn has-value
//
// ```elvish
// has-value $container $value
// ```
//
// Determine whether `$value` is a value in `$container`.
//
// Examples, maps:
//
// ```elvish-transcript
// ~> has-value [&k1=v1 &k2=v2] v1
// ▶ $true
// ~> has-value [&k1=v1 &k2=v2] k1
// ▶ $false
// ```
//
// Examples, lists:
//
// ```elvish-transcript
// ~> has-value [v1 v2] v1
// ▶ $true
// ~> has-value [v1 v2] k1
// ▶ $false
// ```
//
// Examples, strings:
//
// ```elvish-transcript
// ~> has-value ab b
// ▶ $true
// ~> has-value ab c
// ▶ $false
// ```
//elvdoc:fn take
//
// ```elvish
// take $n $input-list?
// ```
//
// Retain the first `$n` input elements. If `$n` is larger than the number of input
// elements, the entire input is retained. Examples:
//
// ```elvish-transcript
// ~> take 3 [a b c d e]
// ▶ a
// ▶ b
// ▶ c
// ~> splits ' ' 'how are you?' | take 1
// ▶ how
// ~> range 2 | take 10
// ▶ 0
// ▶ 1
// ```
//
// Etymology: Haskell.
//elvdoc:fn drop
//
// ```elvish
// drop $n $input-list?
// ```
//
// Drop the first `$n` elements of the input. If `$n` is larger than the number of
// input elements, the entire input is dropped.
//
// Example:
//
// ```elvish-transcript
// ~> drop 2 [a b c d e]
// ▶ c
// ▶ d
// ▶ e
// ~> splits ' ' 'how are you?' | drop 1
// ▶ are
// ▶ 'you?'
// ~> range 2 | drop 10
// ```
//
// Etymology: Haskell.
//
// @cf take
//elvdoc:fn count
//
// ```elvish
// count $input-list?
// ```
//
// Count the number of inputs.
//
// Examples:
//
// ```elvish-transcript
// ~> count lorem # count bytes in a string
// ▶ 5
// ~> count [lorem ipsum]
// ▶ 2
// ~> range 100 | count
// ▶ 100
// ~> seq 100 | count
// ▶ 100
// ```
//elvdoc:fn keys
//
// ```elvish
// keys $map
// ```
//
// Put all keys of `$map` on the structured stdout.
//
// Example:
//
// ```elvish-transcript
// ~> keys [&a=foo &b=bar &c=baz]
// ▶ a
// ▶ c
// ▶ b
// ```
//
// Note that there is no guaranteed order for the keys of a map.
2017-12-17 13:20:03 +08:00
func init() {
2018-02-07 03:39:40 +08:00
addBuiltinFns(map[string]interface{}{
"ns": nsFn,
2017-12-17 13:20:03 +08:00
2020-05-06 06:23:23 +08:00
"make-map": makeMap,
"range": rangeFn,
"repeat": repeat,
"explode": explode,
2017-12-17 13:20:03 +08:00
"assoc": assoc,
"dissoc": dissoc,
2017-12-17 13:20:03 +08:00
"all": all,
2020-01-17 22:00:23 +08:00
"one": one,
2017-12-17 13:20:03 +08:00
"has-key": hasKey,
"has-value": hasValue,
2017-12-17 13:20:03 +08:00
2018-02-05 15:35:49 +08:00
"take": take,
"drop": drop,
"count": count,
"keys": keys,
2017-12-17 13:20:03 +08:00
})
}
var errKeyMustBeString = errors.New("key must be string")
func nsFn(m hashmap.Map) (Ns, error) {
ns := make(Ns)
for it := m.Iterator(); it.HasElem(); it.Next() {
k, v := it.Elem()
kstring, ok := k.(string)
if !ok {
return nil, errKeyMustBeString
}
ns[kstring] = vars.FromInit(v)
}
return ns, nil
}
2020-05-06 06:23:23 +08:00
//elvdoc:fn make-map
//
// ```elvish
// make-map $input?
// ```
//
// Outputs a map from an input consisting of containers with two elements. The
// first element of each container is used as the key, and the second element is
// used as the value.
//
// If the same key appears multiple times, the last value is used.
//
// Examples:
//
// ```elvish-transcript
// ~> make-map [[k v]]
// ▶ [&k=v]
// ~> make-map [[k v1] [k v2]]
// ▶ [&k=v2]
// ~> put [k1 v1] [k2 v2] | make-map
// ▶ [&k1=v1 &k2=v2]
// ~> put aA bB | make-map
// ▶ [&a=A &b=B]
// ```
func makeMap(input Inputs) (vals.Map, error) {
m := vals.EmptyMap
var errMakeMap error
input(func(v interface{}) {
if errMakeMap != nil {
return
}
if !vals.CanIterate(v) {
errMakeMap = errs.BadValue{
What: "input to make-map", Valid: "iterable", Actual: vals.Kind(v)}
return
}
if l := vals.Len(v); l != 2 {
errMakeMap = errs.BadValue{
What: "input to make-map", Valid: "iterable with 2 elements",
Actual: fmt.Sprintf("%v with %v elements", vals.Kind(v), l)}
return
}
elems, err := vals.Collect(v)
if err != nil {
errMakeMap = err
return
}
if len(elems) != 2 {
errMakeMap = fmt.Errorf("internal bug: collected %v values", len(elems))
return
}
m = m.Assoc(elems[0], elems[1])
})
return m, errMakeMap
}
type rangeOpts struct{ Step float64 }
2017-12-17 13:20:03 +08:00
func (o *rangeOpts) SetDefaultOptions() { o.Step = 1 }
func rangeFn(fm *Frame, opts rangeOpts, args ...float64) error {
2017-12-17 13:20:03 +08:00
var lower, upper float64
switch len(args) {
case 1:
upper = args[0]
2017-12-17 13:20:03 +08:00
case 2:
lower, upper = args[0], args[1]
2017-12-17 13:20:03 +08:00
default:
return ErrArgs
2017-12-17 13:20:03 +08:00
}
out := fm.ports[1].Chan
for f := lower; f < upper; f += opts.Step {
2018-02-15 17:14:05 +08:00
out <- vals.FromGo(f)
2017-12-17 13:20:03 +08:00
}
return nil
2017-12-17 13:20:03 +08:00
}
func repeat(fm *Frame, n int, v interface{}) {
out := fm.OutputChan()
2017-12-17 13:20:03 +08:00
for i := 0; i < n; i++ {
out <- v
}
}
func explode(fm *Frame, v interface{}) error {
out := fm.ports[1].Chan
return vals.Iterate(v, func(e interface{}) bool {
2017-12-17 13:20:03 +08:00
out <- e
return true
})
}
func assoc(a, k, v interface{}) (interface{}, error) {
2018-02-15 17:14:05 +08:00
return vals.Assoc(a, k, v)
2017-12-17 13:20:03 +08:00
}
var errCannotDissoc = errors.New("cannot dissoc")
func dissoc(a, k interface{}) (interface{}, error) {
2018-02-15 17:14:05 +08:00
a2 := vals.Dissoc(a, k)
if a2 == nil {
return nil, errCannotDissoc
}
return a2, nil
2017-12-17 13:20:03 +08:00
}
func all(fm *Frame, inputs Inputs) {
out := fm.ports[1].Chan
inputs(func(v interface{}) { out <- v })
2017-12-17 13:20:03 +08:00
}
2020-01-17 22:00:23 +08:00
func one(fm *Frame, inputs Inputs) error {
var val interface{}
n := 0
inputs(func(v interface{}) {
if n == 0 {
val = v
}
n++
})
if n == 1 {
fm.OutputChan() <- val
return nil
}
return fmt.Errorf("expect a single value, got %d", n)
}
2018-02-05 15:35:49 +08:00
func take(fm *Frame, n int, inputs Inputs) {
out := fm.ports[1].Chan
2017-12-17 13:20:03 +08:00
i := 0
2018-02-05 15:35:49 +08:00
inputs(func(v interface{}) {
2017-12-17 13:20:03 +08:00
if i < n {
out <- v
}
i++
})
}
2018-02-05 15:35:49 +08:00
func drop(fm *Frame, n int, inputs Inputs) {
out := fm.ports[1].Chan
2017-12-17 13:20:03 +08:00
i := 0
2018-02-05 15:35:49 +08:00
inputs(func(v interface{}) {
2017-12-17 13:20:03 +08:00
if i >= n {
out <- v
}
i++
})
}
func hasValue(container, value interface{}) (bool, error) {
2017-12-17 13:20:03 +08:00
switch container := container.(type) {
case hashmap.Map:
for it := container.Iterator(); it.HasElem(); it.Next() {
_, v := it.Elem()
2018-02-15 17:14:05 +08:00
if vals.Equal(v, value) {
return true, nil
}
}
return false, nil
2017-12-17 13:20:03 +08:00
default:
var found bool
2018-02-15 17:14:05 +08:00
err := vals.Iterate(container, func(v interface{}) bool {
found = (v == value)
return !found
})
return found, err
2017-12-17 13:20:03 +08:00
}
}
func hasKey(container, key interface{}) bool {
return vals.HasKey(container, key)
2017-12-17 13:20:03 +08:00
}
// The count implmentation uses a custom varargs based implementation rather
// than the more common `Inputs` API (see pkg/eval/go_fn.go) because this
// allows the implmentation to be O(1) for the common cases rather than O(n).
func count(fm *Frame, args ...interface{}) (int, error) {
2017-12-17 13:20:03 +08:00
var n int
switch nargs := len(args); nargs {
2017-12-17 13:20:03 +08:00
case 0:
// Count inputs.
2018-02-05 15:35:49 +08:00
fm.IterateInputs(func(interface{}) {
2017-12-17 13:20:03 +08:00
n++
})
case 1:
// Get length of argument.
v := args[0]
2018-02-15 17:14:05 +08:00
if len := vals.Len(v); len >= 0 {
n = len
} else {
2018-02-15 17:14:05 +08:00
err := vals.Iterate(v, func(interface{}) bool {
2017-12-17 13:20:03 +08:00
n++
return true
})
if err != nil {
return 0, fmt.Errorf("cannot get length of a %s", vals.Kind(v))
}
2017-12-17 13:20:03 +08:00
}
default:
// The error matches what would be returned if the `Inputs` API was
// used. See GoFn.Call().
return 0, errs.ArityMismatch{
What: "arguments here", ValidLow: 0, ValidHigh: 1, Actual: nargs}
2017-12-17 13:20:03 +08:00
}
return n, nil
2017-12-17 13:20:03 +08:00
}
2018-03-02 00:59:50 +08:00
func keys(fm *Frame, v interface{}) error {
out := fm.ports[1].Chan
2018-03-02 00:59:50 +08:00
return vals.IterateKeys(v, func(k interface{}) bool {
out <- k
2018-03-02 00:59:50 +08:00
return true
})
2017-12-17 13:20:03 +08:00
}