1
0
mirror of https://github.com/robertkrimen/otto synced 2025-10-12 20:27:30 +08:00
otto/builtin_string.go
2013-02-21 14:25:04 -08:00

445 lines
11 KiB
Go

package otto
import (
"bytes"
"math"
"regexp"
"strconv"
"strings"
"unicode/utf16"
)
// String
func stringValueFromStringArgumentList(argumentList []Value) Value {
if len(argumentList) > 0 {
return toValue(toString(argumentList[0]))
}
return toValue("")
}
func builtinString(call FunctionCall) Value {
return stringValueFromStringArgumentList(call.ArgumentList)
}
func builtinNewString(self *_object, _ Value, argumentList []Value) Value {
return toValue(self.runtime.newString(stringValueFromStringArgumentList(argumentList)))
}
func builtinString_fromCharCode(call FunctionCall) Value {
chrList := make([]uint16, len(call.ArgumentList))
for index, value := range call.ArgumentList {
chrList[index] = toUint16(value)
}
return toValue(string(utf16.Decode(chrList)))
}
func builtinString_charAt(call FunctionCall) Value {
checkObjectCoercible(call.This)
value := toString(call.This)
value16 := utf16.Encode([]rune(value))
index := toInteger(call.Argument(0))
if 0 > index || index >= int64(len(value16)) {
return toValue("")
}
return toValue(string(value16[index]))
}
func builtinString_charCodeAt(call FunctionCall) Value {
checkObjectCoercible(call.This)
value := toString(call.This)
value16 := utf16.Encode([]rune(value))
index := toInteger(call.Argument(0))
if 0 > index || index >= int64(len(value16)) {
return NaNValue()
}
return toValue(value16[index])
}
func builtinString_concat(call FunctionCall) Value {
checkObjectCoercible(call.This)
var value bytes.Buffer
value.WriteString(toString(call.This))
for _, item := range call.ArgumentList {
value.WriteString(toString(item))
}
return toValue(value.String())
}
func builtinString_indexOf(call FunctionCall) Value {
checkObjectCoercible(call.This)
value := toString(call.This)
target := toString(call.Argument(0))
if 2 > len(call.ArgumentList) {
return toValue(strings.Index(value, target))
}
start := float64(_toInteger(call.Argument(1)))
if 0 > start {
start = 0
} else if start >= float64(len(value)) {
if target == "" {
return toValue(len(value))
}
return toValue(-1)
}
return toValue(strings.Index(value[int(start):], target))
}
func builtinString_lastIndexOf(call FunctionCall) Value {
checkObjectCoercible(call.This)
value := toString(call.This)
target := toString(call.Argument(0))
if 2 > len(call.ArgumentList) || call.ArgumentList[1].IsUndefined() {
return toValue(strings.LastIndex(value, target))
}
length := len(value)
if length == 0 {
return toValue(strings.LastIndex(value, target))
}
startNumber := toFloat(call.ArgumentList[1])
start := int64(0)
if math.IsNaN(startNumber) || math.IsInf(startNumber, 0) {
// startNumber is infinity, so start is the end of string (start = length)
return toValue(strings.LastIndex(value, target))
} else {
start = toInteger(call.ArgumentList[1])
}
if 0 > start {
start = 0
} else if start >= int64(length) {
return toValue(strings.LastIndex(value, target))
}
return toValue(strings.LastIndex(value[:start], target))
}
func builtinString_match(call FunctionCall) Value {
checkObjectCoercible(call.This)
target := toString(call.This)
matcherValue := call.Argument(0)
matcher := matcherValue._object()
if !matcherValue.IsObject() || matcher.class != "RegExp" {
matcher = call.runtime.newRegExp(matcherValue, UndefinedValue())
}
global := toBoolean(matcher.get("global"))
if !global {
match, result := execRegExp(matcher, target)
if !match {
return NullValue()
}
return toValue(execResultToArray(call.runtime, target, result))
}
{
result := matcher._RegExp.RegularExpression.FindAllStringIndex(target, -1)
matchCount := len(result)
if result == nil {
matcher.set("lastIndex", toValue(0), true)
return UndefinedValue() // !match
}
matchCount = len(result)
valueArray := make([]Value, matchCount)
for index := 0; index < matchCount; index++ {
valueArray[index] = toValue(target[result[index][0]:result[index][1]])
}
matcher.set("lastIndex", toValue(result[matchCount-1][1]), true)
return toValue(call.runtime.newArray(valueArray))
}
}
var builtinString_replace_Regexp = regexp.MustCompile("\\$(?:[\\$\\&\\'\\`1-9]|0[1-9]|[1-9][0-9])")
func builtinString_findAndReplaceString(input []byte, lastIndex int, match []int, target []byte, replaceValue []byte) (output []byte) {
matchCount := len(match) / 2
output = input
if match[0] != lastIndex {
output = append(output, target[lastIndex:match[0]]...)
}
replacement := builtinString_replace_Regexp.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] : len(target)-1]
}
matchNumberParse, error := strconv.ParseInt(string(part[1:]), 10, 64)
matchNumber := int(matchNumberParse)
if error != nil || matchNumber >= matchCount {
return []byte{}
}
offset := 2 * matchNumber
if match[offset] != -1 {
return target[match[offset]:match[offset+1]]
}
return []byte{} // The empty string
})
output = append(output, replacement...)
return output
}
func builtinString_replace(call FunctionCall) Value {
checkObjectCoercible(call.This)
target := []byte(toString(call.This))
searchValue := call.Argument(0)
searchObject := searchValue._object()
// TODO If a capture is -1?
var search *regexp.Regexp
global := false
find := 1
if searchValue.IsObject() && searchObject.class == "RegExp" {
search = searchObject._RegExp.RegularExpression
global = toBoolean(searchObject.get("global"))
if global {
find = -1
}
} else {
search = regexp.MustCompile(regexp.QuoteMeta(toString(searchValue)))
}
found := search.FindAllSubmatchIndex(target, find)
if found == nil {
return toValue(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]]...)
}
matchCount := len(match) / 2
argumentList := make([]Value, matchCount+2)
for index := 0; index < matchCount; index++ {
offset := 2 * index
if match[offset] != -1 {
argumentList[index] = toValue(target[match[offset]:match[offset+1]])
} else {
argumentList[index] = UndefinedValue()
}
}
argumentList[matchCount+0] = toValue(match[0])
argumentList[matchCount+1] = toValue(target)
replacement := toString(replace.Call(UndefinedValue(), argumentList))
result = append(result, []byte(replacement)...)
lastIndex = match[1]
}
} else {
replace := []byte(toString(replaceValue))
for _, match := range found {
result = builtinString_findAndReplaceString(result, lastIndex, match, target, replace)
lastIndex = match[1]
}
}
if lastIndex != len(target) {
result = append(result, target[lastIndex:]...)
}
if global && searchObject != nil {
searchObject.put("lastIndex", toValue(lastIndex), true)
}
return toValue(string(result))
}
return UndefinedValue()
}
func builtinString_search(call FunctionCall) Value {
checkObjectCoercible(call.This)
target := toString(call.This)
searchValue := call.Argument(0)
search := searchValue._object()
if !searchValue.IsObject() || search.class != "RegExp" {
search = call.runtime.newRegExp(searchValue, UndefinedValue())
}
result := search._RegExp.RegularExpression.FindStringIndex(target)
if result == nil {
return toValue(-1)
}
return toValue(result[0])
}
func stringSplitMatch(target string, targetLength int64, index uint, search string, searchLength int64) (bool, uint) {
if int64(index)+searchLength > searchLength {
return false, 0
}
found := strings.Index(target[index:], search)
if 0 > found {
return false, 0
}
return true, uint(found)
}
func builtinString_split(call FunctionCall) Value {
checkObjectCoercible(call.This)
target := toString(call.This)
separatorValue := call.Argument(0)
limitValue := call.Argument(1)
limit := -1
if limitValue.IsDefined() {
limit = int(toUint32(limitValue))
}
if limit == 0 {
return toValue(call.runtime.newArray([]Value{}))
}
if separatorValue.IsUndefined() {
return toValue(call.runtime.newArray([]Value{toValue(target)}))
}
if separatorValue.isRegExp() {
targetLength := len(target)
search := separatorValue._object()._RegExp.RegularExpression
valueArray := []Value{}
result := search.FindAllStringSubmatchIndex(target, -1)
lastIndex := 0
found := 0
for _, match := range result {
if match[0] == match[1] {
// An "empty" match
continue
}
if lastIndex != match[0] {
valueArray = append(valueArray, toValue(target[lastIndex:match[0]]))
found++
} else if lastIndex == match[0] {
if lastIndex != -1 {
valueArray = append(valueArray, toValue(""))
found++
}
}
lastIndex = match[1]
if found == limit {
goto RETURN
}
captureCount := len(match) / 2
for index := 1; index < captureCount; index++ {
offset := index * 2
value := UndefinedValue()
if match[offset] != -1 {
value = toValue(target[match[offset]:match[offset+1]])
}
valueArray = append(valueArray, value)
found++
if found == limit {
goto RETURN
}
}
}
if found != limit {
if lastIndex != targetLength {
valueArray = append(valueArray, toValue(target[lastIndex:targetLength]))
} else {
valueArray = append(valueArray, toValue(""))
}
}
RETURN:
return toValue(call.runtime.newArray(valueArray))
} else {
separator := toString(separatorValue)
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 {
valueArray[index] = toValue(value)
}
return toValue(call.runtime.newArray(valueArray))
}
return UndefinedValue()
}
func builtinString_slice(call FunctionCall) Value {
checkObjectCoercible(call.This)
target := toString(call.This)
length := uint(len(target))
start, end := rangeStartEnd(call.ArgumentList, length, false)
if end-start <= 0 {
return toValue("")
}
return toValue(target[start:end])
}
func builtinString_substring(call FunctionCall) Value {
checkObjectCoercible(call.This)
target := toString(call.This)
length := uint(len(target))
start, end := rangeStartEnd(call.ArgumentList, length, true)
if start > end {
start, end = end, start
}
return toValue(target[start:end])
}
func builtinString_substr(call FunctionCall) Value {
target := toString(call.This)
size := int64(len(target))
start, length := rangeStartLength(call.ArgumentList, uint(size))
if start >= size {
return toValue("")
}
if length <= 0 {
return toValue("")
}
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
}
return toValue(target[start : start+length])
}
func builtinString_toLowerCase(call FunctionCall) Value {
checkObjectCoercible(call.This)
return toValue(strings.ToLower(toString(call.This)))
}
func builtinString_toUpperCase(call FunctionCall) Value {
checkObjectCoercible(call.This)
return toValue(strings.ToUpper(toString(call.This)))
}