Skip to content

Commit

Permalink
'enum' module (#166)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
d5 authored Mar 27, 2019
1 parent b9c1c92 commit 2f86800
Show file tree
Hide file tree
Showing 16 changed files with 417 additions and 77 deletions.
4 changes: 4 additions & 0 deletions docs/builtins.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
4 changes: 3 additions & 1 deletion docs/runtime-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
- `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 changes: 19 additions & 0 deletions docs/stdlib-enum.md
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 2 additions & 1 deletion docs/stdlib.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
- [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
12 changes: 12 additions & 0 deletions objects/builtin_type_checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 4 additions & 0 deletions objects/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ var Builtins = []*BuiltinFunction{
Name: "is_immutable_map",
Value: builtinIsImmutableMap,
},
{
Name: "is_iterable",
Value: builtinIsIterable,
},
{
Name: "is_time",
Value: builtinIsTime,
Expand Down
8 changes: 8 additions & 0 deletions objects/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 57 additions & 0 deletions objects/bytes_iterator.go
Original file line number Diff line number Diff line change
@@ -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])}
}
7 changes: 7 additions & 0 deletions objects/module_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
133 changes: 131 additions & 2 deletions runtime/vm_source_modules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 })`,
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"]),
true)
expected)
}
49 changes: 49 additions & 0 deletions runtime/vm_srcmod_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
6 changes: 6 additions & 0 deletions runtime/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 0 additions & 7 deletions stdlib/enum_test.go

This file was deleted.

Loading

0 comments on commit 2f86800

Please sign in to comment.