diff --git a/compiler/stdlib/func_typedefs.go b/compiler/stdlib/func_typedefs.go index 375b29d..987ca6b 100644 --- a/compiler/stdlib/func_typedefs.go +++ b/compiler/stdlib/func_typedefs.go @@ -395,6 +395,31 @@ func FuncASRS(fn func(string) string) *objects.UserFunction { } } +// FuncASRSs transform a function of 'func(string) []string' signature into a user function object. +func FuncASRSs(fn func(string) []string) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + res := fn(s1) + + arr := &objects.Array{} + for _, osArg := range res { + arr.Value = append(arr.Value, &objects.String{Value: osArg}) + } + + return arr, nil + }, + } +} + // FuncASRSE transform a function of 'func(string) (string, error)' signature into a user function object. // User function will return 'true' if underlying native function returns nil. func FuncASRSE(fn func(string) (string, error)) *objects.UserFunction { @@ -462,6 +487,171 @@ func FuncASSRE(fn func(string, string) error) *objects.UserFunction { } } +// FuncASSRSs transform a function of 'func(string, string) []string' signature into a user function object. +func FuncASSRSs(fn func(string, string) []string) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + arr := &objects.Array{} + for _, res := range fn(s1, s2) { + arr.Value = append(arr.Value, &objects.String{Value: res}) + } + + return arr, nil + }, + } +} + +// FuncASSIRSs transform a function of 'func(string, string, int) []string' signature into a user function object. +func FuncASSIRSs(fn func(string, string, int) []string) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (objects.Object, error) { + if len(args) != 3 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + arr := &objects.Array{} + for _, res := range fn(s1, s2, i3) { + arr.Value = append(arr.Value, &objects.String{Value: res}) + } + + return arr, nil + }, + } +} + +// FuncASSRI transform a function of 'func(string, string) int' signature into a user function object. +func FuncASSRI(fn func(string, string) int) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + return &objects.Int{Value: int64(fn(s1, s2))}, nil + }, + } +} + +// FuncASSRS transform a function of 'func(string, string) string' signature into a user function object. +func FuncASSRS(fn func(string, string) string) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + return &objects.String{Value: fn(s1, s2)}, nil + }, + } +} + +// FuncASSRB transform a function of 'func(string, string) bool' signature into a user function object. +func FuncASSRB(fn func(string, string) bool) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + if fn(s1, s2) { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + }, + } +} + +// FuncASsSRS transform a function of 'func([]string, string) string' signature into a user function object. +func FuncASsSRS(fn func([]string, string) string) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + var ss1 []string + arr, ok := args[0].(*objects.Array) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + for _, a := range arr.Value { + as, ok := objects.ToString(a) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + ss1 = append(ss1, as) + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + return &objects.String{Value: fn(ss1, s2)}, nil + }, + } +} + // FuncASI64RE transform a function of 'func(string, int64) error' signature // into a user function object. func FuncASI64RE(fn func(string, int64) error) *objects.UserFunction { @@ -510,6 +700,30 @@ func FuncAIIRE(fn func(int, int) error) *objects.UserFunction { } } +// FuncASIRS transform a function of 'func(string, int) string' signature +// into a user function object. +func FuncASIRS(fn func(string, int) string) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + return &objects.String{Value: fn(s1, i2)}, nil + }, + } +} + // FuncASIIRE transform a function of 'func(string, int, int) error' signature // into a user function object. func FuncASIIRE(fn func(string, int, int) error) *objects.UserFunction { @@ -607,8 +821,8 @@ func FuncAIRSsE(fn func(int) ([]string, error)) *objects.UserFunction { } arr := &objects.Array{} - for _, osArg := range res { - arr.Value = append(arr.Value, &objects.String{Value: osArg}) + for _, r := range res { + arr.Value = append(arr.Value, &objects.String{Value: r}) } return arr, nil diff --git a/compiler/stdlib/func_typedefs_test.go b/compiler/stdlib/func_typedefs_test.go index 7d24f85..cfbdf8c 100644 --- a/compiler/stdlib/func_typedefs_test.go +++ b/compiler/stdlib/func_typedefs_test.go @@ -2,6 +2,8 @@ package stdlib_test import ( "errors" + "strconv" + "strings" "testing" "github.com/d5/tengo/assert" @@ -115,6 +117,15 @@ func TestFuncASRS(t *testing.T) { assert.Equal(t, objects.ErrWrongNumArguments, err) } +func TestFuncASRSs(t *testing.T) { + uf := stdlib.FuncASRSs(func(a string) []string { return []string{a} }) + ret, err := uf.Call(&objects.String{Value: "foo"}) + assert.NoError(t, err) + assert.Equal(t, array(&objects.String{Value: "foo"}), ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + func TestFuncASI64RE(t *testing.T) { uf := stdlib.FuncASI64RE(func(a string, b int64) error { return nil }) ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5}) @@ -168,7 +179,24 @@ func TestFuncASRSE(t *testing.T) { } func TestFuncASSRE(t *testing.T) { + uf := stdlib.FuncASSRE(func(a, b string) error { return nil }) + ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}) + assert.NoError(t, err) + uf = stdlib.FuncASSRE(func(a, b string) error { return errors.New("some error") }) + ret, err = uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}) + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call(&objects.String{Value: "foo"}) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} +func TestFuncASsRS(t *testing.T) { + uf := stdlib.FuncASsSRS(func(a []string, b string) string { return strings.Join(a, b) }) + ret, err := uf.Call(array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), &objects.String{Value: " "}) + assert.NoError(t, err) + assert.Equal(t, &objects.String{Value: "foo bar"}, ret) + ret, err = uf.Call(&objects.String{Value: "foo"}) + assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncARF(t *testing.T) { @@ -247,6 +275,17 @@ func TestFuncAFFRF(t *testing.T) { assert.Equal(t, objects.ErrWrongNumArguments, err) } +func TestFuncASIRS(t *testing.T) { + uf := stdlib.FuncASIRS(func(a string, b int) string { return strings.Repeat(a, b) }) + ret, err := uf.Call(&objects.String{Value: "ab"}, &objects.Int{Value: 2}) + assert.NoError(t, err) + assert.Equal(t, &objects.String{Value: "abab"}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + func TestFuncAIFRF(t *testing.T) { uf := stdlib.FuncAIFRF(func(a int, b float64) float64 { return float64(a) + b @@ -303,6 +342,24 @@ func TestFuncAIRSsE(t *testing.T) { assert.Equal(t, objects.ErrWrongNumArguments, err) } +func TestFuncASSRSs(t *testing.T) { + uf := stdlib.FuncASSRSs(func(a, b string) []string { return []string{a, b} }) + ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}) + assert.NoError(t, err) + assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncASSIRSs(t *testing.T) { + uf := stdlib.FuncASSIRSs(func(a, b string, c int) []string { return []string{a, b, strconv.Itoa(c)} }) + ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}, &objects.Int{Value: 5}) + assert.NoError(t, err) + assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}, &objects.String{Value: "5"}), ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + func TestFuncARB(t *testing.T) { uf := stdlib.FuncARB(func() bool { return true }) ret, err := uf.Call() @@ -355,6 +412,33 @@ func TestFuncAYRIE(t *testing.T) { assert.Equal(t, objects.ErrWrongNumArguments, err) } +func TestFuncASSRI(t *testing.T) { + uf := stdlib.FuncASSRI(func(a, b string) int { return len(a) + len(b) }) + ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}) + assert.NoError(t, err) + assert.Equal(t, &objects.Int{Value: 6}, ret) + ret, err = uf.Call(&objects.String{Value: "foo"}) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncASSRS(t *testing.T) { + uf := stdlib.FuncASSRS(func(a, b string) string { return a + b }) + ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}) + assert.NoError(t, err) + assert.Equal(t, &objects.String{Value: "foobar"}, ret) + ret, err = uf.Call(&objects.String{Value: "foo"}) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncASSRB(t *testing.T) { + uf := stdlib.FuncASSRB(func(a, b string) bool { return len(a) > len(b) }) + ret, err := uf.Call(&objects.String{Value: "123"}, &objects.String{Value: "12"}) + assert.NoError(t, err) + assert.Equal(t, objects.TrueValue, ret) + ret, err = uf.Call(&objects.String{Value: "foo"}) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + func array(elements ...objects.Object) *objects.Array { return &objects.Array{Value: elements} } diff --git a/compiler/stdlib/text.go b/compiler/stdlib/text.go index 40a5810..19b2bc4 100644 --- a/compiler/stdlib/text.go +++ b/compiler/stdlib/text.go @@ -2,6 +2,7 @@ package stdlib import ( "regexp" + "strings" "github.com/d5/tengo/objects" ) @@ -232,6 +233,98 @@ var textModule = map[string]objects.Object{ return }, }, + + // compare(a, b) => int + "compare": FuncASSRI(strings.Compare), + // contains(s, substr) => bool + "contains": FuncASSRB(strings.Contains), + // contains_any(s, chars) => bool + "contains_any": FuncASSRB(strings.ContainsAny), + // count(s, substr) => int + "count": FuncASSRI(strings.Count), + // "equal_fold(s, t) => bool + "equal_fold": FuncASSRB(strings.EqualFold), + // fields(s) => array(string) + "fields": FuncASRSs(strings.Fields), + // has_prefix(s, prefix) => bool + "has_prefix": FuncASSRB(strings.HasPrefix), + // has_suffix(s, suffix) => bool + "has_suffix": FuncASSRB(strings.HasSuffix), + // index(s, substr) => int + "index": FuncASSRI(strings.Index), + // index_any(s, chars) => int + "index_any": FuncASSRI(strings.IndexAny), + // join(arr, sep) => string + "join": FuncASsSRS(strings.Join), + // last_index(s, substr) => int + "last_index": FuncASSRI(strings.LastIndex), + // last_index_any(s, chars) => int + "last_index_any": FuncASSRI(strings.LastIndexAny), + // repeat(s, count) => string + "repeat": FuncASIRS(strings.Repeat), + // replace(s, old, new, n) => string + "replace": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 4 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + + s3, ok := objects.ToString(args[2]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + + i4, ok := objects.ToInt(args[3]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + + ret = &objects.String{Value: strings.Replace(s1, s2, s3, i4)} + + return + }, + }, + // split(s, sep) => []string + "split": FuncASSRSs(strings.Split), + // split_after(s, sep) => []string + "split_after": FuncASSRSs(strings.SplitAfter), + // split_after_n(s, sep, n) => []string + "split_after_n": FuncASSIRSs(strings.SplitAfterN), + // split_n(s, sep, n) => []string + "split_n": FuncASSIRSs(strings.SplitN), + // title(s) => string + "title": FuncASRS(strings.Title), + // to_lower(s) => string + "to_lower": FuncASRS(strings.ToLower), + // to_title(s) => string + "to_title": FuncASRS(strings.ToTitle), + // to_upper(s) => string + "to_upper": FuncASRS(strings.ToUpper), + // trim_left(s, cutset) => string + "trim_left": FuncASSRS(strings.TrimLeft), + // trim_prefix(s, prefix) => string + "trim_prefix": FuncASSRS(strings.TrimPrefix), + // trim_right(s, cutset) => string + "trim_right": FuncASSRS(strings.TrimRight), + // trim_space(s) => string + "trim_space": FuncASRS(strings.TrimSpace), + // trim_suffix(s, suffix) => string + "trim_suffix": FuncASSRS(strings.TrimSuffix), } func stringsRegexpImmutableMap(re *regexp.Regexp) *objects.ImmutableMap { diff --git a/compiler/stdlib/text_test.go b/compiler/stdlib/text_test.go index 46450fc..17fcb22 100644 --- a/compiler/stdlib/text_test.go +++ b/compiler/stdlib/text_test.go @@ -159,5 +159,25 @@ func TestTextRE(t *testing.T) { module(t, "text").call("re_split", d.pattern, d.text, d.count).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) module(t, "text").call("re_compile", d.pattern).call("split", d.text, d.count).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) } - +} + +func TestText(t *testing.T) { + module(t, "text").call("compare", "", "").expect(0) + module(t, "text").call("compare", "", "a").expect(-1) + module(t, "text").call("compare", "a", "").expect(1) + module(t, "text").call("compare", "a", "a").expect(0) + module(t, "text").call("compare", "a", "b").expect(-1) + module(t, "text").call("compare", "b", "a").expect(1) + module(t, "text").call("compare", "abcde", "abcde").expect(0) + module(t, "text").call("compare", "abcde", "abcdf").expect(-1) + module(t, "text").call("compare", "abcdf", "abcde").expect(1) + + module(t, "text").call("contains", "", "").expect(true) + module(t, "text").call("contains", "", "a").expect(false) + module(t, "text").call("contains", "a", "").expect(true) + module(t, "text").call("contains", "a", "a").expect(true) + module(t, "text").call("contains", "abcde", "a").expect(true) + module(t, "text").call("contains", "abcde", "abcde").expect(true) + module(t, "text").call("contains", "abc", "abcde").expect(false) + module(t, "text").call("contains", "ab cd", "bc").expect(false) } diff --git a/script/variable_test.go b/script/variable_test.go index 1a521ee..8f67c32 100644 --- a/script/variable_test.go +++ b/script/variable_test.go @@ -60,7 +60,6 @@ func TestVariable(t *testing.T) { Name: "d", Value: nil, ValueType: "undefined", - StringValue: "", Object: objects.UndefinedValue, IsUndefined: true, },