Repository: d5/tengo (go)

Issue:
builtin function splice
JS `splice` function looks like [this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)
```js
let arrDeletedItems = array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
```
I can implement it like this for tengo
```golang
deleted_items := splice(array, start[, delete_count[, item1[, item2[, ...]]]])
```
if `delete_count` is not provided, JS deletes the array items starting from the `start` to the end. If `delete_count` is provided, after deletion (if greater than 0), item1, item2, etc. are inserted to the original array. If `delete_count` is 0 and items are provided then it inserts the items to the array starting from `start` index and nothing deleted from the array. It appends, deletes and inserts with the same function.
What do you think?

Pull request:
builtin splice
closes #252 , closes #250 

Patch:
diff --git a/builtins.go b/builtins.go
index a89e72f..fcda81c 100644
--- a/builtins.go
+++ b/builtins.go
@@ -17,6 +17,10 @@ var builtinFuncs = []*BuiltinFunction{
 		Name:  "delete",
 		Value: builtinDelete,
 	},
+	{
+		Name:  "splice",
+		Value: builtinSplice,
+	},
 	{
 		Name:  "string",
 		Value: builtinString,
@@ -532,3 +536,76 @@ func builtinDelete(args ...Object) (Object, error) {
 		}
 	}
 }
+
+// builtinSplice deletes and changes given Array, returns deleted items.
+// usage:
+// deleted_items := splice(array[,start[,delete_count[,item1[,item2[,...]]]])
+func builtinSplice(args ...Object) (Object, error) {
+	argsLen := len(args)
+	if argsLen == 0 {
+		return nil, ErrWrongNumArguments
+	}
+
+	array, ok := args[0].(*Array)
+	if !ok {
+		return nil, ErrInvalidArgumentType{
+			Name:     "first",
+			Expected: "array",
+			Found:    args[0].TypeName(),
+		}
+	}
+	arrayLen := len(array.Value)
+
+	var startIdx int
+	if argsLen > 1 {
+		arg1, ok := args[1].(*Int)
+		if !ok {
+			return nil, ErrInvalidArgumentType{
+				Name:     "second",
+				Expected: "int",
+				Found:    args[1].TypeName(),
+			}
+		}
+		startIdx = int(arg1.Value)
+		if startIdx < 0 || startIdx > arrayLen {
+			return nil, ErrIndexOutOfBounds
+		}
+	}
+
+	delCount := len(array.Value)
+	if argsLen > 2 {
+		arg2, ok := args[2].(*Int)
+		if !ok {
+			return nil, ErrInvalidArgumentType{
+				Name:     "third",
+				Expected: "int",
+				Found:    args[2].TypeName(),
+			}
+		}
+		delCount = int(arg2.Value)
+		if delCount < 0 {
+			return nil, ErrIndexOutOfBounds
+		}
+	}
+	// if count of to be deleted items is bigger than expected, truncate it
+	if startIdx+delCount > arrayLen {
+		delCount = arrayLen - startIdx
+	}
+	// delete items
+	endIdx := startIdx + delCount
+	deleted := append([]Object{}, array.Value[startIdx:endIdx]...)
+
+	head := array.Value[:startIdx]
+	var items []Object
+	if argsLen > 3 {
+		items = make([]Object, 0, argsLen-3)
+		for i := 3; i < argsLen; i++ {
+			items = append(items, args[i])
+		}
+	}
+	items = append(items, array.Value[endIdx:]...)
+	array.Value = append(head, items...)
+
+	// return deleted items
+	return &Array{Value: deleted}, nil
+}
diff --git a/docs/builtins.md b/docs/builtins.md
index 23db51e..fc15833 100644
--- a/docs/builtins.md
+++ b/docs/builtins.md
@@ -67,6 +67,65 @@ delete({}) // runtime error, second argument is missing
 delete({}, 1) // runtime error, second argument must be a string type
 ```
 
+## splice
+
+Deletes and/or changes the contents of a given array and returns
+deleted items as a new array. `splice` is similar to
+JS `Array.prototype.splice()` except splice is a builtin function and
+first argument must an array. First argument must be an array, and
+if second and third arguments are provided those must be integers
+otherwise runtime error is returned.
+
+Usage:
+
+`deleted_items := splice(array[, start[, delete_count[, item1[, item2[, ...]]]])`
+
+```golang
+v := [1, 2, 3]
+items := splice(v, 0) // items == [1, 2, 3], v == []
+```
+
+```golang
+v := [1, 2, 3]
+items := splice(v, 1) // items == [2, 3], v == [1]
+```
+
+```golang
+v := [1, 2, 3]
+items := splice(v, 0, 1) // items == [1], v == [2, 3]
+```
+
+```golang
+// deleting
+v := ["a", "b", "c"]
+items := splice(v, 1, 2) // items == ["b", "c"], v == ["a"]
+// splice(v, 1, 3) or splice(v, 1, 99) has same effect for this example
+```
+
+```golang
+// appending
+v := ["a", "b", "c"]
+items := splice(v, 3, 0, "d", "e") // items == [], v == ["a", "b", "c", "d", "e"]
+```
+
+```golang
+// replacing
+v := ["a", "b", "c"]
+items := splice(v, 2, 1, "d") // items == ["c"], v == ["a", "b", "d"]
+```
+
+```golang
+// inserting
+v := ["a", "b", "c"]
+items := splice(v, 0, 0, "d", "e") // items == [], v == ["d", "e", "a", "b", "c"]
+```
+
+```golang
+// deleting and inserting
+v := ["a", "b", "c"]
+items := splice(v, 1, 1, "d", "e") // items == ["b"], v == ["a", "d", "e", "c"]
+```
+
 ## type_name
 
 Returns the type_name of an object.


Test patch:
diff --git a/builtins_test.go b/builtins_test.go
index 4f0d837..2f2e47c 100644
--- a/builtins_test.go
+++ b/builtins_test.go
@@ -1,79 +1,113 @@
-package tengo
+package tengo_test
 
 import (
 	"errors"
 	"reflect"
 	"testing"
+
+	"github.com/d5/tengo/v2"
 )
 
 func Test_builtinDelete(t *testing.T) {
+	var builtinDelete func(args ...tengo.Object) (tengo.Object, error)
+	for _, f := range tengo.GetAllBuiltinFunctions() {
+		if f.Name == "delete" {
+			builtinDelete = f.Value
+			break
+		}
+	}
+	if builtinDelete == nil {
+		t.Fatal("builtin delete not found")
+	}
 	type args struct {
-		args []Object
+		args []tengo.Object
 	}
 	tests := []struct {
 		name      string
 		args      args
-		want      Object
+		want      tengo.Object
 		wantErr   bool
 		wantedErr error
 		target    interface{}
 	}{
-		//Map
-		{name: "invalid-arg", args: args{[]Object{&String{}, &String{}}}, wantErr: true,
-			wantedErr: ErrInvalidArgumentType{Name: "first", Expected: "map", Found: "string"}},
-		{name: "no-args", wantErr: true, wantedErr: ErrWrongNumArguments},
-		{name: "empty-args", args: args{[]Object{}}, wantErr: true, wantedErr: ErrWrongNumArguments},
-		{name: "3-args", args: args{[]Object{(*Map)(nil), (*String)(nil), (*String)(nil)}}, wantErr: true, wantedErr: ErrWrongNumArguments},
-		{name: "nil-map-empty-key", args: args{[]Object{&Map{}, &String{}}}, want: UndefinedValue},
-		{name: "nil-map-nonstr-key", args: args{[]Object{&Map{}, &Int{}}}, wantErr: true,
-			wantedErr: ErrInvalidArgumentType{Name: "second", Expected: "string", Found: "int"}},
-		{name: "nil-map-no-key", args: args{[]Object{&Map{}}}, wantErr: true,
-			wantedErr: ErrWrongNumArguments},
+		{name: "invalid-arg", args: args{[]tengo.Object{&tengo.String{},
+			&tengo.String{}}}, wantErr: true,
+			wantedErr: tengo.ErrInvalidArgumentType{
+				Name:     "first",
+				Expected: "map",
+				Found:    "string"},
+		},
+		{name: "no-args",
+			wantErr: true, wantedErr: tengo.ErrWrongNumArguments},
+		{name: "empty-args", args: args{[]tengo.Object{}}, wantErr: true,
+			wantedErr: tengo.ErrWrongNumArguments,
+		},
+		{name: "3-args", args: args{[]tengo.Object{
+			(*tengo.Map)(nil), (*tengo.String)(nil), (*tengo.String)(nil)}},
+			wantErr: true, wantedErr: tengo.ErrWrongNumArguments,
+		},
+		{name: "nil-map-empty-key",
+			args: args{[]tengo.Object{&tengo.Map{}, &tengo.String{}}},
+			want: tengo.UndefinedValue,
+		},
+		{name: "nil-map-nonstr-key",
+			args: args{[]tengo.Object{
+				&tengo.Map{}, &tengo.Int{}}}, wantErr: true,
+			wantedErr: tengo.ErrInvalidArgumentType{
+				Name: "second", Expected: "string", Found: "int"},
+		},
+		{name: "nil-map-no-key",
+			args: args{[]tengo.Object{&tengo.Map{}}}, wantErr: true,
+			wantedErr: tengo.ErrWrongNumArguments,
+		},
 		{name: "map-missing-key",
 			args: args{
-				[]Object{
-					&Map{Value: map[string]Object{
-						"key": &String{Value: "value"},
+				[]tengo.Object{
+					&tengo.Map{Value: map[string]tengo.Object{
+						"key": &tengo.String{Value: "value"},
 					}},
-					&String{Value: "key1"},
-				}},
-			want:   UndefinedValue,
-			target: &Map{Value: map[string]Object{"key": &String{Value: "value"}}},
+					&tengo.String{Value: "key1"}}},
+			want: tengo.UndefinedValue,
+			target: &tengo.Map{
+				Value: map[string]tengo.Object{
+					"key": &tengo.String{
+						Value: "value"}}},
 		},
 		{name: "map-emptied",
 			args: args{
-				[]Object{
-					&Map{Value: map[string]Object{
-						"key": &String{Value: "value"},
+				[]tengo.Object{
+					&tengo.Map{Value: map[string]tengo.Object{
+						"key": &tengo.String{Value: "value"},
 					}},
-					&String{Value: "key"},
-				}},
-			want:   UndefinedValue,
-			target: &Map{Value: map[string]Object{}},
+					&tengo.String{Value: "key"}}},
+			want:   tengo.UndefinedValue,
+			target: &tengo.Map{Value: map[string]tengo.Object{}},
 		},
 		{name: "map-multi-keys",
 			args: args{
-				[]Object{
-					&Map{Value: map[string]Object{
-						"key1": &String{Value: "value1"},
-						"key2": &Int{Value: 10},
+				[]tengo.Object{
+					&tengo.Map{Value: map[string]tengo.Object{
+						"key1": &tengo.String{Value: "value1"},
+						"key2": &tengo.Int{Value: 10},
 					}},
-					&String{Value: "key1"},
-				}},
-			want:   UndefinedValue,
-			target: &Map{Value: map[string]Object{"key2": &Int{Value: 10}}},
+					&tengo.String{Value: "key1"}}},
+			want: tengo.UndefinedValue,
+			target: &tengo.Map{Value: map[string]tengo.Object{
+				"key2": &tengo.Int{Value: 10}}},
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			got, err := builtinDelete(tt.args.args...)
 			if (err != nil) != tt.wantErr {
-				t.Errorf("builtinDelete() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("builtinDelete() error = %v, wantErr %v",
+					err, tt.wantErr)
 				return
 			}
 			if tt.wantErr && !errors.Is(err, tt.wantedErr) {
 				if err.Error() != tt.wantedErr.Error() {
-					t.Errorf("builtinDelete() error = %v, wantedErr %v", err, tt.wantedErr)
+					t.Errorf("builtinDelete() error = %v, wantedErr %v",
+						err, tt.wantedErr)
 					return
 				}
 			}
@@ -83,15 +117,237 @@ func Test_builtinDelete(t *testing.T) {
 			}
 			if !tt.wantErr && tt.target != nil {
 				switch v := tt.args.args[0].(type) {
-				case *Map, *Array:
+				case *tengo.Map, *tengo.Array:
 					if !reflect.DeepEqual(tt.target, tt.args.args[0]) {
-						t.Errorf("builtinDelete() objects are not equal got: %+v, want: %+v", tt.args.args[0], tt.target)
+						t.Errorf("builtinDelete() objects are not equal "+
+							"got: %+v, want: %+v", tt.args.args[0], tt.target)
 					}
 				default:
-					t.Errorf("builtinDelete() unsuporrted arg[0] type %s", v.TypeName())
+					t.Errorf("builtinDelete() unsuporrted arg[0] type %s",
+						v.TypeName())
 					return
 				}
 			}
 		})
 	}
 }
+
+func Test_builtinSplice(t *testing.T) {
+	var builtinSplice func(args ...tengo.Object) (tengo.Object, error)
+	for _, f := range tengo.GetAllBuiltinFunctions() {
+		if f.Name == "splice" {
+			builtinSplice = f.Value
+			break
+		}
+	}
+	if builtinSplice == nil {
+		t.Fatal("builtin splice not found")
+	}
+	tests := []struct {
+		name      string
+		args      []tengo.Object
+		deleted   tengo.Object
+		Array     *tengo.Array
+		wantErr   bool
+		wantedErr error
+	}{
+		{name: "no args", args: []tengo.Object{}, wantErr: true,
+			wantedErr: tengo.ErrWrongNumArguments,
+		},
+		{name: "invalid args", args: []tengo.Object{&tengo.Map{}},
+			wantErr: true,
+			wantedErr: tengo.ErrInvalidArgumentType{
+				Name: "first", Expected: "array", Found: "map"},
+		},
+		{name: "invalid args",
+			args:    []tengo.Object{&tengo.Array{}, &tengo.String{}},
+			wantErr: true,
+			wantedErr: tengo.ErrInvalidArgumentType{
+				Name: "second", Expected: "int", Found: "string"},
+		},
+		{name: "negative index",
+			args:      []tengo.Object{&tengo.Array{}, &tengo.Int{Value: -1}},
+			wantErr:   true,
+			wantedErr: tengo.ErrIndexOutOfBounds},
+		{name: "non int count",
+			args: []tengo.Object{
+				&tengo.Array{}, &tengo.Int{Value: 0},
+				&tengo.String{Value: ""}},
+			wantErr: true,
+			wantedErr: tengo.ErrInvalidArgumentType{
+				Name: "third", Expected: "int", Found: "string"},
+		},
+		{name: "negative count",
+			args: []tengo.Object{
+				&tengo.Array{Value: []tengo.Object{
+					&tengo.Int{Value: 0},
+					&tengo.Int{Value: 1},
+					&tengo.Int{Value: 2}}},
+				&tengo.Int{Value: 0},
+				&tengo.Int{Value: -1}},
+			wantErr:   true,
+			wantedErr: tengo.ErrIndexOutOfBounds,
+		},
+		{name: "insert with zero count",
+			args: []tengo.Object{
+				&tengo.Array{Value: []tengo.Object{
+					&tengo.Int{Value: 0},
+					&tengo.Int{Value: 1},
+					&tengo.Int{Value: 2}}},
+				&tengo.Int{Value: 0},
+				&tengo.Int{Value: 0},
+				&tengo.String{Value: "b"}},
+			deleted: &tengo.Array{Value: []tengo.Object{}},
+			Array: &tengo.Array{Value: []tengo.Object{
+				&tengo.String{Value: "b"},
+				&tengo.Int{Value: 0},
+				&tengo.Int{Value: 1},
+				&tengo.Int{Value: 2}}},
+		},
+		{name: "insert",
+			args: []tengo.Object{
+				&tengo.Array{Value: []tengo.Object{
+					&tengo.Int{Value: 0},
+					&tengo.Int{Value: 1},
+					&tengo.Int{Value: 2}}},
+				&tengo.Int{Value: 1},
+				&tengo.Int{Value: 0},
+				&tengo.String{Value: "c"},
+				&tengo.String{Value: "d"}},
+			deleted: &tengo.Array{Value: []tengo.Object{}},
+			Array: &tengo.Array{Value: []tengo.Object{
+				&tengo.Int{Value: 0},
+				&tengo.String{Value: "c"},
+				&tengo.String{Value: "d"},
+				&tengo.Int{Value: 1},
+				&tengo.Int{Value: 2}}},
+		},
+		{name: "insert with zero count",
+			args: []tengo.Object{
+				&tengo.Array{Value: []tengo.Object{
+					&tengo.Int{Value: 0},
+					&tengo.Int{Value: 1},
+					&tengo.Int{Value: 2}}},
+				&tengo.Int{Value: 1},
+				&tengo.Int{Value: 0},
+				&tengo.String{Value: "c"},
+				&tengo.String{Value: "d"}},
+			deleted: &tengo.Array{Value: []tengo.Object{}},
+			Array: &tengo.Array{Value: []tengo.Object{
+				&tengo.Int{Value: 0},
+				&tengo.String{Value: "c"},
+				&tengo.String{Value: "d"},
+				&tengo.Int{Value: 1},
+				&tengo.Int{Value: 2}}},
+		},
+		{name: "insert with delete",
+			args: []tengo.Object{
+				&tengo.Array{Value: []tengo.Object{
+					&tengo.Int{Value: 0},
+					&tengo.Int{Value: 1},
+					&tengo.Int{Value: 2}}},
+				&tengo.Int{Value: 1},
+				&tengo.Int{Value: 1},
+				&tengo.String{Value: "c"},
+				&tengo.String{Value: "d"}},
+			deleted: &tengo.Array{
+				Value: []tengo.Object{&tengo.Int{Value: 1}}},
+			Array: &tengo.Array{Value: []tengo.Object{
+				&tengo.Int{Value: 0},
+				&tengo.String{Value: "c"},
+				&tengo.String{Value: "d"},
+				&tengo.Int{Value: 2}}},
+		},
+		{name: "insert with delete multi",
+			args: []tengo.Object{
+				&tengo.Array{Value: []tengo.Object{
+					&tengo.Int{Value: 0},
+					&tengo.Int{Value: 1},
+					&tengo.Int{Value: 2}}},
+				&tengo.Int{Value: 1},
+				&tengo.Int{Value: 2},
+				&tengo.String{Value: "c"},
+				&tengo.String{Value: "d"}},
+			deleted: &tengo.Array{Value: []tengo.Object{
+				&tengo.Int{Value: 1},
+				&tengo.Int{Value: 2}}},
+			Array: &tengo.Array{
+				Value: []tengo.Object{
+					&tengo.Int{Value: 0},
+					&tengo.String{Value: "c"},
+					&tengo.String{Value: "d"}}},
+		},
+		{name: "delete all with positive count",
+			args: []tengo.Object{
+				&tengo.Array{Value: []tengo.Object{
+					&tengo.Int{Value: 0},
+					&tengo.Int{Value: 1},
+					&tengo.Int{Value: 2}}},
+				&tengo.Int{Value: 0},
+				&tengo.Int{Value: 3}},
+			deleted: &tengo.Array{Value: []tengo.Object{
+				&tengo.Int{Value: 0},
+				&tengo.Int{Value: 1},
+				&tengo.Int{Value: 2}}},
+			Array: &tengo.Array{Value: []tengo.Object{}},
+		},
+		{name: "delete all with big count",
+			args: []tengo.Object{
+				&tengo.Array{Value: []tengo.Object{
+					&tengo.Int{Value: 0},
+					&tengo.Int{Value: 1},
+					&tengo.Int{Value: 2}}},
+				&tengo.Int{Value: 0},
+				&tengo.Int{Value: 5}},
+			deleted: &tengo.Array{Value: []tengo.Object{
+				&tengo.Int{Value: 0},
+				&tengo.Int{Value: 1},
+				&tengo.Int{Value: 2}}},
+			Array: &tengo.Array{Value: []tengo.Object{}},
+		},
+		{name: "nothing2",
+			args: []tengo.Object{
+				&tengo.Array{Value: []tengo.Object{
+					&tengo.Int{Value: 0},
+					&tengo.Int{Value: 1},
+					&tengo.Int{Value: 2}}}},
+			Array: &tengo.Array{Value: []tengo.Object{}},
+			deleted: &tengo.Array{Value: []tengo.Object{
+				&tengo.Int{Value: 0},
+				&tengo.Int{Value: 1},
+				&tengo.Int{Value: 2}}},
+		},
+		{name: "pop without count",
+			args: []tengo.Object{
+				&tengo.Array{Value: []tengo.Object{
+					&tengo.Int{Value: 0},
+					&tengo.Int{Value: 1},
+					&tengo.Int{Value: 2}}},
+				&tengo.Int{Value: 2}},
+			deleted: &tengo.Array{Value: []tengo.Object{&tengo.Int{Value: 2}}},
+			Array: &tengo.Array{Value: []tengo.Object{
+				&tengo.Int{Value: 0}, &tengo.Int{Value: 1}}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := builtinSplice(tt.args...)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("builtinSplice() error = %v, wantErr %v",
+					err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.deleted) {
+				t.Errorf("builtinSplice() = %v, want %v", got, tt.deleted)
+			}
+			if tt.wantErr && tt.wantedErr.Error() != err.Error() {
+				t.Errorf("builtinSplice() error = %v, wantedErr %v",
+					err, tt.wantedErr)
+			}
+			if tt.Array != nil && !reflect.DeepEqual(tt.Array, tt.args[0]) {
+				t.Errorf("builtinSplice() arrays are not equal expected"+
+					" %s, got %s", tt.Array, tt.args[0].(*tengo.Array))
+			}
+		})
+	}
+}
diff --git a/vm_test.go b/vm_test.go
index b59806e..4ffaed7 100644
--- a/vm_test.go
+++ b/vm_test.go
@@ -735,31 +735,164 @@ func TestBuiltinFunction(t *testing.T) {
 	expectError(t, `delete(1, 1)`, nil, `invalid type for argument 'first'`)
 	expectError(t, `delete(1.0, 1)`, nil, `invalid type for argument 'first'`)
 	expectError(t, `delete("str", 1)`, nil, `invalid type for argument 'first'`)
-	expectError(t, `delete(bytes("str"), 1)`, nil, `invalid type for argument 'first'`)
-	expectError(t, `delete(error("err"), 1)`, nil, `invalid type for argument 'first'`)
+	expectError(t, `delete(bytes("str"), 1)`, nil,
+		`invalid type for argument 'first'`)
+	expectError(t, `delete(error("err"), 1)`, nil,
+		`invalid type for argument 'first'`)
 	expectError(t, `delete(true, 1)`, nil, `invalid type for argument 'first'`)
-	expectError(t, `delete(char('c'), 1)`, nil, `invalid type for argument 'first'`)
-	expectError(t, `delete(undefined, 1)`, nil, `invalid type for argument 'first'`)
-	expectError(t, `delete(time(1257894000), 1)`, nil, `invalid type for argument 'first'`)
-	expectError(t, `delete(immutable({}), "key")`, nil, `invalid type for argument 'first'`)
-	expectError(t, `delete(immutable([]), "")`, nil, `invalid type for argument 'first'`)
+	expectError(t, `delete(char('c'), 1)`, nil,
+		`invalid type for argument 'first'`)
+	expectError(t, `delete(undefined, 1)`, nil,
+		`invalid type for argument 'first'`)
+	expectError(t, `delete(time(1257894000), 1)`, nil,
+		`invalid type for argument 'first'`)
+	expectError(t, `delete(immutable({}), "key")`, nil,
+		`invalid type for argument 'first'`)
+	expectError(t, `delete(immutable([]), "")`, nil,
+		`invalid type for argument 'first'`)
 	expectError(t, `delete([], "")`, nil, `invalid type for argument 'first'`)
 	expectError(t, `delete({}, 1)`, nil, `invalid type for argument 'second'`)
 	expectError(t, `delete({}, 1.0)`, nil, `invalid type for argument 'second'`)
-	expectError(t, `delete({}, undefined)`, nil, `invalid type for argument 'second'`)
+	expectError(t, `delete({}, undefined)`, nil,
+		`invalid type for argument 'second'`)
 	expectError(t, `delete({}, [])`, nil, `invalid type for argument 'second'`)
 	expectError(t, `delete({}, {})`, nil, `invalid type for argument 'second'`)
-	expectError(t, `delete({}, error("err"))`, nil, `invalid type for argument 'second'`)
-	expectError(t, `delete({}, bytes("str"))`, nil, `invalid type for argument 'second'`)
-	expectError(t, `delete({}, char(35))`, nil, `invalid type for argument 'second'`)
-	expectError(t, `delete({}, time(1257894000))`, nil, `invalid type for argument 'second'`)
-	expectError(t, `delete({}, immutable({}))`, nil, `invalid type for argument 'second'`)
-	expectError(t, `delete({}, immutable([]))`, nil, `invalid type for argument 'second'`)
+	expectError(t, `delete({}, error("err"))`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `delete({}, bytes("str"))`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `delete({}, char(35))`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `delete({}, time(1257894000))`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `delete({}, immutable({}))`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `delete({}, immutable([]))`, nil,
+		`invalid type for argument 'second'`)
 
 	expectRun(t, `out = delete({}, "")`, nil, tengo.UndefinedValue)
 	expectRun(t, `out = {key1: 1}; delete(out, "key1")`, nil, MAP{})
-	expectRun(t, `out = {key1: 1, key2: "2"}; delete(out, "key1")`, nil, MAP{"key2": "2"})
-	expectRun(t, `out = [1, "2", {a: "b", c: 10}]; delete(out[2], "c")`, nil, ARR{1, "2", MAP{"a": "b"}})
+	expectRun(t, `out = {key1: 1, key2: "2"}; delete(out, "key1")`, nil,
+		MAP{"key2": "2"})
+	expectRun(t, `out = [1, "2", {a: "b", c: 10}]; delete(out[2], "c")`, nil,
+		ARR{1, "2", MAP{"a": "b"}})
+
+	// splice
+	expectError(t, `splice()`, nil, tengo.ErrWrongNumArguments.Error())
+	expectError(t, `splice(1)`, nil, `invalid type for argument 'first'`)
+	expectError(t, `splice(1.0)`, nil, `invalid type for argument 'first'`)
+	expectError(t, `splice("str")`, nil, `invalid type for argument 'first'`)
+	expectError(t, `splice(bytes("str"))`, nil,
+		`invalid type for argument 'first'`)
+	expectError(t, `splice(error("err"))`, nil,
+		`invalid type for argument 'first'`)
+	expectError(t, `splice(true)`, nil, `invalid type for argument 'first'`)
+	expectError(t, `splice(char('c'))`, nil,
+		`invalid type for argument 'first'`)
+	expectError(t, `splice(undefined)`, nil,
+		`invalid type for argument 'first'`)
+	expectError(t, `splice(time(1257894000))`, nil,
+		`invalid type for argument 'first'`)
+	expectError(t, `splice(immutable({}))`, nil,
+		`invalid type for argument 'first'`)
+	expectError(t, `splice(immutable([]))`, nil,
+		`invalid type for argument 'first'`)
+	expectError(t, `splice({})`, nil, `invalid type for argument 'first'`)
+	expectError(t, `splice([], 1.0)`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `splice([], "str")`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `splice([], bytes("str"))`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `splice([], error("error"))`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `splice([], false)`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `splice([], char('d'))`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `splice([], undefined)`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `splice([], time(0))`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `splice([], [])`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `splice([], {})`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `splice([], immutable([]))`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `splice([], immutable({}))`, nil,
+		`invalid type for argument 'second'`)
+	expectError(t, `splice([], 0, 1.0)`, nil,
+		`invalid type for argument 'third'`)
+	expectError(t, `splice([], 0, "string")`, nil,
+		`invalid type for argument 'third'`)
+	expectError(t, `splice([], 0, bytes("string"))`, nil,
+		`invalid type for argument 'third'`)
+	expectError(t, `splice([], 0, error("string"))`, nil,
+		`invalid type for argument 'third'`)
+	expectError(t, `splice([], 0, true)`, nil,
+		`invalid type for argument 'third'`)
+	expectError(t, `splice([], 0, char('f'))`, nil,
+		`invalid type for argument 'third'`)
+	expectError(t, `splice([], 0, undefined)`, nil,
+		`invalid type for argument 'third'`)
+	expectError(t, `splice([], 0, time(0))`, nil,
+		`invalid type for argument 'third'`)
+	expectError(t, `splice([], 0, [])`, nil,
+		`invalid type for argument 'third'`)
+	expectError(t, `splice([], 0, {})`, nil,
+		`invalid type for argument 'third'`)
+	expectError(t, `splice([], 0, immutable([]))`, nil,
+		`invalid type for argument 'third'`)
+	expectError(t, `splice([], 0, immutable({}))`, nil,
+		`invalid type for argument 'third'`)
+	expectError(t, `splice([], 1)`, nil, tengo.ErrIndexOutOfBounds.Error())
+	expectError(t, `splice([1, 2, 3], 0, -1)`, nil,
+		tengo.ErrIndexOutOfBounds.Error())
+	expectError(t, `splice([1, 2, 3], 99, 0, "a", "b")`, nil,
+		tengo.ErrIndexOutOfBounds.Error())
+	expectRun(t, `out = []; splice(out)`, nil, ARR{})
+	expectRun(t, `out = ["a"]; splice(out, 1)`, nil, ARR{"a"})
+	expectRun(t, `out = ["a"]; out = splice(out, 1)`, nil, ARR{})
+	expectRun(t, `out = [1, 2, 3]; splice(out, 0, 1)`, nil, ARR{2, 3})
+	expectRun(t, `out = [1, 2, 3]; out = splice(out, 0, 1)`, nil, ARR{1})
+	expectRun(t, `out = [1, 2, 3]; splice(out, 0, 0, "a", "b")`, nil,
+		ARR{"a", "b", 1, 2, 3})
+	expectRun(t, `out = [1, 2, 3]; out = splice(out, 0, 0, "a", "b")`, nil,
+		ARR{})
+	expectRun(t, `out = [1, 2, 3]; splice(out, 1, 0, "a", "b")`, nil,
+		ARR{1, "a", "b", 2, 3})
+	expectRun(t, `out = [1, 2, 3]; out = splice(out, 1, 0, "a", "b")`, nil,
+		ARR{})
+	expectRun(t, `out = [1, 2, 3]; splice(out, 1, 0, "a", "b")`, nil,
+		ARR{1, "a", "b", 2, 3})
+	expectRun(t, `out = [1, 2, 3]; splice(out, 2, 0, "a", "b")`, nil,
+		ARR{1, 2, "a", "b", 3})
+	expectRun(t, `out = [1, 2, 3]; splice(out, 3, 0, "a", "b")`, nil,
+		ARR{1, 2, 3, "a", "b"})
+	expectRun(t, `array := [1, 2, 3]; deleted := splice(array, 1, 1, "a", "b");
+				out = [deleted, array]`, nil, ARR{ARR{2}, ARR{1, "a", "b", 3}})
+	expectRun(t, `array := [1, 2, 3]; deleted := splice(array, 1); 
+		out = [deleted, array]`, nil, ARR{ARR{2, 3}, ARR{1}})
+	expectRun(t, `out = []; splice(out, 0, 0, "a", "b")`, nil, ARR{"a", "b"})
+	expectRun(t, `out = []; splice(out, 0, 1, "a", "b")`, nil, ARR{"a", "b"})
+	expectRun(t, `out = []; out = splice(out, 0, 0, "a", "b")`, nil, ARR{})
+	expectRun(t, `out = splice(splice([1, 2, 3], 0, 3), 1, 3)`, nil, ARR{2, 3})
+	// splice doc examples
+	expectRun(t, `v := [1, 2, 3]; deleted := splice(v, 0);
+		out = [deleted, v]`, nil, ARR{ARR{1, 2, 3}, ARR{}})
+	expectRun(t, `v := [1, 2, 3]; deleted := splice(v, 1);
+		out = [deleted, v]`, nil, ARR{ARR{2, 3}, ARR{1}})
+	expectRun(t, `v := [1, 2, 3]; deleted := splice(v, 0, 1);
+		out = [deleted, v]`, nil, ARR{ARR{1}, ARR{2, 3}})
+	expectRun(t, `v := ["a", "b", "c"]; deleted := splice(v, 1, 2);
+		out = [deleted, v]`, nil, ARR{ARR{"b", "c"}, ARR{"a"}})
+	expectRun(t, `v := ["a", "b", "c"]; deleted := splice(v, 2, 1, "d");
+		out = [deleted, v]`, nil, ARR{ARR{"c"}, ARR{"a", "b", "d"}})
+	expectRun(t, `v := ["a", "b", "c"]; deleted := splice(v, 0, 0, "d", "e");
+		out = [deleted, v]`, nil, ARR{ARR{}, ARR{"d", "e", "a", "b", "c"}})
+	expectRun(t, `v := ["a", "b", "c"]; deleted := splice(v, 1, 1, "d", "e");
+		out = [deleted, v]`, nil, ARR{ARR{"b"}, ARR{"a", "d", "e", "c"}})
 }
 
 func TestBytesN(t *testing.T) {