text module: regex functions (re_match, re_find, re_split, re_replace)

This commit is contained in:
Daniel Kang 2019-01-28 18:30:26 -08:00
parent 85da0cdc24
commit 69a703bea2
5 changed files with 710 additions and 46 deletions

View file

@ -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
}
}

View file

@ -7,4 +7,5 @@ var Modules = map[string]*objects.ImmutableMap{
"math": {Value: mathModule},
"os": {Value: osModule},
"exec": {Value: execModule},
"text": {Value: textModule},
}

View file

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

395
compiler/stdlib/text.go Normal file
View file

@ -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
},
},
},
}
}

View file

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