github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/builtin_fn_container.go (about) 1 package eval 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/markusbkk/elvish/pkg/eval/errs" 8 "github.com/markusbkk/elvish/pkg/eval/vals" 9 "github.com/markusbkk/elvish/pkg/eval/vars" 10 ) 11 12 // Lists and maps. 13 14 func init() { 15 addBuiltinFns(map[string]interface{}{ 16 "ns": nsFn, 17 18 "make-map": makeMap, 19 20 "assoc": assoc, 21 "dissoc": dissoc, 22 23 "has-key": hasKey, 24 "has-value": hasValue, 25 26 "keys": keys, 27 }) 28 } 29 30 //elvdoc:fn ns 31 // 32 // ```elvish 33 // ns $map 34 // ``` 35 // 36 // Constructs a namespace from `$map`, using the keys as variable names and the 37 // values as their values. Examples: 38 // 39 // ```elvish-transcript 40 // ~> var n = (ns [&name=value]) 41 // ~> put $n[name] 42 // ▶ value 43 // ~> var n: = (ns [&name=value]) 44 // ~> put $n:name 45 // ▶ value 46 // ``` 47 48 func nsFn(m vals.Map) (*Ns, error) { 49 nb := BuildNs() 50 for it := m.Iterator(); it.HasElem(); it.Next() { 51 k, v := it.Elem() 52 kstring, ok := k.(string) 53 if !ok { 54 return nil, errs.BadValue{ 55 What: `key of argument of "ns"`, 56 Valid: "string", Actual: vals.Kind(k)} 57 } 58 nb.AddVar(kstring, vars.FromInit(v)) 59 } 60 return nb.Ns(), nil 61 } 62 63 //elvdoc:fn make-map 64 // 65 // ```elvish 66 // make-map $input? 67 // ``` 68 // 69 // Outputs a map from the [value inputs](#value-inputs), each of which must be 70 // an iterable value with with two elements. The first element of each value 71 // is used as the key, and the second element is used as the value. 72 // 73 // If the same key appears multiple times, the last value is used. 74 // 75 // Examples: 76 // 77 // ```elvish-transcript 78 // ~> make-map [[k v]] 79 // ▶ [&k=v] 80 // ~> make-map [[k v1] [k v2]] 81 // ▶ [&k=v2] 82 // ~> put [k1 v1] [k2 v2] | make-map 83 // ▶ [&k1=v1 &k2=v2] 84 // ~> put aA bB | make-map 85 // ▶ [&a=A &b=B] 86 // ``` 87 88 func makeMap(input Inputs) (vals.Map, error) { 89 m := vals.EmptyMap 90 var errMakeMap error 91 input(func(v interface{}) { 92 if errMakeMap != nil { 93 return 94 } 95 if !vals.CanIterate(v) { 96 errMakeMap = errs.BadValue{ 97 What: "input to make-map", Valid: "iterable", Actual: vals.Kind(v)} 98 return 99 } 100 if l := vals.Len(v); l != 2 { 101 errMakeMap = errs.BadValue{ 102 What: "input to make-map", Valid: "iterable with 2 elements", 103 Actual: fmt.Sprintf("%v with %v elements", vals.Kind(v), l)} 104 return 105 } 106 elems, err := vals.Collect(v) 107 if err != nil { 108 errMakeMap = err 109 return 110 } 111 if len(elems) != 2 { 112 errMakeMap = fmt.Errorf("internal bug: collected %v values", len(elems)) 113 return 114 } 115 m = m.Assoc(elems[0], elems[1]) 116 }) 117 return m, errMakeMap 118 } 119 120 //elvdoc:fn assoc 121 // 122 // ```elvish 123 // assoc $container $k $v 124 // ``` 125 // 126 // Output a slightly modified version of `$container`, such that its value at `$k` 127 // is `$v`. Applies to both lists and to maps. 128 // 129 // When `$container` is a list, `$k` may be a negative index. However, slice is not 130 // yet supported. 131 // 132 // ```elvish-transcript 133 // ~> assoc [foo bar quux] 0 lorem 134 // ▶ [lorem bar quux] 135 // ~> assoc [foo bar quux] -1 ipsum 136 // ▶ [foo bar ipsum] 137 // ~> assoc [&k=v] k v2 138 // ▶ [&k=v2] 139 // ~> assoc [&k=v] k2 v2 140 // ▶ [&k2=v2 &k=v] 141 // ``` 142 // 143 // Etymology: [Clojure](https://clojuredocs.org/clojure.core/assoc). 144 // 145 // @cf dissoc 146 147 func assoc(a, k, v interface{}) (interface{}, error) { 148 return vals.Assoc(a, k, v) 149 } 150 151 var errCannotDissoc = errors.New("cannot dissoc") 152 153 //elvdoc:fn dissoc 154 // 155 // ```elvish 156 // dissoc $map $k 157 // ``` 158 // 159 // Output a slightly modified version of `$map`, with the key `$k` removed. If 160 // `$map` does not contain `$k` as a key, the same map is returned. 161 // 162 // ```elvish-transcript 163 // ~> dissoc [&foo=bar &lorem=ipsum] foo 164 // ▶ [&lorem=ipsum] 165 // ~> dissoc [&foo=bar &lorem=ipsum] k 166 // ▶ [&lorem=ipsum &foo=bar] 167 // ``` 168 // 169 // @cf assoc 170 171 func dissoc(a, k interface{}) (interface{}, error) { 172 a2 := vals.Dissoc(a, k) 173 if a2 == nil { 174 return nil, errCannotDissoc 175 } 176 return a2, nil 177 } 178 179 //elvdoc:fn has-value 180 // 181 // ```elvish 182 // has-value $container $value 183 // ``` 184 // 185 // Determine whether `$value` is a value in `$container`. 186 // 187 // Examples, maps: 188 // 189 // ```elvish-transcript 190 // ~> has-value [&k1=v1 &k2=v2] v1 191 // ▶ $true 192 // ~> has-value [&k1=v1 &k2=v2] k1 193 // ▶ $false 194 // ``` 195 // 196 // Examples, lists: 197 // 198 // ```elvish-transcript 199 // ~> has-value [v1 v2] v1 200 // ▶ $true 201 // ~> has-value [v1 v2] k1 202 // ▶ $false 203 // ``` 204 // 205 // Examples, strings: 206 // 207 // ```elvish-transcript 208 // ~> has-value ab b 209 // ▶ $true 210 // ~> has-value ab c 211 // ▶ $false 212 // ``` 213 214 func hasValue(container, value interface{}) (bool, error) { 215 switch container := container.(type) { 216 case vals.Map: 217 for it := container.Iterator(); it.HasElem(); it.Next() { 218 _, v := it.Elem() 219 if vals.Equal(v, value) { 220 return true, nil 221 } 222 } 223 return false, nil 224 default: 225 var found bool 226 err := vals.Iterate(container, func(v interface{}) bool { 227 found = (v == value) 228 return !found 229 }) 230 return found, err 231 } 232 } 233 234 //elvdoc:fn has-key 235 // 236 // ```elvish 237 // has-key $container $key 238 // ``` 239 // 240 // Determine whether `$key` is a key in `$container`. A key could be a map key or 241 // an index on a list or string. This includes a range of indexes. 242 // 243 // Examples, maps: 244 // 245 // ```elvish-transcript 246 // ~> has-key [&k1=v1 &k2=v2] k1 247 // ▶ $true 248 // ~> has-key [&k1=v1 &k2=v2] v1 249 // ▶ $false 250 // ``` 251 // 252 // Examples, lists: 253 // 254 // ```elvish-transcript 255 // ~> has-key [v1 v2] 0 256 // ▶ $true 257 // ~> has-key [v1 v2] 1 258 // ▶ $true 259 // ~> has-key [v1 v2] 2 260 // ▶ $false 261 // ~> has-key [v1 v2] 0:2 262 // ▶ $true 263 // ~> has-key [v1 v2] 0:3 264 // ▶ $false 265 // ``` 266 // 267 // Examples, strings: 268 // 269 // ```elvish-transcript 270 // ~> has-key ab 0 271 // ▶ $true 272 // ~> has-key ab 1 273 // ▶ $true 274 // ~> has-key ab 2 275 // ▶ $false 276 // ~> has-key ab 0:2 277 // ▶ $true 278 // ~> has-key ab 0:3 279 // ▶ $false 280 // ``` 281 282 func hasKey(container, key interface{}) bool { 283 return vals.HasKey(container, key) 284 } 285 286 //elvdoc:fn keys 287 // 288 // ```elvish 289 // keys $map 290 // ``` 291 // 292 // Put all keys of `$map` on the structured stdout. 293 // 294 // Example: 295 // 296 // ```elvish-transcript 297 // ~> keys [&a=foo &b=bar &c=baz] 298 // ▶ a 299 // ▶ c 300 // ▶ b 301 // ``` 302 // 303 // Note that there is no guaranteed order for the keys of a map. 304 305 func keys(fm *Frame, v interface{}) error { 306 out := fm.ValueOutput() 307 var errPut error 308 errIterate := vals.IterateKeys(v, func(k interface{}) bool { 309 errPut = out.Put(k) 310 return errPut == nil 311 }) 312 if errIterate != nil { 313 return errIterate 314 } 315 return errPut 316 }