Repository: abs-lang/abs
Pull request #338 (2020-03-23 23:12:37)

array.chunk(x)
Splits the array into chunks of the given size:

```bash
[1, 2, 3].chunk(2) # [[1, 2], [3]]
[1, 2, 3].chunk(10) # [[1,2,3]]
[1, 2, 3].chunk(1.2) # argument to chunk must be a positive integer, got '1.2'
```


Patch:
diff --git a/docs/types/array.md b/docs/types/array.md
index a2213b4..18d0563 100644
--- a/docs/types/array.md
+++ b/docs/types/array.md
@@ -146,6 +146,16 @@ of strings or numbers:
 [1, 2, 3].contains(4) # false
 ```
 
+### chunk(size)
+
+Splits the array into chunks of the given size:
+
+```bash
+[1, 2, 3].chunk(2) # [[1, 2], [3]]
+[1, 2, 3].chunk(10) # [[1,2,3]]
+[1, 2, 3].chunk(1.2) # argument to chunk must be a positive integer, got '1.2'
+```
+
 ### every(f)
 
 Returns true when all elements in the array
diff --git a/evaluator/functions.go b/evaluator/functions.go
index ec4cc8f..df00f8a 100644
--- a/evaluator/functions.go
+++ b/evaluator/functions.go
@@ -141,6 +141,11 @@ func getFns() map[string]*object.Builtin {
 			Types: []string{object.FUNCTION_OBJ, object.BUILTIN_OBJ},
 			Fn:    callFn,
 		},
+		// chnk([...], int:2)
+		"chunk": &object.Builtin{
+			Types: []string{object.ARRAY_OBJ},
+			Fn:    chunkFn,
+		},
 		// split(string:"hello")
 		"split": &object.Builtin{
 			Types: []string{object.STRING_OBJ},
@@ -849,6 +854,36 @@ func callFn(tok token.Token, env *object.Environment, args ...object.Object) obj
 	return applyFunction(tok, args[0], env, args[1].(*object.Array).Elements)
 }
 
+// chunk([...], integer:2)
+func chunkFn(tok token.Token, env *object.Environment, args ...object.Object) object.Object {
+	err := validateArgs(tok, "chunk", args, 2, [][]string{{object.ARRAY_OBJ}, {object.NUMBER_OBJ}})
+	if err != nil {
+		return err
+	}
+
+	number := args[1].(*object.Number)
+	size := int(number.Value)
+
+	if size < 1 || !number.IsInt() {
+		return newError(tok, "argument to chunk must be a positive integer, got '%s'", number.Inspect())
+	}
+
+	var chunks []object.Object
+	elements := args[0].(*object.Array).Elements
+
+	for i := 0; i < len(elements); i += size {
+		end := i + size
+
+		if end > len(elements) {
+			end = len(elements)
+		}
+
+		chunks = append(chunks, &object.Array{Elements: elements[i:end]})
+	}
+
+	return &object.Array{Elements: chunks}
+}
+
 // split(string:"hello world!", sep:" ")
 func splitFn(tok token.Token, env *object.Environment, args ...object.Object) object.Object {
 	err, spec := validateVarArgs(tok, "split", args, [][][]string{
diff --git a/object/object.go b/object/object.go
index 47440f7..0fba0d2 100644
--- a/object/object.go
+++ b/object/object.go
@@ -74,11 +74,14 @@ func (n *Number) Type() ObjectType { return NUMBER_OBJ }
 // If it's a float, let's remove as many zeroes
 // as possible (1.10000 becomes 1.1).
 func (n *Number) Inspect() string {
-	if n.Value == float64(int64(n.Value)) {
+	if n.IsInt() {
 		return fmt.Sprintf("%d", int64(n.Value))
 	}
 	return strconv.FormatFloat(n.Value, 'f', -1, 64)
 }
+func (n *Number) IsInt() bool {
+	return n.Value == float64(int64(n.Value))
+}
 func (n *Number) Json() string       { return n.Inspect() }
 func (n *Number) ZeroValue() float64 { return float64(0) }
 func (n *Number) Int() int           { return int(n.Value) }


Test patch:
diff --git a/evaluator/builtin_functions_test.go b/evaluator/builtin_functions_test.go
index 2ff8387..46153ea 100644
--- a/evaluator/builtin_functions_test.go
+++ b/evaluator/builtin_functions_test.go
@@ -539,6 +539,24 @@ c")`, []string{"a", "b", "c"}},
 	testBuiltinFunction(tests, t)
 }
 
+func TestChunk(t *testing.T) {
+	tests := []Tests{
+		{`chunk([1,2,3,4,5])`, "wrong number of arguments to chunk(...): got=1, want=2"},
+		{`x = chunk([1,2,3,4,5], 2); len(x)`, 3},
+		{`x = chunk([1,2,3,4,5], 2); x[0]`, []int{1, 2}},
+		{`x = chunk([1,2,3,4,5], 2); x[1]`, []int{3, 4}},
+		{`x = chunk([1,2,3,4,5], 2); x[2]`, []int{5}},
+		{`x = chunk([1,2,3,4,5], 0);`, "argument to chunk must be a positive integer, got '0'"},
+		{`x = chunk([1,2,3,4,5], -1);`, "argument to chunk must be a positive integer, got '-1'"},
+		{`x = chunk([1,2,3,4,5], -1.5);`, "argument to chunk must be a positive integer, got '-1.5'"},
+		{`x = chunk([1,2,3,4,5], 1.5);`, "argument to chunk must be a positive integer, got '1.5'"},
+		{`x = chunk([], 10); len(x)`, 0},
+		{`x = chunk([], 10); x`, []int{}},
+	}
+
+	testBuiltinFunction(tests, t)
+}
+
 func testBuiltinFunction(tests []Tests, t *testing.T) {
 	for _, tt := range tests {
 		evaluated := testEval(tt.input)