diff --git a/docs/builtins.md b/docs/builtins.md index 38d4252..6cd0003 100644 --- a/docs/builtins.md +++ b/docs/builtins.md @@ -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`. diff --git a/docs/runtime-types.md b/docs/runtime-types.md index eb1e6a9..e5779f1 100644 --- a/docs/runtime-types.md +++ b/docs/runtime-types.md @@ -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 @@ -76,4 +77,5 @@ _* time.Unix(): use `time.Unix(v, 0)` to convert to Time_ - `is_immutable_map(x)`: return `true` if `x` is immutable map; `false` otherwise - `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 \ No newline at end of file +- `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. \ No newline at end of file diff --git a/docs/stdlib-enum.md b/docs/stdlib-enum.md new file mode 100644 index 0000000..033e898 --- /dev/null +++ b/docs/stdlib-enum.md @@ -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. \ No newline at end of file diff --git a/docs/stdlib.md b/docs/stdlib.md index e051f75..ccb4f1c 100644 --- a/docs/stdlib.md +++ b/docs/stdlib.md @@ -6,4 +6,5 @@ - [times](https://github.com/d5/tengo/blob/master/docs/stdlib-times.md): time-related functions - [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 \ No newline at end of file +- [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 \ No newline at end of file diff --git a/objects/builtin_type_checks.go b/objects/builtin_type_checks.go index 960f782..d1e8471 100644 --- a/objects/builtin_type_checks.go +++ b/objects/builtin_type_checks.go @@ -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 +} diff --git a/objects/builtins.go b/objects/builtins.go index 67aec46..bfd004d 100644 --- a/objects/builtins.go +++ b/objects/builtins.go @@ -83,6 +83,10 @@ var Builtins = []*BuiltinFunction{ Name: "is_immutable_map", Value: builtinIsImmutableMap, }, + { + Name: "is_iterable", + Value: builtinIsIterable, + }, { Name: "is_time", Value: builtinIsTime, diff --git a/objects/bytes.go b/objects/bytes.go index 16b6168..6710c7c 100644 --- a/objects/bytes.go +++ b/objects/bytes.go @@ -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), + } +} diff --git a/objects/bytes_iterator.go b/objects/bytes_iterator.go new file mode 100644 index 0000000..18a36e1 --- /dev/null +++ b/objects/bytes_iterator.go @@ -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 "" +} + +// 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])} +} diff --git a/objects/module_map.go b/objects/module_map.go index 7b4d3bb..874b8a2 100644 --- a/objects/module_map.go +++ b/objects/module_map.go @@ -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 + } +} diff --git a/runtime/vm_source_modules_test.go b/runtime/vm_source_modules_test.go index ab5ca3a..660170c 100644 --- a/runtime/vm_source_modules_test.go +++ b/runtime/vm_source_modules_test.go @@ -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) } diff --git a/runtime/vm_srcmod_test.go b/runtime/vm_srcmod_test.go new file mode 100644 index 0000000..e7a0f6f --- /dev/null +++ b/runtime/vm_srcmod_test.go @@ -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) +} diff --git a/runtime/vm_test.go b/runtime/vm_test.go index ed56ac3..152b9e7 100644 --- a/runtime/vm_test.go +++ b/runtime/vm_test.go @@ -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) { diff --git a/stdlib/enum_test.go b/stdlib/enum_test.go deleted file mode 100644 index 393399f..0000000 --- a/stdlib/enum_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package stdlib_test - -import "testing" - -func TestEnum(t *testing.T) { - -} diff --git a/stdlib/gensrcmods.go b/stdlib/gensrcmods.go index e048e00..fada66b 100644 --- a/stdlib/gensrcmods.go +++ b/stdlib/gensrcmods.go @@ -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") diff --git a/stdlib/source_modules.go b/stdlib/source_modules.go index cadc87c..ca69d7d 100644 --- a/stdlib/source_modules.go +++ b/stdlib/source_modules.go @@ -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", } diff --git a/stdlib/srcmod_enum.tengo b/stdlib/srcmod_enum.tengo index 914d78a..7a5ea63 100644 --- a/stdlib/srcmod_enum.tengo +++ b/stdlib/srcmod_enum.tengo @@ -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 } + + numElements := len(x) + if !numElements { return [] } - 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 } }