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.
|
// NoError asserts err is not an error.
|
||||||
func NoError(t *testing.T, err error, msg ...interface{}) bool {
|
func NoError(t *testing.T, err error, msg ...interface{}) bool {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -26,8 +24,6 @@ func NoError(t *testing.T, err error, msg ...interface{}) bool {
|
||||||
|
|
||||||
// Error asserts err is an error.
|
// Error asserts err is an error.
|
||||||
func Error(t *testing.T, err error, msg ...interface{}) bool {
|
func Error(t *testing.T, err error, msg ...interface{}) bool {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -37,8 +33,6 @@ func Error(t *testing.T, err error, msg ...interface{}) bool {
|
||||||
|
|
||||||
// Nil asserts v is nil.
|
// Nil asserts v is nil.
|
||||||
func Nil(t *testing.T, v interface{}, msg ...interface{}) bool {
|
func Nil(t *testing.T, v interface{}, msg ...interface{}) bool {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -48,8 +42,6 @@ func Nil(t *testing.T, v interface{}, msg ...interface{}) bool {
|
||||||
|
|
||||||
// True asserts v is true.
|
// True asserts v is true.
|
||||||
func True(t *testing.T, v bool, msg ...interface{}) bool {
|
func True(t *testing.T, v bool, msg ...interface{}) bool {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if v {
|
if v {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -59,8 +51,6 @@ func True(t *testing.T, v bool, msg ...interface{}) bool {
|
||||||
|
|
||||||
// False asserts vis false.
|
// False asserts vis false.
|
||||||
func False(t *testing.T, v bool, msg ...interface{}) bool {
|
func False(t *testing.T, v bool, msg ...interface{}) bool {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if !v {
|
if !v {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -70,8 +60,6 @@ func False(t *testing.T, v bool, msg ...interface{}) bool {
|
||||||
|
|
||||||
// NotNil asserts v is not nil.
|
// NotNil asserts v is not nil.
|
||||||
func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool {
|
func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if v != nil {
|
if v != nil {
|
||||||
return true
|
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.
|
// IsType asserts expected and actual are of the same type.
|
||||||
func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
|
func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if reflect.TypeOf(expected) == reflect.TypeOf(actual) {
|
if reflect.TypeOf(expected) == reflect.TypeOf(actual) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -92,15 +78,13 @@ func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
|
||||||
|
|
||||||
// Equal asserts expected and actual are equal.
|
// Equal asserts expected and actual are equal.
|
||||||
func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
|
func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
if expected == nil {
|
if expected == nil {
|
||||||
return Nil(t, actual, "expected nil, but got not nil")
|
return Nil(t, actual, "expected nil, but got not nil")
|
||||||
}
|
}
|
||||||
if !NotNil(t, actual, "expected not nil, but got nil") {
|
if !NotNil(t, actual, "expected not nil, but got nil") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !IsType(t, expected, actual) {
|
if !IsType(t, expected, actual, msg...) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,43 +134,43 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
return failExpectedActual(t, expected, actual, msg...)
|
||||||
}
|
}
|
||||||
case []objects.Object:
|
case []objects.Object:
|
||||||
return equalObjectSlice(t, expected, actual.([]objects.Object))
|
return equalObjectSlice(t, expected, actual.([]objects.Object), msg...)
|
||||||
case *objects.Int:
|
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:
|
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:
|
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:
|
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:
|
case *objects.Bool:
|
||||||
if expected != actual {
|
if expected != actual {
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
return failExpectedActual(t, expected, actual, msg...)
|
||||||
}
|
}
|
||||||
case *objects.ReturnValue:
|
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:
|
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:
|
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:
|
case *objects.Bytes:
|
||||||
if bytes.Compare(expected.Value, actual.(*objects.Bytes).Value) != 0 {
|
if bytes.Compare(expected.Value, actual.(*objects.Bytes).Value) != 0 {
|
||||||
return failExpectedActual(t, string(expected.Value), string(actual.(*objects.Bytes).Value), msg...)
|
return failExpectedActual(t, string(expected.Value), string(actual.(*objects.Bytes).Value), msg...)
|
||||||
}
|
}
|
||||||
case *objects.Map:
|
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:
|
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:
|
case *objects.CompiledFunction:
|
||||||
return equalCompiledFunction(t, expected, actual.(*objects.CompiledFunction))
|
return equalCompiledFunction(t, expected, actual.(*objects.CompiledFunction), msg...)
|
||||||
case *objects.Closure:
|
case *objects.Closure:
|
||||||
return equalClosure(t, expected, actual.(*objects.Closure))
|
return equalClosure(t, expected, actual.(*objects.Closure), msg...)
|
||||||
case *objects.Undefined:
|
case *objects.Undefined:
|
||||||
if expected != actual {
|
if expected != actual {
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
return failExpectedActual(t, expected, actual, msg...)
|
||||||
}
|
}
|
||||||
case *objects.Error:
|
case *objects.Error:
|
||||||
return Equal(t, expected.Value, actual.(*objects.Error).Value)
|
return Equal(t, expected.Value, actual.(*objects.Error).Value, msg...)
|
||||||
case error:
|
case error:
|
||||||
if expected != actual.(error) {
|
if expected != actual.(error) {
|
||||||
return failExpectedActual(t, expected, actual, msg...)
|
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.
|
// Fail marks the function as having failed but continues execution.
|
||||||
func Fail(t *testing.T, msg ...interface{}) bool {
|
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.Logf("\nError trace:\n\t%s\n%s", strings.Join(errorTrace(), "\n\t"), message(msg...))
|
||||||
|
|
||||||
t.Fail()
|
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 {
|
func failExpectedActual(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
var addMsg string
|
var addMsg string
|
||||||
if len(msg) > 0 {
|
if len(msg) > 0 {
|
||||||
addMsg = "\nMessage: " + message(msg...)
|
addMsg = "\nMessage: " + message(msg...)
|
||||||
|
@ -260,15 +240,15 @@ func equalSymbol(a, b compiler.Symbol) bool {
|
||||||
a.Scope == b.Scope
|
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
|
// 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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(expected); i++ {
|
for i := 0; i < len(expected); i++ {
|
||||||
if !Equal(t, expected[i], actual[i]) {
|
if !Equal(t, expected[i], actual[i], msg...) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -276,15 +256,15 @@ func equalObjectSlice(t *testing.T, expected, actual []objects.Object) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object) bool {
|
func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object, msg ...interface{}) bool {
|
||||||
if !Equal(t, len(expected), len(actual)) {
|
if !Equal(t, len(expected), len(actual), msg...) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, expectedVal := range expected {
|
for key, expectedVal := range expected {
|
||||||
actualVal := actual[key]
|
actualVal := actual[key]
|
||||||
|
|
||||||
if !Equal(t, expectedVal, actualVal) {
|
if !Equal(t, expectedVal, actualVal, msg...) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,27 +272,27 @@ func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object) bo
|
||||||
return true
|
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)
|
expectedT := expected.(*objects.CompiledFunction)
|
||||||
actualT := actual.(*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)
|
expectedT := expected.(*objects.Closure)
|
||||||
actualT := actual.(*objects.Closure)
|
actualT := actual.(*objects.Closure)
|
||||||
|
|
||||||
if !Equal(t, expectedT.Fn, actualT.Fn) {
|
if !Equal(t, expectedT.Fn, actualT.Fn, msg...) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !Equal(t, len(expectedT.Free), len(actualT.Free)) {
|
if !Equal(t, len(expectedT.Free), len(actualT.Free), msg...) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(expectedT.Free); i++ {
|
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
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,4 +7,5 @@ var Modules = map[string]*objects.ImmutableMap{
|
||||||
"math": {Value: mathModule},
|
"math": {Value: mathModule},
|
||||||
"os": {Value: osModule},
|
"os": {Value: osModule},
|
||||||
"exec": {Value: execModule},
|
"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