text module: regex functions (re_match, re_find, re_split, re_replace)
This commit is contained in:
parent
85da0cdc24
commit
69a703bea2
5 changed files with 710 additions and 46 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,5 @@ var Modules = map[string]*objects.ImmutableMap{
|
|||
"math": {Value: mathModule},
|
||||
"os": {Value: osModule},
|
||||
"exec": {Value: execModule},
|
||||
"text": {Value: textModule},
|
||||
}
|
||||
|
|
125
compiler/stdlib/stdlib_test.go
Normal file
125
compiler/stdlib/stdlib_test.go
Normal 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
395
compiler/stdlib/text.go
Normal 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
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
163
compiler/stdlib/text_test.go
Normal file
163
compiler/stdlib/text_test.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue