aboutsummaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src/github.com/robertkrimen/otto/builtin_string.go
diff options
context:
space:
mode:
Diffstat (limited to 'Godeps/_workspace/src/github.com/robertkrimen/otto/builtin_string.go')
-rw-r--r--Godeps/_workspace/src/github.com/robertkrimen/otto/builtin_string.go500
1 files changed, 500 insertions, 0 deletions
diff --git a/Godeps/_workspace/src/github.com/robertkrimen/otto/builtin_string.go b/Godeps/_workspace/src/github.com/robertkrimen/otto/builtin_string.go
new file mode 100644
index 000000000..6a1718458
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/robertkrimen/otto/builtin_string.go
@@ -0,0 +1,500 @@
+package otto
+
+import (
+ "bytes"
+ "regexp"
+ "strconv"
+ "strings"
+ "unicode/utf8"
+)
+
+// String
+
+func stringValueFromStringArgumentList(argumentList []Value) Value {
+ if len(argumentList) > 0 {
+ return toValue_string(argumentList[0].string())
+ }
+ return toValue_string("")
+}
+
+func builtinString(call FunctionCall) Value {
+ return stringValueFromStringArgumentList(call.ArgumentList)
+}
+
+func builtinNewString(self *_object, argumentList []Value) Value {
+ return toValue_object(self.runtime.newString(stringValueFromStringArgumentList(argumentList)))
+}
+
+func builtinString_toString(call FunctionCall) Value {
+ return call.thisClassObject("String").primitiveValue()
+}
+func builtinString_valueOf(call FunctionCall) Value {
+ return call.thisClassObject("String").primitiveValue()
+}
+
+func builtinString_fromCharCode(call FunctionCall) Value {
+ chrList := make([]uint16, len(call.ArgumentList))
+ for index, value := range call.ArgumentList {
+ chrList[index] = toUint16(value)
+ }
+ return toValue_string16(chrList)
+}
+
+func builtinString_charAt(call FunctionCall) Value {
+ checkObjectCoercible(call.runtime, call.This)
+ idx := int(call.Argument(0).number().int64)
+ chr := stringAt(call.This._object().stringValue(), idx)
+ if chr == utf8.RuneError {
+ return toValue_string("")
+ }
+ return toValue_string(string(chr))
+}
+
+func builtinString_charCodeAt(call FunctionCall) Value {
+ checkObjectCoercible(call.runtime, call.This)
+ idx := int(call.Argument(0).number().int64)
+ chr := stringAt(call.This._object().stringValue(), idx)
+ if chr == utf8.RuneError {
+ return NaNValue()
+ }
+ return toValue_uint16(uint16(chr))
+}
+
+func builtinString_concat(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())
+ }
+ return toValue_string(value.String())
+}
+
+func builtinString_indexOf(call FunctionCall) Value {
+ checkObjectCoercible(call.runtime, call.This)
+ value := call.This.string()
+ target := call.Argument(0).string()
+ if 2 > len(call.ArgumentList) {
+ return toValue_int(strings.Index(value, target))
+ }
+ start := toIntegerFloat(call.Argument(1))
+ if 0 > start {
+ start = 0
+ } else if start >= float64(len(value)) {
+ if target == "" {
+ return toValue_int(len(value))
+ }
+ return toValue_int(-1)
+ }
+ index := strings.Index(value[int(start):], target)
+ if index >= 0 {
+ index += int(start)
+ }
+ return toValue_int(index)
+}
+
+func builtinString_lastIndexOf(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() {
+ return toValue_int(strings.LastIndex(value, target))
+ }
+ length := len(value)
+ if length == 0 {
+ return toValue_int(strings.LastIndex(value, target))
+ }
+ start := call.ArgumentList[1].number()
+ if start.kind == numberInfinity { // FIXME
+ // startNumber is infinity, so start is the end of string (start = length)
+ return toValue_int(strings.LastIndex(value, target))
+ }
+ if 0 > start.int64 {
+ start.int64 = 0
+ }
+ end := int(start.int64) + len(target)
+ if end > length {
+ end = length
+ }
+ return toValue_int(strings.LastIndex(value[:end], target))
+}
+
+func builtinString_match(call FunctionCall) Value {
+ checkObjectCoercible(call.runtime, call.This)
+ target := call.This.string()
+ matcherValue := call.Argument(0)
+ matcher := matcherValue._object()
+ if !matcherValue.IsObject() || matcher.class != "RegExp" {
+ matcher = call.runtime.newRegExp(matcherValue, Value{})
+ }
+ global := matcher.get("global").bool()
+ if !global {
+ match, result := execRegExp(matcher, target)
+ if !match {
+ return nullValue
+ }
+ return toValue_object(execResultToArray(call.runtime, target, result))
+ }
+
+ {
+ result := matcher.regExpValue().regularExpression.FindAllStringIndex(target, -1)
+ matchCount := len(result)
+ if result == nil {
+ matcher.put("lastIndex", toValue_int(0), true)
+ return Value{} // !match
+ }
+ matchCount = len(result)
+ valueArray := make([]Value, matchCount)
+ for index := 0; index < matchCount; index++ {
+ valueArray[index] = toValue_string(target[result[index][0]:result[index][1]])
+ }
+ matcher.put("lastIndex", toValue_int(result[matchCount-1][1]), true)
+ return toValue_object(call.runtime.newArrayOf(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)]
+ }
+ 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.runtime, call.This)
+ target := []byte(call.This.string())
+ 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" {
+ regExp := searchObject.regExpValue()
+ search = regExp.regularExpression
+ if regExp.global {
+ find = -1
+ }
+ } else {
+ search = regexp.MustCompile(regexp.QuoteMeta(searchValue.string()))
+ }
+
+ found := search.FindAllSubmatchIndex(target, find)
+ if found == nil {
+ return toValue_string(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_string(target[match[offset]:match[offset+1]])
+ } else {
+ argumentList[index] = Value{}
+ }
+ }
+ argumentList[matchCount+0] = toValue_int(match[0])
+ argumentList[matchCount+1] = toValue_string(target)
+ replacement := replace.call(Value{}, argumentList, false, nativeFrame).string()
+ result = append(result, []byte(replacement)...)
+ lastIndex = match[1]
+ }
+
+ } else {
+ replace := []byte(replaceValue.string())
+ 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_int(lastIndex), true)
+ }
+
+ return toValue_string(string(result))
+ }
+}
+
+func builtinString_search(call FunctionCall) Value {
+ checkObjectCoercible(call.runtime, call.This)
+ target := call.This.string()
+ searchValue := call.Argument(0)
+ search := searchValue._object()
+ if !searchValue.IsObject() || search.class != "RegExp" {
+ search = call.runtime.newRegExp(searchValue, Value{})
+ }
+ result := search.regExpValue().regularExpression.FindStringIndex(target)
+ if result == nil {
+ return toValue_int(-1)
+ }
+ return toValue_int(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.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 {
+ return toValue_object(call.runtime.newArray(0))
+ }
+
+ if separatorValue.IsUndefined() {
+ return toValue_object(call.runtime.newArrayOf([]Value{toValue_string(target)}))
+ }
+
+ if separatorValue.isRegExp() {
+ targetLength := len(target)
+ 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] {
+ valueArray = append(valueArray, toValue_string(target[lastIndex:match[0]]))
+ found++
+ } else if lastIndex == match[0] {
+ if lastIndex != -1 {
+ valueArray = append(valueArray, toValue_string(""))
+ 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 {
+ value = toValue_string(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_string(target[lastIndex:targetLength]))
+ } else {
+ valueArray = append(valueArray, toValue_string(""))
+ }
+ }
+
+ RETURN:
+ return toValue_object(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 {
+ valueArray[index] = toValue_string(value)
+ }
+
+ return toValue_object(call.runtime.newArrayOf(valueArray))
+ }
+}
+
+func builtinString_slice(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 {
+ return toValue_string("")
+ }
+ return toValue_string(target[start:end])
+}
+
+func builtinString_substring(call FunctionCall) Value {
+ checkObjectCoercible(call.runtime, call.This)
+ target := call.This.string()
+
+ length := int64(len(target))
+ start, end := rangeStartEnd(call.ArgumentList, length, true)
+ if start > end {
+ start, end = end, start
+ }
+ return toValue_string(target[start:end])
+}
+
+func builtinString_substr(call FunctionCall) Value {
+ target := call.This.string()
+
+ size := int64(len(target))
+ start, length := rangeStartLength(call.ArgumentList, size)
+
+ if start >= size {
+ return toValue_string("")
+ }
+
+ if length <= 0 {
+ return toValue_string("")
+ }
+
+ 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_string(target[start : start+length])
+}
+
+func builtinString_toLowerCase(call FunctionCall) Value {
+ checkObjectCoercible(call.runtime, call.This)
+ return toValue_string(strings.ToLower(call.This.string()))
+}
+
+func builtinString_toUpperCase(call FunctionCall) Value {
+ checkObjectCoercible(call.runtime, call.This)
+ return toValue_string(strings.ToUpper(call.This.string()))
+}
+
+// 7.2 Table 2 — Whitespace Characters & 7.3 Table 3 - Line Terminator Characters
+const builtinString_trim_whitespace = "\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 builtinString_trim(call FunctionCall) Value {
+ checkObjectCoercible(call.runtime, call.This)
+ return toValue(strings.Trim(call.This.string(),
+ builtinString_trim_whitespace))
+}
+
+// Mozilla extension, not ECMAScript 5
+func builtinString_trimLeft(call FunctionCall) Value {
+ checkObjectCoercible(call.runtime, call.This)
+ return toValue(strings.TrimLeft(call.This.string(),
+ builtinString_trim_whitespace))
+}
+
+// Mozilla extension, not ECMAScript 5
+func builtinString_trimRight(call FunctionCall) Value {
+ checkObjectCoercible(call.runtime, call.This)
+ return toValue(strings.TrimRight(call.This.string(),
+ builtinString_trim_whitespace))
+}
+
+func builtinString_localeCompare(call FunctionCall) Value {
+ checkObjectCoercible(call.runtime, call.This)
+ this := call.This.string()
+ that := call.Argument(0).string()
+ if this < that {
+ return toValue_int(-1)
+ } else if this == that {
+ return toValue_int(0)
+ }
+ return toValue_int(1)
+}
+
+/*
+An alternate version of String.trim
+func builtinString_trim(call FunctionCall) Value {
+ checkObjectCoercible(call.This)
+ return toValue_string(strings.TrimFunc(call.string(.This), isWhiteSpaceOrLineTerminator))
+}
+*/
+
+func builtinString_toLocaleLowerCase(call FunctionCall) Value {
+ return builtinString_toLowerCase(call)
+}
+
+func builtinString_toLocaleUpperCase(call FunctionCall) Value {
+ return builtinString_toUpperCase(call)
+}