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)) +}