diff --git a/assert/assert.go b/assert/assert.go index 55ebfb8..4c3a4ce 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -15,8 +15,6 @@ import ( // NoError asserts err is not an error. func NoError(t *testing.T, err error, msg ...interface{}) bool { - t.Helper() - if err == nil { return true } @@ -26,8 +24,6 @@ func NoError(t *testing.T, err error, msg ...interface{}) bool { // Error asserts err is an error. func Error(t *testing.T, err error, msg ...interface{}) bool { - t.Helper() - if err != nil { return true } @@ -37,8 +33,6 @@ func Error(t *testing.T, err error, msg ...interface{}) bool { // Nil asserts v is nil. func Nil(t *testing.T, v interface{}, msg ...interface{}) bool { - t.Helper() - if v == nil { return true } @@ -48,8 +42,6 @@ func Nil(t *testing.T, v interface{}, msg ...interface{}) bool { // True asserts v is true. func True(t *testing.T, v bool, msg ...interface{}) bool { - t.Helper() - if v { return true } @@ -59,8 +51,6 @@ func True(t *testing.T, v bool, msg ...interface{}) bool { // False asserts vis false. func False(t *testing.T, v bool, msg ...interface{}) bool { - t.Helper() - if !v { return true } @@ -70,8 +60,6 @@ func False(t *testing.T, v bool, msg ...interface{}) bool { // NotNil asserts v is not nil. func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool { - t.Helper() - if v != nil { return true } @@ -81,8 +69,6 @@ func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool { // IsType asserts expected and actual are of the same type. func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool { - t.Helper() - if reflect.TypeOf(expected) == reflect.TypeOf(actual) { return true } @@ -92,15 +78,13 @@ func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool // Equal asserts expected and actual are equal. func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool { - t.Helper() - if expected == nil { return Nil(t, actual, "expected nil, but got not nil") } if !NotNil(t, actual, "expected not nil, but got nil") { return false } - if !IsType(t, expected, actual) { + if !IsType(t, expected, actual, msg...) { return false } @@ -150,43 +134,43 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool return failExpectedActual(t, expected, actual, msg...) } case []objects.Object: - return equalObjectSlice(t, expected, actual.([]objects.Object)) + return equalObjectSlice(t, expected, actual.([]objects.Object), msg...) case *objects.Int: - return Equal(t, expected.Value, actual.(*objects.Int).Value) + return Equal(t, expected.Value, actual.(*objects.Int).Value, msg...) case *objects.Float: - return Equal(t, expected.Value, actual.(*objects.Float).Value) + return Equal(t, expected.Value, actual.(*objects.Float).Value, msg...) case *objects.String: - return Equal(t, expected.Value, actual.(*objects.String).Value) + return Equal(t, expected.Value, actual.(*objects.String).Value, msg...) case *objects.Char: - return Equal(t, expected.Value, actual.(*objects.Char).Value) + return Equal(t, expected.Value, actual.(*objects.Char).Value, msg...) case *objects.Bool: if expected != actual { return failExpectedActual(t, expected, actual, msg...) } case *objects.ReturnValue: - return Equal(t, expected.Value, actual.(objects.ReturnValue).Value) + return Equal(t, expected.Value, actual.(objects.ReturnValue).Value, msg...) case *objects.Array: - return equalObjectSlice(t, expected.Value, actual.(*objects.Array).Value) + return equalObjectSlice(t, expected.Value, actual.(*objects.Array).Value, msg...) case *objects.ImmutableArray: - return equalObjectSlice(t, expected.Value, actual.(*objects.ImmutableArray).Value) + return equalObjectSlice(t, expected.Value, actual.(*objects.ImmutableArray).Value, msg...) case *objects.Bytes: if bytes.Compare(expected.Value, actual.(*objects.Bytes).Value) != 0 { return failExpectedActual(t, string(expected.Value), string(actual.(*objects.Bytes).Value), msg...) } case *objects.Map: - return equalObjectMap(t, expected.Value, actual.(*objects.Map).Value) + return equalObjectMap(t, expected.Value, actual.(*objects.Map).Value, msg...) case *objects.ImmutableMap: - return equalObjectMap(t, expected.Value, actual.(*objects.ImmutableMap).Value) + return equalObjectMap(t, expected.Value, actual.(*objects.ImmutableMap).Value, msg...) case *objects.CompiledFunction: - return equalCompiledFunction(t, expected, actual.(*objects.CompiledFunction)) + return equalCompiledFunction(t, expected, actual.(*objects.CompiledFunction), msg...) case *objects.Closure: - return equalClosure(t, expected, actual.(*objects.Closure)) + return equalClosure(t, expected, actual.(*objects.Closure), msg...) case *objects.Undefined: if expected != actual { return failExpectedActual(t, expected, actual, msg...) } case *objects.Error: - return Equal(t, expected.Value, actual.(*objects.Error).Value) + return Equal(t, expected.Value, actual.(*objects.Error).Value, msg...) case error: if expected != actual.(error) { return failExpectedActual(t, expected, actual, msg...) @@ -200,8 +184,6 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool // Fail marks the function as having failed but continues execution. func Fail(t *testing.T, msg ...interface{}) bool { - t.Helper() - t.Logf("\nError trace:\n\t%s\n%s", strings.Join(errorTrace(), "\n\t"), message(msg...)) t.Fail() @@ -210,8 +192,6 @@ func Fail(t *testing.T, msg ...interface{}) bool { } func failExpectedActual(t *testing.T, expected, actual interface{}, msg ...interface{}) bool { - t.Helper() - var addMsg string if len(msg) > 0 { addMsg = "\nMessage: " + message(msg...) @@ -260,15 +240,15 @@ func equalSymbol(a, b compiler.Symbol) bool { a.Scope == b.Scope } -func equalObjectSlice(t *testing.T, expected, actual []objects.Object) bool { +func equalObjectSlice(t *testing.T, expected, actual []objects.Object, msg ...interface{}) bool { // TODO: this test does not differentiate nil vs empty slice - if !Equal(t, len(expected), len(actual)) { + if !Equal(t, len(expected), len(actual), msg...) { return false } for i := 0; i < len(expected); i++ { - if !Equal(t, expected[i], actual[i]) { + if !Equal(t, expected[i], actual[i], msg...) { return false } } @@ -276,15 +256,15 @@ func equalObjectSlice(t *testing.T, expected, actual []objects.Object) bool { return true } -func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object) bool { - if !Equal(t, len(expected), len(actual)) { +func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object, msg ...interface{}) bool { + if !Equal(t, len(expected), len(actual), msg...) { return false } for key, expectedVal := range expected { actualVal := actual[key] - if !Equal(t, expectedVal, actualVal) { + if !Equal(t, expectedVal, actualVal, msg...) { return false } } @@ -292,27 +272,27 @@ func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object) bo return true } -func equalCompiledFunction(t *testing.T, expected, actual objects.Object) bool { +func equalCompiledFunction(t *testing.T, expected, actual objects.Object, msg ...interface{}) bool { expectedT := expected.(*objects.CompiledFunction) actualT := actual.(*objects.CompiledFunction) - return Equal(t, expectedT.Instructions, actualT.Instructions) + return Equal(t, expectedT.Instructions, actualT.Instructions, msg...) } -func equalClosure(t *testing.T, expected, actual objects.Object) bool { +func equalClosure(t *testing.T, expected, actual objects.Object, msg ...interface{}) bool { expectedT := expected.(*objects.Closure) actualT := actual.(*objects.Closure) - if !Equal(t, expectedT.Fn, actualT.Fn) { + if !Equal(t, expectedT.Fn, actualT.Fn, msg...) { return false } - if !Equal(t, len(expectedT.Free), len(actualT.Free)) { + if !Equal(t, len(expectedT.Free), len(actualT.Free), msg...) { return false } for i := 0; i < len(expectedT.Free); i++ { - if !Equal(t, *expectedT.Free[i], *actualT.Free[i]) { + if !Equal(t, *expectedT.Free[i], *actualT.Free[i], msg...) { return false } } diff --git a/compiler/stdlib/stdlib.go b/compiler/stdlib/stdlib.go index 3ca5dd3..9a3c474 100644 --- a/compiler/stdlib/stdlib.go +++ b/compiler/stdlib/stdlib.go @@ -7,4 +7,5 @@ var Modules = map[string]*objects.ImmutableMap{ "math": {Value: mathModule}, "os": {Value: osModule}, "exec": {Value: execModule}, + "text": {Value: textModule}, } diff --git a/compiler/stdlib/stdlib_test.go b/compiler/stdlib/stdlib_test.go new file mode 100644 index 0000000..76f307b --- /dev/null +++ b/compiler/stdlib/stdlib_test.go @@ -0,0 +1,125 @@ +package stdlib_test + +import ( + "fmt" + "testing" + + "github.com/d5/tengo/assert" + "github.com/d5/tengo/compiler/stdlib" + "github.com/d5/tengo/objects" +) + +type ARR = []interface{} +type MAP = map[string]interface{} +type IARR []interface{} +type IMAP map[string]interface{} + +type callres struct { + t *testing.T + o objects.Object + e error +} + +func (c callres) call(funcName string, args ...interface{}) callres { + if c.e != nil { + return c + } + + imap, ok := c.o.(*objects.ImmutableMap) + if !ok { + return c + } + + m, ok := imap.Value[funcName] + if !ok { + return callres{t: c.t, e: fmt.Errorf("function not found: %s", funcName)} + } + + f, ok := m.(*objects.UserFunction) + if !ok { + return callres{t: c.t, e: fmt.Errorf("non-callable: %s", funcName)} + } + + var oargs []objects.Object + for _, v := range args { + oargs = append(oargs, object(v)) + } + + res, err := f.Value(oargs...) + + return callres{t: c.t, o: res, e: err} +} + +func (c callres) expect(expected interface{}, msgAndArgs ...interface{}) bool { + return assert.NoError(c.t, c.e, msgAndArgs...) && + assert.Equal(c.t, object(expected), c.o, msgAndArgs...) +} + +func (c callres) expectError() bool { + return assert.Error(c.t, c.e) +} + +func module(t *testing.T, moduleName string) callres { + mod, ok := stdlib.Modules[moduleName] + if !ok { + return callres{t: t, e: fmt.Errorf("module not found: %s", moduleName)} + } + + return callres{t: t, o: mod} +} + +func object(v interface{}) objects.Object { + switch v := v.(type) { + case objects.Object: + return v + case string: + return &objects.String{Value: v} + case int64: + return &objects.Int{Value: v} + case int: // for convenience + return &objects.Int{Value: int64(v)} + case bool: + if v { + return objects.TrueValue + } + return objects.FalseValue + case rune: + return &objects.Char{Value: v} + case byte: // for convenience + return &objects.Char{Value: rune(v)} + case float64: + return &objects.Float{Value: v} + case []byte: + return &objects.Bytes{Value: v} + case MAP: + objs := make(map[string]objects.Object) + for k, v := range v { + objs[k] = object(v) + } + + return &objects.Map{Value: objs} + case ARR: + var objs []objects.Object + for _, e := range v { + objs = append(objs, object(e)) + } + + return &objects.Array{Value: objs} + case IMAP: + objs := make(map[string]objects.Object) + for k, v := range v { + objs[k] = object(v) + } + + return &objects.ImmutableMap{Value: objs} + case IARR: + var objs []objects.Object + for _, e := range v { + objs = append(objs, object(e)) + } + + return &objects.ImmutableArray{Value: objs} + } + + panic(fmt.Errorf("unknown type: %T", v)) +} diff --git a/compiler/stdlib/text.go b/compiler/stdlib/text.go new file mode 100644 index 0000000..40a5810 --- /dev/null +++ b/compiler/stdlib/text.go @@ -0,0 +1,395 @@ +package stdlib + +import ( + "regexp" + + "github.com/d5/tengo/objects" +) + +var textModule = map[string]objects.Object{ + // re_match(pattern, text) => bool/error + "re_match": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + 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 + } + + matched, err := regexp.MatchString(s1, s2) + if err != nil { + ret = wrapError(err) + return + } + + if matched { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return + }, + }, + + // re_find(pattern, text) => array(array({text:,begin:,end:}))/undefined + // re_find(pattern, text, maxCount) => array(array({text:,begin:,end:}))/undefined + "re_find": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 2 && numArgs != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + + if numArgs < 3 { + m := re.FindStringSubmatchIndex(s2) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for i := 0; i < len(m); i += 2 { + arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s2[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + ret = &objects.Array{Value: []objects.Object{arr}} + + return + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + m := re.FindAllStringSubmatchIndex(s2, i3) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for _, m := range m { + subMatch := &objects.Array{} + for i := 0; i < len(m); i += 2 { + subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s2[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + arr.Value = append(arr.Value, subMatch) + } + + ret = arr + + return + }, + }, + + // re_replace(pattern, text, repl) => string/error + "re_replace": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 3 { + 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 + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + } else { + ret = &objects.String{Value: re.ReplaceAllString(s2, s3)} + } + + return + }, + }, + + // re_split(pattern, text) => array(string)/error + // re_split(pattern, text, maxCount) => array(string)/error + "re_split": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 2 && numArgs != 3 { + 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 + } + + var i3 = -1 + if numArgs > 2 { + i3, ok = objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + return + } + + arr := &objects.Array{} + for _, s := range re.Split(s2, i3) { + arr.Value = append(arr.Value, &objects.String{Value: s}) + } + + ret = arr + + return + }, + }, + + // re_compile(pattern) => Regexp/error + "re_compile": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + } else { + ret = stringsRegexpImmutableMap(re) + } + + return + }, + }, +} + +func stringsRegexpImmutableMap(re *regexp.Regexp) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + // match(text) => bool + "match": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + + if re.MatchString(s1) { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return + }, + }, + + // find(text) => array(array({text:,begin:,end:}))/undefined + // find(text, maxCount) => array(array({text:,begin:,end:}))/undefined + "find": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 1 && numArgs != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + + if numArgs == 1 { + m := re.FindStringSubmatchIndex(s1) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for i := 0; i < len(m); i += 2 { + arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s1[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + ret = &objects.Array{Value: []objects.Object{arr}} + + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + m := re.FindAllStringSubmatchIndex(s1, i2) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for _, m := range m { + subMatch := &objects.Array{} + for i := 0; i < len(m); i += 2 { + subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s1[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + arr.Value = append(arr.Value, subMatch) + } + + ret = arr + + return + }, + }, + + // replace(src, repl) => string + "replace": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + 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 + } + + ret = &objects.String{Value: re.ReplaceAllString(s1, s2)} + + return + }, + }, + + // split(text) => array(string) + // split(text, maxCount) => array(string) + "split": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 1 && numArgs != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + + var i2 = -1 + if numArgs > 1 { + i2, ok = objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidTypeConversion + return + } + } + + arr := &objects.Array{} + for _, s := range re.Split(s1, i2) { + arr.Value = append(arr.Value, &objects.String{Value: s}) + } + + ret = arr + + return + }, + }, + }, + } +} diff --git a/compiler/stdlib/text_test.go b/compiler/stdlib/text_test.go new file mode 100644 index 0000000..46450fc --- /dev/null +++ b/compiler/stdlib/text_test.go @@ -0,0 +1,163 @@ +package stdlib_test + +import ( + "testing" + + "github.com/d5/tengo/objects" +) + +func TestTextRE(t *testing.T) { + // re_match(pattern, text) + for _, d := range []struct { + pattern string + text string + expected interface{} + }{ + {"abc", "", false}, + {"abc", "abc", true}, + {"a", "abc", true}, + {"b", "abc", true}, + {"^a", "abc", true}, + {"^b", "abc", false}, + } { + module(t, "text").call("re_match", d.pattern, d.text).expect(d.expected, "pattern: %q, src: %q", d.pattern, d.text) + module(t, "text").call("re_compile", d.pattern).call("match", d.text).expect(d.expected, "patter: %q, src: %q", d.pattern, d.text) + } + + // re_find(pattern, text) + for _, d := range []struct { + pattern string + text string + expected interface{} + }{ + {"a(b)", "", objects.UndefinedValue}, + {"a(b)", "ab", ARR{ + ARR{ + IMAP{"text": "ab", "begin": 0, "end": 2}, + IMAP{"text": "b", "begin": 1, "end": 2}, + }, + }}, + {"a(bc)d", "abcdefgabcd", ARR{ + ARR{ + IMAP{"text": "abcd", "begin": 0, "end": 4}, + IMAP{"text": "bc", "begin": 1, "end": 3}, + }, + }}, + {"(a)b(c)d", "abcdefgabcd", ARR{ + ARR{ + IMAP{"text": "abcd", "begin": 0, "end": 4}, + IMAP{"text": "a", "begin": 0, "end": 1}, + IMAP{"text": "c", "begin": 2, "end": 3}, + }, + }}, + } { + module(t, "text").call("re_find", d.pattern, d.text).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) + module(t, "text").call("re_compile", d.pattern).call("find", d.text).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) + } + + // re_find(pattern, text, count)) + for _, d := range []struct { + pattern string + text string + count int + expected interface{} + }{ + {"a(b)", "", -1, objects.UndefinedValue}, + {"a(b)", "ab", -1, ARR{ + ARR{ + IMAP{"text": "ab", "begin": 0, "end": 2}, + IMAP{"text": "b", "begin": 1, "end": 2}, + }, + }}, + {"a(bc)d", "abcdefgabcd", -1, ARR{ + ARR{ + IMAP{"text": "abcd", "begin": 0, "end": 4}, + IMAP{"text": "bc", "begin": 1, "end": 3}, + }, + ARR{ + IMAP{"text": "abcd", "begin": 7, "end": 11}, + IMAP{"text": "bc", "begin": 8, "end": 10}, + }, + }}, + {"(a)b(c)d", "abcdefgabcd", -1, ARR{ + ARR{ + IMAP{"text": "abcd", "begin": 0, "end": 4}, + IMAP{"text": "a", "begin": 0, "end": 1}, + IMAP{"text": "c", "begin": 2, "end": 3}, + }, + ARR{ + IMAP{"text": "abcd", "begin": 7, "end": 11}, + IMAP{"text": "a", "begin": 7, "end": 8}, + IMAP{"text": "c", "begin": 9, "end": 10}, + }, + }}, + {"(a)b(c)d", "abcdefgabcd", 0, objects.UndefinedValue}, + {"(a)b(c)d", "abcdefgabcd", 1, ARR{ + ARR{ + IMAP{"text": "abcd", "begin": 0, "end": 4}, + IMAP{"text": "a", "begin": 0, "end": 1}, + IMAP{"text": "c", "begin": 2, "end": 3}, + }, + }}, + } { + module(t, "text").call("re_find", 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("find", d.text, d.count).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) + } + + // re_replace(pattern, text, repl) + for _, d := range []struct { + pattern string + text string + repl string + expected interface{} + }{ + {"a", "", "b", ""}, + {"a", "a", "b", "b"}, + {"a", "acac", "b", "bcbc"}, + {"a", "acac", "123", "123c123c"}, + {"ac", "acac", "99", "9999"}, + {"ac$", "acac", "foo", "acfoo"}, + } { + module(t, "text").call("re_replace", d.pattern, d.text, d.repl).expect(d.expected, "pattern: %q, text: %q, repl: %q", d.pattern, d.text, d.repl) + module(t, "text").call("re_compile", d.pattern).call("replace", d.text, d.repl).expect(d.expected, "pattern: %q, text: %q, repl: %q", d.pattern, d.text, d.repl) + } + + // re_split(pattern, text) + for _, d := range []struct { + pattern string + text string + expected interface{} + }{ + {"a", "", ARR{""}}, + {"a", "abcabc", ARR{"", "bc", "bc"}}, + {"ab", "abcabc", ARR{"", "c", "c"}}, + {"^a", "abcabc", ARR{"", "bcabc"}}, + } { + module(t, "text").call("re_split", d.pattern, d.text).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) + module(t, "text").call("re_compile", d.pattern).call("split", d.text).expect(d.expected, "pattern: %q, text: %q", d.pattern, d.text) + } + + // re_split(pattern, text, count)) + for _, d := range []struct { + pattern string + text string + count int + expected interface{} + }{ + {"a", "", -1, ARR{""}}, + {"a", "abcabc", -1, ARR{"", "bc", "bc"}}, + {"ab", "abcabc", -1, ARR{"", "c", "c"}}, + {"^a", "abcabc", -1, ARR{"", "bcabc"}}, + {"a", "abcabc", 0, ARR{}}, + {"a", "abcabc", 1, ARR{"abcabc"}}, + {"a", "abcabc", 2, ARR{"", "bcabc"}}, + {"a", "abcabc", 3, ARR{"", "bc", "bc"}}, + {"b", "abcabc", 1, ARR{"abcabc"}}, + {"b", "abcabc", 2, ARR{"a", "cabc"}}, + {"b", "abcabc", 3, ARR{"a", "ca", "c"}}, + } { + 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) + } + +}