rum-goggles/v1/vendor/github.com/robertkrimen/otto/builtin_string.go

509 lines
13 KiB
Go
Raw Normal View History

package otto
import (
"bytes"
"regexp"
"strconv"
"strings"
"unicode/utf16"
"unicode/utf8"
)
// String
func stringValueFromStringArgumentList(argumentList []Value) Value {
if len(argumentList) > 0 {
2024-04-04 14:46:14 +00:00
return stringValue(argumentList[0].string())
}
2024-04-04 14:46:14 +00:00
return stringValue("")
}
func builtinString(call FunctionCall) Value {
return stringValueFromStringArgumentList(call.ArgumentList)
}
2024-04-04 14:46:14 +00:00
func builtinNewString(obj *object, argumentList []Value) Value {
return objectValue(obj.runtime.newString(stringValueFromStringArgumentList(argumentList)))
}
2024-04-04 14:46:14 +00:00
func builtinStringToString(call FunctionCall) Value {
return call.thisClassObject(classStringName).primitiveValue()
}
2024-04-04 14:46:14 +00:00
func builtinStringValueOf(call FunctionCall) Value {
return call.thisClassObject(classStringName).primitiveValue()
}
2024-04-04 14:46:14 +00:00
func builtinStringFromCharCode(call FunctionCall) Value {
chrList := make([]uint16, len(call.ArgumentList))
for index, value := range call.ArgumentList {
chrList[index] = toUint16(value)
}
2024-04-04 14:46:14 +00:00
return string16Value(chrList)
}
2024-04-04 14:46:14 +00:00
func builtinStringCharAt(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
idx := int(call.Argument(0).number().int64)
2024-04-04 14:46:14 +00:00
chr := stringAt(call.This.object().stringValue(), idx)
if chr == utf8.RuneError {
2024-04-04 14:46:14 +00:00
return stringValue("")
}
2024-04-04 14:46:14 +00:00
return stringValue(string(chr))
}
2024-04-04 14:46:14 +00:00
func builtinStringCharCodeAt(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
idx := int(call.Argument(0).number().int64)
2024-04-04 14:46:14 +00:00
chr := stringAt(call.This.object().stringValue(), idx)
if chr == utf8.RuneError {
return NaNValue()
}
2024-04-04 14:46:14 +00:00
return uint16Value(uint16(chr))
}
2024-04-04 14:46:14 +00:00
func builtinStringConcat(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
var value bytes.Buffer
value.WriteString(call.This.string())
for _, item := range call.ArgumentList {
value.WriteString(item.string())
}
2024-04-04 14:46:14 +00:00
return stringValue(value.String())
}
func lastIndexRune(s, substr string) int {
if i := strings.LastIndex(s, substr); i >= 0 {
return utf16Length(s[:i])
}
return -1
}
func indexRune(s, substr string) int {
if i := strings.Index(s, substr); i >= 0 {
return utf16Length(s[:i])
}
return -1
}
func utf16Length(s string) int {
return len(utf16.Encode([]rune(s)))
}
2024-04-04 14:46:14 +00:00
func builtinStringIndexOf(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
value := call.This.string()
target := call.Argument(0).string()
if 2 > len(call.ArgumentList) {
2024-04-04 14:46:14 +00:00
return intValue(indexRune(value, target))
}
start := toIntegerFloat(call.Argument(1))
if 0 > start {
start = 0
} else if start >= float64(len(value)) {
if target == "" {
2024-04-04 14:46:14 +00:00
return intValue(len(value))
}
2024-04-04 14:46:14 +00:00
return intValue(-1)
}
index := indexRune(value[int(start):], target)
if index >= 0 {
index += int(start)
}
2024-04-04 14:46:14 +00:00
return intValue(index)
}
2024-04-04 14:46:14 +00:00
func builtinStringLastIndexOf(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
value := call.This.string()
target := call.Argument(0).string()
if 2 > len(call.ArgumentList) || call.ArgumentList[1].IsUndefined() {
2024-04-04 14:46:14 +00:00
return intValue(lastIndexRune(value, target))
}
length := len(value)
if length == 0 {
2024-04-04 14:46:14 +00:00
return intValue(lastIndexRune(value, target))
}
start := call.ArgumentList[1].number()
if start.kind == numberInfinity { // FIXME
// startNumber is infinity, so start is the end of string (start = length)
2024-04-04 14:46:14 +00:00
return intValue(lastIndexRune(value, target))
}
if 0 > start.int64 {
start.int64 = 0
}
end := int(start.int64) + len(target)
if end > length {
end = length
}
2024-04-04 14:46:14 +00:00
return intValue(lastIndexRune(value[:end], target))
}
2024-04-04 14:46:14 +00:00
func builtinStringMatch(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := call.This.string()
matcherValue := call.Argument(0)
2024-04-04 14:46:14 +00:00
matcher := matcherValue.object()
if !matcherValue.IsObject() || matcher.class != classRegExpName {
matcher = call.runtime.newRegExp(matcherValue, Value{})
}
global := matcher.get("global").bool()
if !global {
match, result := execRegExp(matcher, target)
if !match {
return nullValue
}
2024-04-04 14:46:14 +00:00
return objectValue(execResultToArray(call.runtime, target, result))
}
2024-04-04 14:46:14 +00:00
result := matcher.regExpValue().regularExpression.FindAllStringIndex(target, -1)
if result == nil {
matcher.put("lastIndex", intValue(0), true)
return Value{} // !match
}
2024-04-04 14:46:14 +00:00
matchCount := len(result)
valueArray := make([]Value, matchCount)
for index := 0; index < matchCount; index++ {
valueArray[index] = stringValue(target[result[index][0]:result[index][1]])
}
matcher.put("lastIndex", intValue(result[matchCount-1][1]), true)
return objectValue(call.runtime.newArrayOf(valueArray))
}
2024-04-04 14:46:14 +00:00
var builtinStringReplaceRegexp = regexp.MustCompile("\\$(?:[\\$\\&\\'\\`1-9]|0[1-9]|[1-9][0-9])")
2024-04-04 14:46:14 +00:00
func builtinStringFindAndReplaceString(input []byte, lastIndex int, match []int, target []byte, replaceValue []byte) []byte {
matchCount := len(match) / 2
2024-04-04 14:46:14 +00:00
output := input
if match[0] != lastIndex {
output = append(output, target[lastIndex:match[0]]...)
}
2024-04-04 14:46:14 +00:00
replacement := builtinStringReplaceRegexp.ReplaceAllFunc(replaceValue, func(part []byte) []byte {
// TODO Check if match[0] or match[1] can be -1 in this scenario
switch part[1] {
case '$':
return []byte{'$'}
case '&':
return target[match[0]:match[1]]
case '`':
return target[:match[0]]
case '\'':
return target[match[1]:]
}
matchNumberParse, err := strconv.ParseInt(string(part[1:]), 10, 64)
if err != nil {
2024-04-04 14:46:14 +00:00
return nil
}
matchNumber := int(matchNumberParse)
if matchNumber >= matchCount {
2024-04-04 14:46:14 +00:00
return nil
}
offset := 2 * matchNumber
if match[offset] != -1 {
return target[match[offset]:match[offset+1]]
}
2024-04-04 14:46:14 +00:00
return nil // The empty string
})
2024-04-04 14:46:14 +00:00
return append(output, replacement...)
}
2024-04-04 14:46:14 +00:00
func builtinStringReplace(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := []byte(call.This.string())
searchValue := call.Argument(0)
2024-04-04 14:46:14 +00:00
searchObject := searchValue.object()
// TODO If a capture is -1?
var search *regexp.Regexp
global := false
find := 1
2024-04-04 14:46:14 +00:00
if searchValue.IsObject() && searchObject.class == classRegExpName {
regExp := searchObject.regExpValue()
search = regExp.regularExpression
if regExp.global {
find = -1
2024-04-04 14:46:14 +00:00
global = true
}
} else {
search = regexp.MustCompile(regexp.QuoteMeta(searchValue.string()))
}
found := search.FindAllSubmatchIndex(target, find)
if found == nil {
2024-04-04 14:46:14 +00:00
return stringValue(string(target)) // !match
}
lastIndex := 0
result := []byte{}
replaceValue := call.Argument(1)
if replaceValue.isCallable() {
target := string(target)
replace := replaceValue.object()
for _, match := range found {
if match[0] != lastIndex {
result = append(result, target[lastIndex:match[0]]...)
}
2024-04-04 14:46:14 +00:00
matchCount := len(match) / 2
argumentList := make([]Value, matchCount+2)
for index := 0; index < matchCount; index++ {
offset := 2 * index
if match[offset] != -1 {
argumentList[index] = stringValue(target[match[offset]:match[offset+1]])
} else {
argumentList[index] = Value{}
}
}
2024-04-04 14:46:14 +00:00
argumentList[matchCount+0] = intValue(match[0])
argumentList[matchCount+1] = stringValue(target)
replacement := replace.call(Value{}, argumentList, false, nativeFrame).string()
result = append(result, []byte(replacement)...)
lastIndex = match[1]
}
2024-04-04 14:46:14 +00:00
} else {
replace := []byte(replaceValue.string())
for _, match := range found {
result = builtinStringFindAndReplaceString(result, lastIndex, match, target, replace)
lastIndex = match[1]
}
2024-04-04 14:46:14 +00:00
}
2024-04-04 14:46:14 +00:00
if lastIndex != len(target) {
result = append(result, target[lastIndex:]...)
}
2024-04-04 14:46:14 +00:00
if global && searchObject != nil {
searchObject.put("lastIndex", intValue(lastIndex), true)
}
2024-04-04 14:46:14 +00:00
return stringValue(string(result))
}
2024-04-04 14:46:14 +00:00
func builtinStringSearch(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := call.This.string()
searchValue := call.Argument(0)
2024-04-04 14:46:14 +00:00
search := searchValue.object()
if !searchValue.IsObject() || search.class != classRegExpName {
search = call.runtime.newRegExp(searchValue, Value{})
}
result := search.regExpValue().regularExpression.FindStringIndex(target)
if result == nil {
2024-04-04 14:46:14 +00:00
return intValue(-1)
}
2024-04-04 14:46:14 +00:00
return intValue(result[0])
}
2024-04-04 14:46:14 +00:00
func builtinStringSplit(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := call.This.string()
separatorValue := call.Argument(0)
limitValue := call.Argument(1)
limit := -1
if limitValue.IsDefined() {
limit = int(toUint32(limitValue))
}
if limit == 0 {
2024-04-04 14:46:14 +00:00
return objectValue(call.runtime.newArray(0))
}
if separatorValue.IsUndefined() {
2024-04-04 14:46:14 +00:00
return objectValue(call.runtime.newArrayOf([]Value{stringValue(target)}))
}
if separatorValue.isRegExp() {
targetLength := len(target)
2024-04-04 14:46:14 +00:00
search := separatorValue.object().regExpValue().regularExpression
valueArray := []Value{}
result := search.FindAllStringSubmatchIndex(target, -1)
lastIndex := 0
found := 0
for _, match := range result {
if match[0] == match[1] {
// FIXME Ugh, this is a hack
if match[0] == 0 || match[0] == targetLength {
continue
}
}
if lastIndex != match[0] {
2024-04-04 14:46:14 +00:00
valueArray = append(valueArray, stringValue(target[lastIndex:match[0]]))
found++
} else if lastIndex == match[0] {
if lastIndex != -1 {
2024-04-04 14:46:14 +00:00
valueArray = append(valueArray, stringValue(""))
found++
}
}
lastIndex = match[1]
if found == limit {
goto RETURN
}
captureCount := len(match) / 2
for index := 1; index < captureCount; index++ {
offset := index * 2
value := Value{}
if match[offset] != -1 {
2024-04-04 14:46:14 +00:00
value = stringValue(target[match[offset]:match[offset+1]])
}
valueArray = append(valueArray, value)
found++
if found == limit {
goto RETURN
}
}
}
if found != limit {
if lastIndex != targetLength {
2024-04-04 14:46:14 +00:00
valueArray = append(valueArray, stringValue(target[lastIndex:targetLength]))
} else {
2024-04-04 14:46:14 +00:00
valueArray = append(valueArray, stringValue(""))
}
}
RETURN:
2024-04-04 14:46:14 +00:00
return objectValue(call.runtime.newArrayOf(valueArray))
} else {
separator := separatorValue.string()
splitLimit := limit
excess := false
if limit > 0 {
splitLimit = limit + 1
excess = true
}
split := strings.SplitN(target, separator, splitLimit)
if excess && len(split) > limit {
split = split[:limit]
}
valueArray := make([]Value, len(split))
for index, value := range split {
2024-04-04 14:46:14 +00:00
valueArray[index] = stringValue(value)
}
2024-04-04 14:46:14 +00:00
return objectValue(call.runtime.newArrayOf(valueArray))
}
}
2024-04-04 14:46:14 +00:00
func builtinStringSlice(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := call.This.string()
length := int64(len(target))
start, end := rangeStartEnd(call.ArgumentList, length, false)
if end-start <= 0 {
2024-04-04 14:46:14 +00:00
return stringValue("")
}
2024-04-04 14:46:14 +00:00
return stringValue(target[start:end])
}
2024-04-04 14:46:14 +00:00
func builtinStringSubstring(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := []rune(call.This.string())
length := int64(len(target))
start, end := rangeStartEnd(call.ArgumentList, length, true)
if start > end {
start, end = end, start
}
2024-04-04 14:46:14 +00:00
return stringValue(string(target[start:end]))
}
2024-04-04 14:46:14 +00:00
func builtinStringSubstr(call FunctionCall) Value {
target := []rune(call.This.string())
size := int64(len(target))
start, length := rangeStartLength(call.ArgumentList, size)
if start >= size {
2024-04-04 14:46:14 +00:00
return stringValue("")
}
if length <= 0 {
2024-04-04 14:46:14 +00:00
return stringValue("")
}
if start+length >= size {
// Cap length to be to the end of the string
// start = 3, length = 5, size = 4 [0, 1, 2, 3]
// 4 - 3 = 1
// target[3:4]
length = size - start
}
2024-04-04 14:46:14 +00:00
return stringValue(string(target[start : start+length]))
}
2024-04-04 14:46:14 +00:00
func builtinStringStartsWith(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
2024-04-04 14:46:14 +00:00
target := call.This.string()
search := call.Argument(0).string()
length := len(search)
if length > len(target) {
return boolValue(false)
}
return boolValue(target[:length] == search)
}
2024-04-04 14:46:14 +00:00
func builtinStringToLowerCase(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
2024-04-04 14:46:14 +00:00
return stringValue(strings.ToLower(call.This.string()))
}
2024-04-04 14:46:14 +00:00
func builtinStringToUpperCase(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
return stringValue(strings.ToUpper(call.This.string()))
}
2024-04-04 14:46:14 +00:00
// 7.2 Table 2 — Whitespace Characters & 7.3 Table 3 - Line Terminator Characters.
const builtinStringTrimWhitespace = "\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF"
func builtinStringTrim(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
return toValue(strings.Trim(call.This.string(),
2024-04-04 14:46:14 +00:00
builtinStringTrimWhitespace))
}
2024-04-04 14:46:14 +00:00
// Mozilla extension, not ECMAScript 5.
func builtinStringTrimLeft(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
return toValue(strings.TrimLeft(call.This.string(),
2024-04-04 14:46:14 +00:00
builtinStringTrimWhitespace))
}
2024-04-04 14:46:14 +00:00
// Mozilla extension, not ECMAScript 5.
func builtinStringTrimRight(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
return toValue(strings.TrimRight(call.This.string(),
2024-04-04 14:46:14 +00:00
builtinStringTrimWhitespace))
}
2024-04-04 14:46:14 +00:00
func builtinStringLocaleCompare(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
2024-04-04 14:46:14 +00:00
this := call.This.string() //nolint: ifshort
that := call.Argument(0).string()
if this < that {
2024-04-04 14:46:14 +00:00
return intValue(-1)
} else if this == that {
2024-04-04 14:46:14 +00:00
return intValue(0)
}
2024-04-04 14:46:14 +00:00
return intValue(1)
}
2024-04-04 14:46:14 +00:00
func builtinStringToLocaleLowerCase(call FunctionCall) Value {
return builtinStringToLowerCase(call)
}
2024-04-04 14:46:14 +00:00
func builtinStringToLocaleUpperCase(call FunctionCall) Value {
return builtinStringToUpperCase(call)
}