diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..35a964e0
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/tengo.iml b/.idea/tengo.iml
new file mode 100644
index 00000000..5e764c4f
--- /dev/null
+++ b/.idea/tengo.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/thriftCompiler.xml b/.idea/thriftCompiler.xml
new file mode 100644
index 00000000..7bc123c6
--- /dev/null
+++ b/.idea/thriftCompiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 00000000..a4c732c3
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,9 @@
+
+
+ {
+ "keyToString": {
+ "go.import.settings.migrated": "true",
+ "settings.editor.selected.configurable": "ssh.settings"
+ }
+}
+
\ No newline at end of file
diff --git a/stdlib/source_modules.go b/stdlib/source_modules.go
index 50e09287..83a849b5 100644
--- a/stdlib/source_modules.go
+++ b/stdlib/source_modules.go
@@ -5,4 +5,5 @@ package stdlib
// SourceModules are source type standard library modules.
var SourceModules = map[string]string{
"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. It returns undefined if `x` is not array.\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",
+ "sort": "_sort := func(arr, left, right, less) {\n if right-left <= 0 {\n return arr\n }\n\ti := left\n\n\tfor j := left; j < right; j++ {\n\t\tif less(j, right) {\n\t if i != j {\n tmp := arr[i]\n arr[i] = arr[j]\n arr[j] = tmp\n }\n\t\t\ti++\n\t\t}\n\t}\n\n if i != right {\n tmp := arr[i]\n arr[i] = arr[right]\n arr[right] = tmp\n }\n\n\t_sort(arr, left, i-1, less)\n\t_sort(arr, i+1, right, less)\n\treturn arr\n}\n\nexport {\n\tsort: func(arr, less) {\n\t\treturn _sort(arr, 0, len(arr)-1, less)\n\t}\n}",
}
diff --git a/stdlib/srcmod_sort.tengo b/stdlib/srcmod_sort.tengo
new file mode 100644
index 00000000..d2549129
--- /dev/null
+++ b/stdlib/srcmod_sort.tengo
@@ -0,0 +1,33 @@
+_sort := func(arr, left, right, less) {
+ if right-left <= 0 {
+ return arr
+ }
+ i := left
+
+ for j := left; j < right; j++ {
+ if less(j, right) {
+ if i != j {
+ tmp := arr[i]
+ arr[i] = arr[j]
+ arr[j] = tmp
+ }
+ i++
+ }
+ }
+
+ if i != right {
+ tmp := arr[i]
+ arr[i] = arr[right]
+ arr[right] = tmp
+ }
+
+ _sort(arr, left, i-1, less)
+ _sort(arr, i+1, right, less)
+ return arr
+}
+
+export {
+ sort: func(arr, less) {
+ return _sort(arr, 0, len(arr)-1, less)
+ }
+}
\ No newline at end of file
diff --git a/stdlib/stdlib_test.go b/stdlib/stdlib_test.go
index ecd59788..5aa7c9b6 100644
--- a/stdlib/stdlib_test.go
+++ b/stdlib/stdlib_test.go
@@ -1,7 +1,11 @@
package stdlib_test
import (
+ "encoding/json"
"fmt"
+ "math/rand"
+ "sort"
+ "strings"
"testing"
"time"
@@ -72,6 +76,81 @@ if !is_error(cmd) {
}
+func TestSortModule(t *testing.T) {
+ // normal
+ expect(t, `
+sort := import("sort")
+a := [4, 5, 3, 1, 2]
+a = sort.sort(a, func(i, j) {
+ return a[i] < a[j]
+})
+out := import("json").encode(a)
+`, []byte("[1,2,3,4,5]"))
+
+ // generator random sequence
+ rand.Seed(time.Now().UnixNano())
+ generateRandomSequence := func() []int {
+ length := 1 + rand.Intn(100)
+ arr := make([]int, length)
+ for i := 0; i < length; i++ {
+ arr[i] = rand.Intn(1000000)
+ }
+ return arr
+ }
+ for i := 0; i < 300; i++ {
+ seq := generateRandomSequence()
+ seqBytes, _ := json.Marshal(seq)
+ sort.Ints(seq)
+ sortResult, _ := json.Marshal(seq)
+ expect(t, fmt.Sprintf(`
+sort := import("sort")
+a := %s
+a = sort.sort(a, func(i, j) {
+ return a[i] < a[j]
+})
+out := import("json").encode(a)
+`, string(seqBytes)), sortResult)
+ }
+
+ // less is not a function
+ expectErr(t, `
+sort := import("sort")
+a := [4, 5, 3, 1, 2]
+a = sort.sort(a, 0)
+out := import("json").encode(a)`, "Runtime Error: not callable: int")
+
+ // arr is not an array
+ expectErr(t, `
+sort := import("sort")
+a := 12345
+a = sort.sort(a, func(i, j) {
+ return a[i] < a[j]
+})
+out := import("json").encode(a)`, "Runtime Error: invalid type for argument 'first' in call to 'builtin-function:len': expected array/s")
+
+ // empty array
+ expect(t, `
+sort := import("sort")
+a := []
+a = sort.sort(a, func(i, j) {
+ return a[i] < a[j]
+})
+out := import("json").encode(a)`, []byte("[]"))
+
+ // sort json
+ expect(t, `
+sort := import("sort")
+a := [{"age": 12, "name": "A"}, {"age": 18, "name": "B"}, {"age": 9, "name": "C"}, {"age": 10, "name": "D"}, {"age": 21, "name": "E"}]
+a = sort.sort(a, func(i, j) {
+ return a[i].age < a[j].age
+})
+out := []
+for item in a {
+ out = append(out, item.name)
+}
+out = import("json").encode(out)`, []byte(`["C","D","A","B","E"]`))
+}
+
func TestGetModules(t *testing.T) {
mods := stdlib.GetModuleMap()
require.Equal(t, 0, mods.Len())
@@ -240,3 +319,10 @@ func expect(t *testing.T, input string, expected interface{}) {
require.NotNil(t, v)
require.Equal(t, expected, v.Value())
}
+
+func expectErr(t *testing.T, input string, errMsg string) {
+ s := tengo.NewScript([]byte(input))
+ s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
+ _, err := s.Run()
+ require.True(t, strings.Contains(err.Error(), errMsg))
+}