stdlib/strings/module.go

Summary

Maintainability
A
40 mins
Test Coverage
// Copyright (c) 2020-2023 Ozan Hacıbekiroğlu.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.

// Package strings provides strings module implementing simple functions to
// manipulate UTF-8 encoded strings for uGO script language. It wraps Go's
// strings package functionalities.
package strings

import (
    "strconv"
    "strings"
    "unicode/utf8"

    "github.com/ozanh/ugo"
    "github.com/ozanh/ugo/stdlib"
)

// Module represents time module.
var Module = map[string]ugo.Object{
    // ugo:doc
    // # strings Module
    //
    // ## Functions
    // Contains(s string, substr string) -> bool
    // Reports whether substr is within s.
    "Contains": &ugo.Function{
        Name:    "Contains",
        Value:   stdlib.FuncPssRO(containsFunc),
        ValueEx: stdlib.FuncPssROEx(containsFunc),
    },
    // ugo:doc
    // ContainsAny(s string, chars string) -> bool
    // Reports whether any char in chars are within s.
    "ContainsAny": &ugo.Function{
        Name:    "ContainsAny",
        Value:   stdlib.FuncPssRO(containsAnyFunc),
        ValueEx: stdlib.FuncPssROEx(containsAnyFunc),
    },
    // ugo:doc
    // ContainsChar(s string, c char) -> bool
    // Reports whether the char c is within s.
    "ContainsChar": &ugo.Function{
        Name:    "ContainsChar",
        Value:   stdlib.FuncPsrRO(containsCharFunc),
        ValueEx: stdlib.FuncPsrROEx(containsCharFunc),
    },
    // ugo:doc
    // Count(s string, substr string) -> int
    // Counts the number of non-overlapping instances of substr in s.
    "Count": &ugo.Function{
        Name:    "Count",
        Value:   stdlib.FuncPssRO(countFunc),
        ValueEx: stdlib.FuncPssROEx(countFunc),
    },
    // ugo:doc
    // EqualFold(s string, t string) -> bool
    // EqualFold reports whether s and t, interpreted as UTF-8 strings,
    // are equal under Unicode case-folding, which is a more general form of
    // case-insensitivity.
    "EqualFold": &ugo.Function{
        Name:    "EqualFold",
        Value:   stdlib.FuncPssRO(equalFoldFunc),
        ValueEx: stdlib.FuncPssROEx(equalFoldFunc),
    },
    // ugo:doc
    // Fields(s string) -> array
    // Splits the string s around each instance of one or more consecutive white
    // space characters, returning an array of substrings of s or an empty array
    // if s contains only white space.
    "Fields": &ugo.Function{
        Name:    "Fields",
        Value:   stdlib.FuncPsRO(fieldsFunc),
        ValueEx: stdlib.FuncPsROEx(fieldsFunc),
    },
    // ugo:doc
    // FieldsFunc(s string, f func(char) bool) -> array
    // Splits the string s at each run of Unicode code points c satisfying f(c),
    // and returns an array of slices of s. If all code points in s satisfy
    // f(c) or the string is empty, an empty array is returned.
    "FieldsFunc": &ugo.Function{
        Name: "FieldsFunc",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return fieldsFuncInv(ugo.NewCall(nil, args))
        },
        ValueEx: fieldsFuncInv,
    },
    // ugo:doc
    // HasPrefix(s string, prefix string) -> bool
    // Reports whether the string s begins with prefix.
    "HasPrefix": &ugo.Function{
        Name:    "HasPrefix",
        Value:   stdlib.FuncPssRO(hasPrefixFunc),
        ValueEx: stdlib.FuncPssROEx(hasPrefixFunc),
    },
    // ugo:doc
    // HasSuffix(s string, suffix string) -> bool
    // Reports whether the string s ends with prefix.
    "HasSuffix": &ugo.Function{
        Name:    "HasSuffix",
        Value:   stdlib.FuncPssRO(hasSuffixFunc),
        ValueEx: stdlib.FuncPssROEx(hasSuffixFunc),
    },
    // ugo:doc
    // Index(s string, substr string) -> int
    // Returns the index of the first instance of substr in s, or -1 if substr
    // is not present in s.
    "Index": &ugo.Function{
        Name:    "Index",
        Value:   stdlib.FuncPssRO(indexFunc),
        ValueEx: stdlib.FuncPssROEx(indexFunc),
    },
    // ugo:doc
    // IndexAny(s string, chars string) -> int
    // Returns the index of the first instance of any char from chars in s, or
    // -1 if no char from chars is present in s.
    "IndexAny": &ugo.Function{
        Name:    "IndexAny",
        Value:   stdlib.FuncPssRO(indexAnyFunc),
        ValueEx: stdlib.FuncPssROEx(indexAnyFunc),
    },
    // ugo:doc
    // IndexByte(s string, c char|int) -> int
    // Returns the index of the first byte value of c in s, or -1 if byte value
    // of c is not present in s. c's integer value must be between 0 and 255.
    "IndexByte": &ugo.Function{
        Name:    "IndexByte",
        Value:   stdlib.FuncPsrRO(indexByteFunc),
        ValueEx: stdlib.FuncPsrROEx(indexByteFunc),
    },
    // ugo:doc
    // IndexChar(s string, c char) -> int
    // Returns the index of the first instance of the char c, or -1 if char is
    // not present in s.
    "IndexChar": &ugo.Function{
        Name:    "IndexChar",
        Value:   stdlib.FuncPsrRO(indexCharFunc),
        ValueEx: stdlib.FuncPsrROEx(indexCharFunc),
    },
    // ugo:doc
    // IndexFunc(s string, f func(char) bool) -> int
    // Returns the index into s of the first Unicode code point satisfying f(c),
    // or -1 if none do.
    "IndexFunc": &ugo.Function{
        Name: "IndexFunc",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return newIndexFuncInv(strings.IndexFunc)(ugo.NewCall(nil, args))
        },
        ValueEx: newIndexFuncInv(strings.IndexFunc),
    },
    // ugo:doc
    // Join(arr array, sep string) -> string
    // Concatenates the string values of array arr elements to create a
    // single string. The separator string sep is placed between elements in the
    // resulting string.
    "Join": &ugo.Function{
        Name:    "Join",
        Value:   stdlib.FuncPAsRO(joinFunc),
        ValueEx: stdlib.FuncPAsROEx(joinFunc),
    },
    // ugo:doc
    // LastIndex(s string, substr string) -> int
    // Returns the index of the last instance of substr in s, or -1 if substr
    // is not present in s.
    "LastIndex": &ugo.Function{
        Name:    "LastIndex",
        Value:   stdlib.FuncPssRO(lastIndexFunc),
        ValueEx: stdlib.FuncPssROEx(lastIndexFunc),
    },
    // ugo:doc
    // LastIndexAny(s string, chars string) -> int
    // Returns the index of the last instance of any char from chars in s, or
    // -1 if no char from chars is present in s.
    "LastIndexAny": &ugo.Function{
        Name:    "LastIndexAny",
        Value:   stdlib.FuncPssRO(lastIndexAnyFunc),
        ValueEx: stdlib.FuncPssROEx(lastIndexAnyFunc),
    },
    // ugo:doc
    // LastIndexByte(s string, c char|int) -> int
    // Returns the index of byte value of the last instance of c in s, or -1
    // if c is not present in s. c's integer value must be between 0 and 255.
    "LastIndexByte": &ugo.Function{
        Name:    "LastIndexByte",
        Value:   stdlib.FuncPsrRO(lastIndexByteFunc),
        ValueEx: stdlib.FuncPsrROEx(lastIndexByteFunc),
    },
    // ugo:doc
    // LastIndexFunc(s string, f func(char) bool) -> int
    // Returns the index into s of the last Unicode code point satisfying f(c),
    // or -1 if none do.
    "LastIndexFunc": &ugo.Function{
        Name: "LastIndexFunc",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return newIndexFuncInv(strings.LastIndexFunc)(ugo.NewCall(nil, args))
        },
        ValueEx: newIndexFuncInv(strings.LastIndexFunc),
    },
    // ugo:doc
    // Map(f func(char) char, s string) -> string
    // Returns a copy of the string s with all its characters modified
    // according to the mapping function f. If f returns a negative value, the
    // character is dropped from the string with no replacement.
    "Map": &ugo.Function{
        Name: "Map",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return mapFuncInv(ugo.NewCall(nil, args))
        },
        ValueEx: mapFuncInv,
    },
    // ugo:doc
    // PadLeft(s string, padLen int[, padWith any]) -> string
    // Returns a string that is padded on the left with the string `padWith` until
    // the `padLen` length is reached. If padWith is not given, a white space is
    // used as default padding.
    "PadLeft": &ugo.Function{
        Name: "PadLeft",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return pad(ugo.NewCall(nil, args), true)
        },
        ValueEx: func(c ugo.Call) (ugo.Object, error) {
            return pad(c, true)
        },
    },
    // ugo:doc
    // PadRight(s string, padLen int[, padWith any]) -> string
    // Returns a string that is padded on the right with the string `padWith` until
    // the `padLen` length is reached. If padWith is not given, a white space is
    // used as default padding.
    "PadRight": &ugo.Function{
        Name: "PadRight",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return pad(ugo.NewCall(nil, args), false)
        },
        ValueEx: func(c ugo.Call) (ugo.Object, error) {
            return pad(c, false)
        },
    },
    // ugo:doc
    // Repeat(s string, count int) -> string
    // Returns a new string consisting of count copies of the string s.
    //
    // - If count is a negative int, it returns empty string.
    // - If (len(s) * count) overflows, it panics.
    "Repeat": &ugo.Function{
        Name:    "Repeat",
        Value:   stdlib.FuncPsiRO(repeatFunc),
        ValueEx: stdlib.FuncPsiROEx(repeatFunc),
    },
    // ugo:doc
    // Replace(s string, old string, new string[, n int]) -> string
    // Returns a copy of the string s with the first n non-overlapping instances
    // of old replaced by new. If n is not provided or -1, it replaces all
    // instances.
    "Replace": &ugo.Function{
        Name: "Replace",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return replaceFunc(ugo.NewCall(nil, args))
        },
        ValueEx: replaceFunc,
    },
    // ugo:doc
    // Split(s string, sep string[, n int]) -> [string]
    // Splits s into substrings separated by sep and returns an array of
    // the substrings between those separators.
    //
    // n determines the number of substrings to return:
    //
    // - n < 0: all substrings (default)
    // - n > 0: at most n substrings; the last substring will be the unsplit remainder.
    // - n == 0: the result is empty array
    "Split": &ugo.Function{
        Name: "Split",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return newSplitFunc(strings.SplitN)(ugo.NewCall(nil, args))
        },
        ValueEx: newSplitFunc(strings.SplitN),
    },
    // ugo:doc
    // SplitAfter(s string, sep string[, n int]) -> [string]
    // Slices s into substrings after each instance of sep and returns an array
    // of those substrings.
    //
    // n determines the number of substrings to return:
    //
    // - n < 0: all substrings (default)
    // - n > 0: at most n substrings; the last substring will be the unsplit remainder.
    // - n == 0: the result is empty array
    "SplitAfter": &ugo.Function{
        Name: "SplitAfter",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return newSplitFunc(strings.SplitAfterN)(ugo.NewCall(nil, args))
        },
        ValueEx: newSplitFunc(strings.SplitAfterN),
    },
    // ugo:doc
    // Title(s string) -> string
    // Deprecated: Returns a copy of the string s with all Unicode letters that
    // begin words mapped to their Unicode title case.
    "Title": &ugo.Function{
        Name:    "Title",
        Value:   stdlib.FuncPsRO(titleFunc),
        ValueEx: stdlib.FuncPsROEx(titleFunc),
    },
    // ugo:doc
    // ToLower(s string) -> string
    // Returns s with all Unicode letters mapped to their lower case.
    "ToLower": &ugo.Function{
        Name:    "ToLower",
        Value:   stdlib.FuncPsRO(toLowerFunc),
        ValueEx: stdlib.FuncPsROEx(toLowerFunc),
    },
    // ugo:doc
    // ToTitle(s string) -> string
    // Returns a copy of the string s with all Unicode letters mapped to their
    // Unicode title case.
    "ToTitle": &ugo.Function{
        Name:    "ToTitle",
        Value:   stdlib.FuncPsRO(toTitleFunc),
        ValueEx: stdlib.FuncPsROEx(toTitleFunc),
    },
    // ugo:doc
    // ToUpper(s string) -> string
    // Returns s with all Unicode letters mapped to their upper case.
    "ToUpper": &ugo.Function{
        Name:    "ToUpper",
        Value:   stdlib.FuncPsRO(toUpperFunc),
        ValueEx: stdlib.FuncPsROEx(toUpperFunc),
    },
    // ugo:doc
    // ToValidUTF8(s string[, replacement string]) -> string
    // Returns a copy of the string s with each run of invalid UTF-8 byte
    // sequences replaced by the replacement string, which may be empty.
    "ToValidUTF8": &ugo.Function{
        Name: "ToValidUTF8",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return toValidUTF8Func(ugo.NewCall(nil, args))
        },
        ValueEx: toValidUTF8Func,
    },
    // ugo:doc
    // Trim(s string, cutset string) -> string
    // Returns a slice of the string s with all leading and trailing Unicode
    // code points contained in cutset removed.
    "Trim": &ugo.Function{
        Name:    "Trim",
        Value:   stdlib.FuncPssRO(trimFunc),
        ValueEx: stdlib.FuncPssROEx(trimFunc),
    },
    // ugo:doc
    // TrimFunc(s string, f func(char) bool) -> string
    // Returns a slice of the string s with all leading and trailing Unicode
    // code points satisfying f removed.
    "TrimFunc": &ugo.Function{
        Name: "TrimFunc",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return newTrimFuncInv(strings.TrimFunc)(ugo.NewCall(nil, args))
        },
        ValueEx: newTrimFuncInv(strings.TrimFunc),
    },
    // ugo:doc
    // TrimLeft(s string, cutset string) -> string
    // Returns a slice of the string s with all leading Unicode code points
    // contained in cutset removed.
    "TrimLeft": &ugo.Function{
        Name:    "TrimLeft",
        Value:   stdlib.FuncPssRO(trimLeftFunc),
        ValueEx: stdlib.FuncPssROEx(trimLeftFunc),
    },
    // ugo:doc
    // TrimLeftFunc(s string, f func(char) bool) -> string
    // Returns a slice of the string s with all leading Unicode code points
    // c satisfying f(c) removed.
    "TrimLeftFunc": &ugo.Function{
        Name: "TrimLeftFunc",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return newTrimFuncInv(strings.TrimLeftFunc)(ugo.NewCall(nil, args))
        },
        ValueEx: newTrimFuncInv(strings.TrimLeftFunc),
    },
    // ugo:doc
    // TrimPrefix(s string, prefix string) -> string
    // Returns s without the provided leading prefix string. If s doesn't start
    // with prefix, s is returned unchanged.
    "TrimPrefix": &ugo.Function{
        Name:    "TrimPrefix",
        Value:   stdlib.FuncPssRO(trimPrefixFunc),
        ValueEx: stdlib.FuncPssROEx(trimPrefixFunc),
    },
    // ugo:doc
    // TrimRight(s string, cutset string) -> string
    // Returns a slice of the string s with all trailing Unicode code points
    // contained in cutset removed.
    "TrimRight": &ugo.Function{
        Name:    "TrimRight",
        Value:   stdlib.FuncPssRO(trimRightFunc),
        ValueEx: stdlib.FuncPssROEx(trimRightFunc),
    },
    // ugo:doc
    // TrimRightFunc(s string, f func(char) bool) -> string
    // Returns a slice of the string s with all trailing Unicode code points
    // c satisfying f(c) removed.
    "TrimRightFunc": &ugo.Function{
        Name: "TrimRightFunc",
        Value: func(args ...ugo.Object) (ugo.Object, error) {
            return newTrimFuncInv(strings.TrimRightFunc)(ugo.NewCall(nil, args))
        },
        ValueEx: newTrimFuncInv(strings.TrimRightFunc),
    },
    // ugo:doc
    // TrimSpace(s string) -> string
    // Returns a slice of the string s, with all leading and trailing white
    // space removed, as defined by Unicode.
    "TrimSpace": &ugo.Function{
        Name:    "TrimSpace",
        Value:   stdlib.FuncPsRO(trimSpaceFunc),
        ValueEx: stdlib.FuncPsROEx(trimSpaceFunc),
    },
    // ugo:doc
    // TrimSuffix(s string, suffix string) -> string
    // Returns s without the provided trailing suffix string. If s doesn't end
    // with suffix, s is returned unchanged.
    "TrimSuffix": &ugo.Function{
        Name:    "TrimSuffix",
        Value:   stdlib.FuncPssRO(trimSuffixFunc),
        ValueEx: stdlib.FuncPssROEx(trimSuffixFunc),
    },
}

func containsFunc(s, substr string) ugo.Object {
    return ugo.Bool(strings.Contains(s, substr))
}

func containsAnyFunc(s, chars string) ugo.Object {
    return ugo.Bool(strings.ContainsAny(s, chars))
}

func containsCharFunc(s string, c rune) ugo.Object {
    return ugo.Bool(strings.ContainsRune(s, c))
}

func countFunc(s, substr string) ugo.Object {
    return ugo.Int(strings.Count(s, substr))
}

func equalFoldFunc(s, t string) ugo.Object {
    return ugo.Bool(strings.EqualFold(s, t))
}

func fieldsFunc(s string) ugo.Object {
    fields := strings.Fields(s)
    out := make(ugo.Array, 0, len(fields))
    for _, s := range fields {
        out = append(out, ugo.String(s))
    }
    return out
}

func fieldsFuncInv(c ugo.Call) (ugo.Object, error) {
    return stringInvoke(c, 0, 1,
        func(s string, inv *ugo.Invoker) (ugo.Object, error) {
            var err error
            fields := strings.FieldsFunc(s, func(r rune) bool {
                if err != nil {
                    return false
                }
                var ret ugo.Object
                ret, err = inv.Invoke(ugo.Char(r))
                if err != nil {
                    return false
                }
                return !ret.IsFalsy()
            })
            if err != nil {
                return ugo.Undefined, err
            }
            out := make(ugo.Array, 0, len(fields))
            for _, s := range fields {
                out = append(out, ugo.String(s))
            }
            return out, nil
        },
    )
}

func hasPrefixFunc(s, prefix string) ugo.Object {
    return ugo.Bool(strings.HasPrefix(s, prefix))
}

func hasSuffixFunc(s, suffix string) ugo.Object {
    return ugo.Bool(strings.HasSuffix(s, suffix))
}

func indexFunc(s, substr string) ugo.Object {
    return ugo.Int(strings.Index(s, substr))
}

func indexAnyFunc(s, chars string) ugo.Object {
    return ugo.Int(strings.IndexAny(s, chars))
}

func indexByteFunc(s string, c rune) ugo.Object {
    if c > 255 || c < 0 {
        return ugo.Int(-1)
    }
    return ugo.Int(strings.IndexByte(s, byte(c)))
}

func indexCharFunc(s string, c rune) ugo.Object {
    return ugo.Int(strings.IndexRune(s, c))
}

func joinFunc(arr ugo.Array, sep string) ugo.Object {
    elems := make([]string, len(arr))
    for i := range arr {
        elems[i] = arr[i].String()
    }
    return ugo.String(strings.Join(elems, sep))
}

func lastIndexFunc(s, substr string) ugo.Object {
    return ugo.Int(strings.LastIndex(s, substr))
}

func lastIndexAnyFunc(s, chars string) ugo.Object {
    return ugo.Int(strings.LastIndexAny(s, chars))
}

func lastIndexByteFunc(s string, c rune) ugo.Object {
    if c > 255 || c < 0 {
        return ugo.Int(-1)
    }
    return ugo.Int(strings.LastIndexByte(s, byte(c)))
}

func mapFuncInv(c ugo.Call) (ugo.Object, error) {
    return stringInvoke(c, 1, 0,
        func(s string, inv *ugo.Invoker) (ugo.Object, error) {
            var err error
            out := strings.Map(func(r rune) rune {
                if err != nil {
                    return utf8.RuneError
                }
                var ret ugo.Object
                ret, err = inv.Invoke(ugo.Char(r))
                if err != nil {
                    return 0
                }
                r, ok := ugo.ToGoRune(ret)
                if !ok {
                    return utf8.RuneError
                }
                return r
            }, s)
            return ugo.String(out), err
        },
    )
}

func pad(c ugo.Call, left bool) (ugo.Object, error) {
    size := c.Len()
    if size != 2 && size != 3 {
        return ugo.Undefined,
            ugo.ErrWrongNumArguments.NewError("want=2..3 got=" + strconv.Itoa(size))
    }
    s := c.Get(0).String()
    padLen, ok := ugo.ToGoInt(c.Get(1))
    if !ok {
        return ugo.Undefined,
            ugo.NewArgumentTypeError("2nd", "int", c.Get(1).TypeName())
    }
    diff := padLen - len(s)
    if diff <= 0 {
        return ugo.String(s), nil
    }
    padWith := " "
    if size > 2 {
        if padWith = c.Get(2).String(); len(padWith) == 0 {
            return ugo.String(s), nil
        }
    }
    r := (diff-len(padWith))/len(padWith) + 2
    if r <= 0 {
        return ugo.String(s), nil
    }
    var sb strings.Builder
    sb.Grow(padLen)
    if left {
        sb.WriteString(strings.Repeat(padWith, r)[:diff])
        sb.WriteString(s)
    } else {
        sb.WriteString(s)
        sb.WriteString(strings.Repeat(padWith, r)[:diff])
    }
    return ugo.String(sb.String()), nil
}

func repeatFunc(s string, count int) ugo.Object {
    // if n is negative strings.Repeat function panics
    if count < 0 {
        return ugo.String("")
    }
    return ugo.String(strings.Repeat(s, count))
}

func replaceFunc(c ugo.Call) (ugo.Object, error) {
    size := c.Len()
    if size != 3 && size != 4 {
        return ugo.Undefined,
            ugo.ErrWrongNumArguments.NewError("want=3..4 got=" + strconv.Itoa(size))
    }
    s := c.Get(0).String()
    old := c.Get(1).String()
    news := c.Get(2).String()
    n := -1
    if size == 4 {
        v, ok := ugo.ToGoInt(c.Get(3))
        if !ok {
            return ugo.Undefined,
                ugo.NewArgumentTypeError("4th", "int", c.Get(3).TypeName())
        }
        n = v
    }
    return ugo.String(strings.Replace(s, old, news, n)), nil
}

func titleFunc(s string) ugo.Object {
    //lint:ignore SA1019 Keep it for backward compatibility.
    return ugo.String(strings.Title(s)) //nolint staticcheck Keep it for backward compatibility
}

func toLowerFunc(s string) ugo.Object { return ugo.String(strings.ToLower(s)) }

func toTitleFunc(s string) ugo.Object { return ugo.String(strings.ToTitle(s)) }

func toUpperFunc(s string) ugo.Object { return ugo.String(strings.ToUpper(s)) }

func toValidUTF8Func(c ugo.Call) (ugo.Object, error) {
    size := c.Len()
    if size != 1 && size != 2 {
        return ugo.Undefined,
            ugo.ErrWrongNumArguments.NewError("want=1..2 got=" + strconv.Itoa(size))
    }
    s := c.Get(0).String()
    var repl string
    if size == 2 {
        repl = c.Get(1).String()
    }
    return ugo.String(strings.ToValidUTF8(s, repl)), nil
}

func trimFunc(s, cutset string) ugo.Object {
    return ugo.String(strings.Trim(s, cutset))
}

func trimLeftFunc(s, cutset string) ugo.Object {
    return ugo.String(strings.TrimLeft(s, cutset))
}

func trimPrefixFunc(s, prefix string) ugo.Object {
    return ugo.String(strings.TrimPrefix(s, prefix))
}

func trimRightFunc(s, cutset string) ugo.Object {
    return ugo.String(strings.TrimRight(s, cutset))
}

func trimSpaceFunc(s string) ugo.Object {
    return ugo.String(strings.TrimSpace(s))
}

func trimSuffixFunc(s, suffix string) ugo.Object {
    return ugo.String(strings.TrimSuffix(s, suffix))
}

func newSplitFunc(fn func(string, string, int) []string) ugo.CallableExFunc {
    return func(c ugo.Call) (ugo.Object, error) {
        size := c.Len()
        if size != 2 && size != 3 {
            return ugo.Undefined,
                ugo.ErrWrongNumArguments.NewError("want=2..3 got=" + strconv.Itoa(size))
        }
        s := c.Get(0).String()
        sep := c.Get(1).String()
        n := -1
        if size == 3 {
            v, ok := ugo.ToGoInt(c.Get(2))
            if !ok {
                return ugo.Undefined,
                    ugo.NewArgumentTypeError("3rd", "int", c.Get(2).TypeName())
            }
            n = v
        }
        strs := fn(s, sep, n)
        out := make(ugo.Array, 0, len(strs))
        for _, s := range strs {
            out = append(out, ugo.String(s))
        }
        return out, nil
    }
}

func newIndexFuncInv(fn func(string, func(rune) bool) int) ugo.CallableExFunc {
    return func(c ugo.Call) (ugo.Object, error) {
        return stringInvoke(c, 0, 1,
            func(s string, inv *ugo.Invoker) (ugo.Object, error) {
                var err error
                out := fn(s, func(r rune) bool {
                    if err != nil {
                        return false
                    }
                    var ret ugo.Object
                    ret, err = inv.Invoke(ugo.Char(r))
                    if err != nil {
                        return false
                    }
                    return !ret.IsFalsy()
                })
                return ugo.Int(out), err
            },
        )
    }
}

func newTrimFuncInv(fn func(string, func(rune) bool) string) ugo.CallableExFunc {
    return func(c ugo.Call) (ugo.Object, error) {
        return stringInvoke(c, 0, 1,
            func(s string, inv *ugo.Invoker) (ugo.Object, error) {
                var err error
                out := fn(s, func(r rune) bool {
                    if err != nil {
                        return false
                    }
                    var ret ugo.Object
                    ret, err = inv.Invoke(ugo.Char(r))
                    if err != nil {
                        return false
                    }
                    return !ret.IsFalsy()
                })
                return ugo.String(out), err
            },
        )
    }
}

func stringInvoke(
    c ugo.Call,
    sidx int,
    cidx int,
    fn func(string, *ugo.Invoker) (ugo.Object, error),
) (ugo.Object, error) {
    err := c.CheckLen(2)
    if err != nil {
        return ugo.Undefined, err
    }

    str := c.Get(sidx).String()
    callee := c.Get(cidx)
    if !callee.CanCall() {
        return ugo.Undefined, ugo.ErrNotCallable
    }
    if c.VM() == nil {
        if _, ok := callee.(*ugo.CompiledFunction); ok {
            return ugo.Undefined, ugo.ErrNotCallable
        }
    }

    inv := ugo.NewInvoker(c.VM(), callee)
    inv.Acquire()
    defer inv.Release()
    return fn(str, inv)
}