'enum' module (#166)

* 1. adding more methods to enum module
2. added ModuleMap.AddMap()
3. added bytes iterator

* add builtin functions 'is_enumerable' and 'is_array_like'

* builtin function 'is_iterable'

* first iteration on 'enum' module

* fix 'is_iterable' builtin function
This commit is contained in:
Daniel 2019-03-27 01:27:59 -07:00 committed by GitHub
parent b9c1c92d2d
commit 2f86800724
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 418 additions and 78 deletions

View file

@ -195,6 +195,10 @@ Returns `true` if the object's type is map. Or it returns `false`.
Returns `true` if the object's type is immutable map. Or it returns `false`.
## is_iterable
Returns `true` if the object's type is iterable: array, immutable array, map, immutable map, string, and bytes are iterable types in Tengo.
## is_time
Returns `true` if the object's type is time. Or it returns `false`.

View file

@ -61,6 +61,7 @@ _* time.Unix(): use `time.Unix(v, 0)` to convert to Time_
- `bytes(x)`: tries to convert `x` into bytes; returns `undefined` if failed
- `bytes(N)`: as a special case this will create a Bytes variable with the given size `N` (only if `N` is int)
- `time(x)`: tries to convert `x` into time; returns `undefined` if failed
- See [Builtins](https://github.com/d5/tengo/blob/master/docs/builtins.md) for the full list of builtin functions.
## Type Checking Builtin Functions
@ -77,3 +78,4 @@ _* time.Unix(): use `time.Unix(v, 0)` to convert to Time_
- `is_time(x)`: return `true` if `x` is time; `false` otherwise
- `is_error(x)`: returns `true` if `x` is error; `false` otherwise
- `is_undefined(x)`: returns `true` if `x` is undefined; `false` otherwise
- See [Builtins](https://github.com/d5/tengo/blob/master/docs/builtins.md) for the full list of builtin functions.

19
docs/stdlib-enum.md Normal file
View file

@ -0,0 +1,19 @@
# Module - "enum"
```golang
enum := import("enum")
```
## Functions
- `all(x, fn) => bool`: returns true if the given function `fn` evaluates to a truthy value on all of the items in `x`. It returns undefined if `x` is not enumerable.
- `any(x, fn) => bool`: returns true if the given function `fn` evaluates to a truthy value on any of the items in `x`. It returns undefined if `x` is not enumerable.
- `chunk(x, size) => [object]`: returns an array of elements split into groups the length of size. If `x` can't be split evenly, the final chunk will be the remaining elements. It returns undefined if `x` is not array.
- `at(x, key) => object`: returns an element at the given index (if `x` is array) or key (if `x` is map). It returns undefined if `x` is not enumerable.
- `each(x, fn)`: iterates over elements of `x` and invokes `fn` for each element. `fn` is invoked with two arguments: `key` and `value`. `key` is an int index if `x` is array. `key` is a string key if `x` is map. It does not iterate and returns undefined if `x` is not enumerable.`
- `filter(x, fn) => [object]`: iterates over elements of `x`, returning an array of all elements `fn` returns truthy for. `fn` is invoked with two arguments: `key` and `value`. `key` is an int index if `x` is array. `key` is a string key if `x` is map. It returns undefined if `x` is not enumerable.
- `find(x, fn) => object`: iterates over elements of `x`, returning value of the first element `fn` returns truthy for. `fn` is invoked with two arguments: `key` and `value`. `key` is an int index if `x` is array. `key` is a string key if `x` is map. It returns undefined if `x` is not enumerable.
- `find_key(x, fn) => int/string`: iterates over elements of `x`, returning key or index of the first element `fn` returns truthy for. `fn` is invoked with two arguments: `key` and `value`. `key` is an int index if `x` is array. `key` is a string key if `x` is map. It returns undefined if `x` is not enumerable.
- `map(x, fn) => [object]`: creates an array of values by running each element in `x` through `fn`. `fn` is invoked with two arguments: `key` and `value`. `key` is an int index if `x` is array. `key` is a string key if `x` is map. It returns undefined if `x` is not enumerable.
- `key(k, _) => object`: returns the first argument.
- `value(_, v) => object`: returns the second argument.

View file

@ -7,3 +7,4 @@
- [rand](https://github.com/d5/tengo/blob/master/docs/stdlib-rand.md): random functions
- [fmt](https://github.com/d5/tengo/blob/master/docs/stdlib-fmt.md): formatting functions
- [json](https://github.com/d5/tengo/blob/master/docs/stdlib-json.md): JSON functions
- [enum](https://github.com/d5/tengo/blob/master/docs/stdlib-enum.md): Enumeration functions

View file

@ -181,3 +181,15 @@ func builtinIsCallable(args ...Object) (Object, error) {
return FalseValue, nil
}
func builtinIsIterable(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(Iterable); ok {
return TrueValue, nil
}
return FalseValue, nil
}

View file

@ -83,6 +83,10 @@ var Builtins = []*BuiltinFunction{
Name: "is_immutable_map",
Value: builtinIsImmutableMap,
},
{
Name: "is_iterable",
Value: builtinIsIterable,
},
{
Name: "is_time",
Value: builtinIsTime,

View file

@ -79,3 +79,11 @@ func (o *Bytes) IndexGet(index Object) (res Object, err error) {
return
}
// Iterate creates a bytes iterator.
func (o *Bytes) Iterate() Iterator {
return &BytesIterator{
v: o.Value,
l: len(o.Value),
}
}

57
objects/bytes_iterator.go Normal file
View file

@ -0,0 +1,57 @@
package objects
import "github.com/d5/tengo/compiler/token"
// BytesIterator represents an iterator for a string.
type BytesIterator struct {
v []byte
i int
l int
}
// TypeName returns the name of the type.
func (i *BytesIterator) TypeName() string {
return "bytes-iterator"
}
func (i *BytesIterator) String() string {
return "<bytes-iterator>"
}
// BinaryOp returns another object that is the result of
// a given binary operator and a right-hand side object.
func (i *BytesIterator) BinaryOp(op token.Token, rhs Object) (Object, error) {
return nil, ErrInvalidOperator
}
// IsFalsy returns true if the value of the type is falsy.
func (i *BytesIterator) IsFalsy() bool {
return true
}
// Equals returns true if the value of the type
// is equal to the value of another object.
func (i *BytesIterator) Equals(Object) bool {
return false
}
// Copy returns a copy of the type.
func (i *BytesIterator) Copy() Object {
return &BytesIterator{v: i.v, i: i.i, l: i.l}
}
// Next returns true if there are more elements to iterate.
func (i *BytesIterator) Next() bool {
i.i++
return i.i <= i.l
}
// Key returns the key or index value of the current element.
func (i *BytesIterator) Key() Object {
return &Int{Value: int64(i.i - 1)}
}
// Value returns the value of the current element.
func (i *BytesIterator) Value() Object {
return &Int{Value: int64(i.v[i.i-1])}
}

View file

@ -68,3 +68,10 @@ func (m *ModuleMap) Copy() *ModuleMap {
func (m *ModuleMap) Len() int {
return len(m.m)
}
// AddMap adds named modules from another module map.
func (m *ModuleMap) AddMap(o *ModuleMap) {
for name, mod := range o.m {
m.m[name] = mod
}
}

View file

@ -3,11 +3,140 @@ package runtime_test
import (
"testing"
"github.com/d5/tengo/objects"
"github.com/d5/tengo/stdlib"
)
func TestSourceModules(t *testing.T) {
expect(t, `enum := import("enum"); out = enum.any([1,2,3], func(i, v) { return v == 2 })`,
Opts().Module("enum", stdlib.SourceModules["enum"]),
true)
testEnumModule(t, `out = enum.key(0, 20)`, 0)
testEnumModule(t, `out = enum.key(10, 20)`, 10)
testEnumModule(t, `out = enum.value(0, 0)`, 0)
testEnumModule(t, `out = enum.value(10, 20)`, 20)
testEnumModule(t, `out = enum.all([], enum.value)`, true)
testEnumModule(t, `out = enum.all([1], enum.value)`, true)
testEnumModule(t, `out = enum.all([true, 1], enum.value)`, true)
testEnumModule(t, `out = enum.all([true, 0], enum.value)`, false)
testEnumModule(t, `out = enum.all([true, 0, 1], enum.value)`, false)
testEnumModule(t, `out = enum.all(immutable([true, 0, 1]), enum.value)`, false) // immutable-array
testEnumModule(t, `out = enum.all({}, enum.value)`, true)
testEnumModule(t, `out = enum.all({a:1}, enum.value)`, true)
testEnumModule(t, `out = enum.all({a:true, b:1}, enum.value)`, true)
testEnumModule(t, `out = enum.all(immutable({a:true, b:1}), enum.value)`, true) // immutable-map
testEnumModule(t, `out = enum.all({a:true, b:0}, enum.value)`, false)
testEnumModule(t, `out = enum.all({a:true, b:0, c:1}, enum.value)`, false)
testEnumModule(t, `out = enum.all(0, enum.value)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out = enum.all("123", enum.value)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out = enum.any([], enum.value)`, false)
testEnumModule(t, `out = enum.any([1], enum.value)`, true)
testEnumModule(t, `out = enum.any([true, 1], enum.value)`, true)
testEnumModule(t, `out = enum.any([true, 0], enum.value)`, true)
testEnumModule(t, `out = enum.any([true, 0, 1], enum.value)`, true)
testEnumModule(t, `out = enum.any(immutable([true, 0, 1]), enum.value)`, true) // immutable-array
testEnumModule(t, `out = enum.any([false], enum.value)`, false)
testEnumModule(t, `out = enum.any([false, 0], enum.value)`, false)
testEnumModule(t, `out = enum.any({}, enum.value)`, false)
testEnumModule(t, `out = enum.any({a:1}, enum.value)`, true)
testEnumModule(t, `out = enum.any({a:true, b:1}, enum.value)`, true)
testEnumModule(t, `out = enum.any({a:true, b:0}, enum.value)`, true)
testEnumModule(t, `out = enum.any({a:true, b:0, c:1}, enum.value)`, true)
testEnumModule(t, `out = enum.any(immutable({a:true, b:0, c:1}), enum.value)`, true) // immutable-map
testEnumModule(t, `out = enum.any({a:false}, enum.value)`, false)
testEnumModule(t, `out = enum.any({a:false, b:0}, enum.value)`, false)
testEnumModule(t, `out = enum.any(0, enum.value)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out = enum.any("123", enum.value)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out = enum.chunk([], 1)`, ARR{})
testEnumModule(t, `out = enum.chunk([1], 1)`, ARR{ARR{1}})
testEnumModule(t, `out = enum.chunk([1,2,3], 1)`, ARR{ARR{1}, ARR{2}, ARR{3}})
testEnumModule(t, `out = enum.chunk([1,2,3], 2)`, ARR{ARR{1, 2}, ARR{3}})
testEnumModule(t, `out = enum.chunk([1,2,3], 3)`, ARR{ARR{1, 2, 3}})
testEnumModule(t, `out = enum.chunk([1,2,3], 4)`, ARR{ARR{1, 2, 3}})
testEnumModule(t, `out = enum.chunk([1,2,3,4], 3)`, ARR{ARR{1, 2, 3}, ARR{4}})
testEnumModule(t, `out = enum.chunk([], 0)`, objects.UndefinedValue) // size=0: undefined
testEnumModule(t, `out = enum.chunk([1], 0)`, objects.UndefinedValue) // size=0: undefined
testEnumModule(t, `out = enum.chunk([1,2,3], 0)`, objects.UndefinedValue) // size=0: undefined
testEnumModule(t, `out = enum.chunk({a:1,b:2,c:3}, 1)`, objects.UndefinedValue) // map: undefined
testEnumModule(t, `out = enum.chunk(0, 1)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out = enum.chunk("123", 1)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out = enum.at([], 0)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.at([], 1)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.at([], -1)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.at(["one"], 0)`, "one")
testEnumModule(t, `out = enum.at(["one"], 1)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.at(["one"], -1)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.at(["one","two","three"], 0)`, "one")
testEnumModule(t, `out = enum.at(["one","two","three"], 1)`, "two")
testEnumModule(t, `out = enum.at(["one","two","three"], 2)`, "three")
testEnumModule(t, `out = enum.at(["one","two","three"], -1)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.at(["one","two","three"], 3)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.at(["one","two","three"], "1")`, objects.UndefinedValue) // non-int index: undefined
testEnumModule(t, `out = enum.at({}, "a")`, objects.UndefinedValue)
testEnumModule(t, `out = enum.at({a:"one"}, "a")`, "one")
testEnumModule(t, `out = enum.at({a:"one"}, "b")`, objects.UndefinedValue)
testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, "a")`, "one")
testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, "b")`, "two")
testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, "c")`, "three")
testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, "d")`, objects.UndefinedValue)
testEnumModule(t, `out = enum.at({a:"one",b:"two",c:"three"}, 'a')`, objects.UndefinedValue) // non-string index: undefined
testEnumModule(t, `out = enum.at(0, 1)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out = enum.at("abc", 1)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out=0; enum.each([],func(k,v){out+=v})`, 0)
testEnumModule(t, `out=0; enum.each([1,2,3],func(k,v){out+=v})`, 6)
testEnumModule(t, `out=0; enum.each([1,2,3],func(k,v){out+=k})`, 3)
testEnumModule(t, `out=0; enum.each({a:1,b:2,c:3},func(k,v){out+=v})`, 6)
testEnumModule(t, `out=""; enum.each({a:1,b:2,c:3},func(k,v){out+=k}); out=len(out)`, 3)
testEnumModule(t, `out=0; enum.each(5,func(k,v){out+=v})`, 0) // non-enumerable: no iteration
testEnumModule(t, `out=0; enum.each("123",func(k,v){out+=v})`, 0) // non-enumerable: no iteration
testEnumModule(t, `out = enum.filter([], enum.value)`, ARR{})
testEnumModule(t, `out = enum.filter([false,1,2], enum.value)`, ARR{1, 2})
testEnumModule(t, `out = enum.filter([false,1,0,2], enum.value)`, ARR{1, 2})
testEnumModule(t, `out = enum.filter({}, enum.value)`, objects.UndefinedValue) // non-array: undefined
testEnumModule(t, `out = enum.filter(0, enum.value)`, objects.UndefinedValue) // non-array: undefined
testEnumModule(t, `out = enum.filter("123", enum.value)`, objects.UndefinedValue) // non-array: undefined
testEnumModule(t, `out = enum.find([], enum.value)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.find([0], enum.value)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.find([1], enum.value)`, 1)
testEnumModule(t, `out = enum.find([false,0,undefined,1], enum.value)`, 1)
testEnumModule(t, `out = enum.find([1,2,3], enum.value)`, 1)
testEnumModule(t, `out = enum.find({}, enum.value)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.find({a:0}, enum.value)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.find({a:1}, enum.value)`, 1)
testEnumModule(t, `out = enum.find({a:false,b:0,c:undefined,d:1}, enum.value)`, 1)
//testEnumModule(t, `out = enum.find({a:1,b:2,c:3}, enum.value)`, 1)
testEnumModule(t, `out = enum.find(0, enum.value)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out = enum.find("123", enum.value)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out = enum.find_key([], enum.value)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.find_key([0], enum.value)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.find_key([1], enum.value)`, 0)
testEnumModule(t, `out = enum.find_key([false,0,undefined,1], enum.value)`, 3)
testEnumModule(t, `out = enum.find_key([1,2,3], enum.value)`, 0)
testEnumModule(t, `out = enum.find_key({}, enum.value)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.find_key({a:0}, enum.value)`, objects.UndefinedValue)
testEnumModule(t, `out = enum.find_key({a:1}, enum.value)`, "a")
testEnumModule(t, `out = enum.find_key({a:false,b:0,c:undefined,d:1}, enum.value)`, "d")
//testEnumModule(t, `out = enum.find_key({a:1,b:2,c:3}, enum.value)`, "a")
testEnumModule(t, `out = enum.find_key(0, enum.value)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out = enum.find_key("123", enum.value)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out = enum.map([], enum.value)`, ARR{})
testEnumModule(t, `out = enum.map([1,2,3], enum.value)`, ARR{1, 2, 3})
testEnumModule(t, `out = enum.map([1,2,3], enum.key)`, ARR{0, 1, 2})
testEnumModule(t, `out = enum.map([1,2,3], func(k,v) { return v*2 })`, ARR{2, 4, 6})
testEnumModule(t, `out = enum.map({}, enum.value)`, ARR{})
testEnumModule(t, `out = enum.map({a:1}, func(k,v) { return v*2 })`, ARR{2})
testEnumModule(t, `out = enum.map(0, enum.value)`, objects.UndefinedValue) // non-enumerable: undefined
testEnumModule(t, `out = enum.map("123", enum.value)`, objects.UndefinedValue) // non-enumerable: undefined
}
func testEnumModule(t *testing.T, input string, expected interface{}) {
expect(t, `enum := import("enum"); `+input,
Opts().Module("enum", stdlib.SourceModules["enum"]),
expected)
}

49
runtime/vm_srcmod_test.go Normal file
View file

@ -0,0 +1,49 @@
package runtime_test
import "testing"
func TestSrcModEnum(t *testing.T) {
expect(t, `
x := import("enum")
out = x.all([1, 2, 3], func(_, v) { return v >= 1 })
`, Opts().Stdlib(), true)
expect(t, `
x := import("enum")
out = x.all([1, 2, 3], func(_, v) { return v >= 2 })
`, Opts().Stdlib(), false)
expect(t, `
x := import("enum")
out = x.any([1, 2, 3], func(_, v) { return v >= 1 })
`, Opts().Stdlib(), true)
expect(t, `
x := import("enum")
out = x.any([1, 2, 3], func(_, v) { return v >= 2 })
`, Opts().Stdlib(), true)
expect(t, `
x := import("enum")
out = x.chunk([1, 2, 3], 1)
`, Opts().Stdlib(), ARR{ARR{1}, ARR{2}, ARR{3}})
expect(t, `
x := import("enum")
out = x.chunk([1, 2, 3], 2)
`, Opts().Stdlib(), ARR{ARR{1, 2}, ARR{3}})
expect(t, `
x := import("enum")
out = x.chunk([1, 2, 3], 3)
`, Opts().Stdlib(), ARR{ARR{1, 2, 3}})
expect(t, `
x := import("enum")
out = x.chunk([1, 2, 3], 4)
`, Opts().Stdlib(), ARR{ARR{1, 2, 3}})
expect(t, `
x := import("enum")
out = x.chunk([1, 2, 3, 4, 5, 6], 2)
`, Opts().Stdlib(), ARR{ARR{1, 2}, ARR{3, 4}, ARR{5, 6}})
expect(t, `
x := import("enum")
out = x.at([1, 2, 3], 0)
`, Opts().Stdlib(), 1)
}

View file

@ -14,6 +14,7 @@ import (
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/objects"
"github.com/d5/tengo/runtime"
"github.com/d5/tengo/stdlib"
)
const testOut = "out"
@ -52,6 +53,11 @@ func (o *testopts) copy() *testopts {
return c
}
func (o *testopts) Stdlib() *testopts {
o.modules.AddMap(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
return o
}
func (o *testopts) Module(name string, mod interface{}) *testopts {
c := o.copy()
switch mod := mod.(type) {

View file

@ -1,7 +0,0 @@
package stdlib_test
import "testing"
func TestEnum(t *testing.T) {
}

View file

@ -7,6 +7,7 @@ import (
"io/ioutil"
"log"
"regexp"
"strconv"
)
var tengoModFileRE = regexp.MustCompile(`^srcmod_(\w+).tengo$`)
@ -41,7 +42,7 @@ package stdlib
// SourceModules are source type standard library modules.
var SourceModules = map[string]string{` + "\n")
for modName, modSrc := range modules {
out.WriteString("\t\"" + modName + "\": `" + modSrc + "`,\n")
out.WriteString("\t\"" + modName + "\": " + strconv.Quote(modSrc) + ",\n")
}
out.WriteString("}\n")

View file

@ -4,45 +4,5 @@ package stdlib
// SourceModules are source type standard library modules.
var SourceModules = map[string]string{
"enum": `export {
// all returns true if the given function fn evaluates to a truthy value on
// all of the items in the enumerable.
all: func(enumerable, fn) {
for k, v in enumerable {
if !fn(k, v) {
return false
}
}
return true
},
// any returns true if the given function fn evaluates to a truthy value on
// any of the items in the enumerable.
any: func(enumerable, fn) {
for k, v in enumerable {
if fn(k, v) {
return true
}
}
return false
},
// chunk returns an array of elements split into groups the length of size.
// If the enumerable can't be split evenly, the final chunk will be the
// remaining elements.
chunk: func(enumerable, size) {
numElements := len(enumerable)
if !numElements {
return []
}
res := []
idx := 0
for idx < numElements {
res = append(res, enumerable[idx:idx+size])
idx += size
}
return res
}
}
`,
"enum": "is_enumerable := func(x) {\n return is_array(x) || is_map(x) || is_immutable_array(x) || is_immutable_map(x)\n}\n\nis_array_like := func(x) {\n return is_array(x) || is_immutable_array(x)\n}\n\nexport {\n // all returns true if the given function `fn` evaluates to a truthy value on\n // all of the items in `x`. It returns undefined if `x` is not enumerable.\n all: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n if !fn(k, v) { return false }\n }\n\n return true\n },\n // any returns true if the given function `fn` evaluates to a truthy value on\n // any of the items in `x`. It returns undefined if `x` is not enumerable.\n any: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n if fn(k, v) { return true }\n }\n\n return false\n },\n // chunk returns an array of elements split into groups the length of size.\n // If `x` can't be split evenly, the final chunk will be the remaining elements.\n // It returns undefined if `x` is not array.\n chunk: func(x, size) {\n if !is_array_like(x) || !size { return undefined }\n\n numElements := len(x)\n if !numElements { return [] }\n\n res := []\n idx := 0\n for idx < numElements {\n res = append(res, x[idx:idx+size])\n idx += size\n }\n\n return res\n },\n // at returns an element at the given index (if `x` is array) or\n // key (if `x` is map). It returns undefined if `x` is not enumerable.\n at: func(x, key) {\n if !is_enumerable(x) { return undefined }\n\n if is_array_like(x) {\n if !is_int(key) { return undefined }\n } else {\n if !is_string(key) { return undefined }\n }\n\n return x[key]\n },\n // each iterates over elements of `x` and invokes `fn` for each element. `fn` is\n // invoked with two arguments: `key` and `value`. `key` is an int index\n // if `x` is array. `key` is a string key if `x` is map. It does not iterate\n // and returns undefined if `x` is not enumerable.\n each: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n fn(k, v)\n }\n },\n // filter iterates over elements of `x`, returning an array of all elements `fn`\n // returns truthy for. `fn` is invoked with two arguments: `key` and `value`.\n // `key` is an int index if `x` is array. `key` is a string key if `x` is map.\n // It returns undefined if `x` is not enumerable.\n filter: func(x, fn) {\n if !is_array_like(x) { return undefined }\n\n dst := []\n for k, v in x {\n if fn(k, v) { dst = append(dst, v) }\n }\n\n return dst\n },\n // find iterates over elements of `x`, returning value of the first element `fn`\n // returns truthy for. `fn` is invoked with two arguments: `key` and `value`.\n // `key` is an int index if `x` is array. `key` is a string key if `x` is map.\n // It returns undefined if `x` is not enumerable.\n find: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n if fn(k, v) { return v }\n }\n },\n // find_key iterates over elements of `x`, returning key or index of the first\n // element `fn` returns truthy for. `fn` is invoked with two arguments: `key`\n // and `value`. `key` is an int index if `x` is array. `key` is a string key if\n // `x` is map. It returns undefined if `x` is not enumerable.\n find_key: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n if fn(k, v) { return k }\n }\n },\n // map creates an array of values by running each element in `x` through `fn`.\n // `fn` is invoked with two arguments: `key` and `value`. `key` is an int index\n // if `x` is array. `key` is a string key if `x` is map. It returns undefined\n // if `x` is not enumerable.\n map: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n dst := []\n for k, v in x {\n dst = append(dst, fn(k, v))\n }\n\n return dst\n },\n // key returns the first argument.\n key: func(k, _) { return k },\n // value returns the second argument.\n value: func(_, v) { return v }\n}\n",
}

View file

@ -1,40 +1,128 @@
is_enumerable := func(x) {
return is_array(x) || is_map(x) || is_immutable_array(x) || is_immutable_map(x)
}
is_array_like := func(x) {
return is_array(x) || is_immutable_array(x)
}
export {
// all returns true if the given function fn evaluates to a truthy value on
// all of the items in the enumerable.
all: func(enumerable, fn) {
for k, v in enumerable {
if !fn(k, v) {
return false
}
// all returns true if the given function `fn` evaluates to a truthy value on
// all of the items in `x`. It returns undefined if `x` is not enumerable.
all: func(x, fn) {
if !is_enumerable(x) { return undefined }
for k, v in x {
if !fn(k, v) { return false }
}
return true
},
// any returns true if the given function fn evaluates to a truthy value on
// any of the items in the enumerable.
any: func(enumerable, fn) {
for k, v in enumerable {
if fn(k, v) {
return true
}
// any returns true if the given function `fn` evaluates to a truthy value on
// any of the items in `x`. It returns undefined if `x` is not enumerable.
any: func(x, fn) {
if !is_enumerable(x) { return undefined }
for k, v in x {
if fn(k, v) { return true }
}
return false
},
// chunk returns an array of elements split into groups the length of size.
// If the enumerable can't be split evenly, the final chunk will be the
// remaining elements.
chunk: func(enumerable, size) {
numElements := len(enumerable)
// If `x` can't be split evenly, the final chunk will be the remaining elements.
// It returns undefined if `x` is not array.
chunk: func(x, size) {
if !is_array_like(x) || !size { return undefined }
if !numElements {
return []
}
numElements := len(x)
if !numElements { return [] }
res := []
idx := 0
for idx < numElements {
res = append(res, enumerable[idx:idx+size])
res = append(res, x[idx:idx+size])
idx += size
}
return res
},
// at returns an element at the given index (if `x` is array) or
// key (if `x` is map). It returns undefined if `x` is not enumerable.
at: func(x, key) {
if !is_enumerable(x) { return undefined }
if is_array_like(x) {
if !is_int(key) { return undefined }
} else {
if !is_string(key) { return undefined }
}
return x[key]
},
// each iterates over elements of `x` and invokes `fn` for each element. `fn` is
// invoked with two arguments: `key` and `value`. `key` is an int index
// if `x` is array. `key` is a string key if `x` is map. It does not iterate
// and returns undefined if `x` is not enumerable.
each: func(x, fn) {
if !is_enumerable(x) { return undefined }
for k, v in x {
fn(k, v)
}
},
// filter iterates over elements of `x`, returning an array of all elements `fn`
// returns truthy for. `fn` is invoked with two arguments: `key` and `value`.
// `key` is an int index if `x` is array. `key` is a string key if `x` is map.
// It returns undefined if `x` is not enumerable.
filter: func(x, fn) {
if !is_array_like(x) { return undefined }
dst := []
for k, v in x {
if fn(k, v) { dst = append(dst, v) }
}
return dst
},
// find iterates over elements of `x`, returning value of the first element `fn`
// returns truthy for. `fn` is invoked with two arguments: `key` and `value`.
// `key` is an int index if `x` is array. `key` is a string key if `x` is map.
// It returns undefined if `x` is not enumerable.
find: func(x, fn) {
if !is_enumerable(x) { return undefined }
for k, v in x {
if fn(k, v) { return v }
}
},
// find_key iterates over elements of `x`, returning key or index of the first
// element `fn` returns truthy for. `fn` is invoked with two arguments: `key`
// and `value`. `key` is an int index if `x` is array. `key` is a string key if
// `x` is map. It returns undefined if `x` is not enumerable.
find_key: func(x, fn) {
if !is_enumerable(x) { return undefined }
for k, v in x {
if fn(k, v) { return k }
}
},
// map creates an array of values by running each element in `x` through `fn`.
// `fn` is invoked with two arguments: `key` and `value`. `key` is an int index
// if `x` is array. `key` is a string key if `x` is map. It returns undefined
// if `x` is not enumerable.
map: func(x, fn) {
if !is_enumerable(x) { return undefined }
dst := []
for k, v in x {
dst = append(dst, fn(k, v))
}
return dst
},
// key returns the first argument.
key: func(k, _) { return k },
// value returns the second argument.
value: func(_, v) { return v }
}