builtin_string.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. package otto
  2. import (
  3. "bytes"
  4. "regexp"
  5. "strconv"
  6. "strings"
  7. "unicode/utf16"
  8. "unicode/utf8"
  9. )
  10. // String
  11. func stringValueFromStringArgumentList(argumentList []Value) Value {
  12. if len(argumentList) > 0 {
  13. return stringValue(argumentList[0].string())
  14. }
  15. return stringValue("")
  16. }
  17. func builtinString(call FunctionCall) Value {
  18. return stringValueFromStringArgumentList(call.ArgumentList)
  19. }
  20. func builtinNewString(obj *object, argumentList []Value) Value {
  21. return objectValue(obj.runtime.newString(stringValueFromStringArgumentList(argumentList)))
  22. }
  23. func builtinStringToString(call FunctionCall) Value {
  24. return call.thisClassObject(classStringName).primitiveValue()
  25. }
  26. func builtinStringValueOf(call FunctionCall) Value {
  27. return call.thisClassObject(classStringName).primitiveValue()
  28. }
  29. func builtinStringFromCharCode(call FunctionCall) Value {
  30. chrList := make([]uint16, len(call.ArgumentList))
  31. for index, value := range call.ArgumentList {
  32. chrList[index] = toUint16(value)
  33. }
  34. return string16Value(chrList)
  35. }
  36. func builtinStringCharAt(call FunctionCall) Value {
  37. checkObjectCoercible(call.runtime, call.This)
  38. idx := int(call.Argument(0).number().int64)
  39. chr := stringAt(call.This.object().stringValue(), idx)
  40. if chr == utf8.RuneError {
  41. return stringValue("")
  42. }
  43. return stringValue(string(chr))
  44. }
  45. func builtinStringCharCodeAt(call FunctionCall) Value {
  46. checkObjectCoercible(call.runtime, call.This)
  47. idx := int(call.Argument(0).number().int64)
  48. chr := stringAt(call.This.object().stringValue(), idx)
  49. if chr == utf8.RuneError {
  50. return NaNValue()
  51. }
  52. return uint16Value(uint16(chr))
  53. }
  54. func builtinStringConcat(call FunctionCall) Value {
  55. checkObjectCoercible(call.runtime, call.This)
  56. var value bytes.Buffer
  57. value.WriteString(call.This.string())
  58. for _, item := range call.ArgumentList {
  59. value.WriteString(item.string())
  60. }
  61. return stringValue(value.String())
  62. }
  63. func lastIndexRune(s, substr string) int {
  64. if i := strings.LastIndex(s, substr); i >= 0 {
  65. return utf16Length(s[:i])
  66. }
  67. return -1
  68. }
  69. func indexRune(s, substr string) int {
  70. if i := strings.Index(s, substr); i >= 0 {
  71. return utf16Length(s[:i])
  72. }
  73. return -1
  74. }
  75. func utf16Length(s string) int {
  76. return len(utf16.Encode([]rune(s)))
  77. }
  78. func builtinStringIndexOf(call FunctionCall) Value {
  79. checkObjectCoercible(call.runtime, call.This)
  80. value := call.This.string()
  81. target := call.Argument(0).string()
  82. if 2 > len(call.ArgumentList) {
  83. return intValue(indexRune(value, target))
  84. }
  85. start := toIntegerFloat(call.Argument(1))
  86. if 0 > start {
  87. start = 0
  88. } else if start >= float64(len(value)) {
  89. if target == "" {
  90. return intValue(len(value))
  91. }
  92. return intValue(-1)
  93. }
  94. index := indexRune(value[int(start):], target)
  95. if index >= 0 {
  96. index += int(start)
  97. }
  98. return intValue(index)
  99. }
  100. func builtinStringLastIndexOf(call FunctionCall) Value {
  101. checkObjectCoercible(call.runtime, call.This)
  102. value := call.This.string()
  103. target := call.Argument(0).string()
  104. if 2 > len(call.ArgumentList) || call.ArgumentList[1].IsUndefined() {
  105. return intValue(lastIndexRune(value, target))
  106. }
  107. length := len(value)
  108. if length == 0 {
  109. return intValue(lastIndexRune(value, target))
  110. }
  111. start := call.ArgumentList[1].number()
  112. if start.kind == numberInfinity { // FIXME
  113. // startNumber is infinity, so start is the end of string (start = length)
  114. return intValue(lastIndexRune(value, target))
  115. }
  116. if 0 > start.int64 {
  117. start.int64 = 0
  118. }
  119. end := int(start.int64) + len(target)
  120. if end > length {
  121. end = length
  122. }
  123. return intValue(lastIndexRune(value[:end], target))
  124. }
  125. func builtinStringMatch(call FunctionCall) Value {
  126. checkObjectCoercible(call.runtime, call.This)
  127. target := call.This.string()
  128. matcherValue := call.Argument(0)
  129. matcher := matcherValue.object()
  130. if !matcherValue.IsObject() || matcher.class != classRegExpName {
  131. matcher = call.runtime.newRegExp(matcherValue, Value{})
  132. }
  133. global := matcher.get("global").bool()
  134. if !global {
  135. match, result := execRegExp(matcher, target)
  136. if !match {
  137. return nullValue
  138. }
  139. return objectValue(execResultToArray(call.runtime, target, result))
  140. }
  141. result := matcher.regExpValue().regularExpression.FindAllStringIndex(target, -1)
  142. if result == nil {
  143. matcher.put("lastIndex", intValue(0), true)
  144. return Value{} // !match
  145. }
  146. matchCount := len(result)
  147. valueArray := make([]Value, matchCount)
  148. for index := range matchCount {
  149. valueArray[index] = stringValue(target[result[index][0]:result[index][1]])
  150. }
  151. matcher.put("lastIndex", intValue(result[matchCount-1][1]), true)
  152. return objectValue(call.runtime.newArrayOf(valueArray))
  153. }
  154. var builtinStringReplaceRegexp = regexp.MustCompile("\\$(?:[\\$\\&\\'\\`1-9]|0[1-9]|[1-9][0-9])")
  155. func builtinStringFindAndReplaceString(input []byte, lastIndex int, match []int, target []byte, replaceValue []byte) []byte {
  156. matchCount := len(match) / 2
  157. output := input
  158. if match[0] != lastIndex {
  159. output = append(output, target[lastIndex:match[0]]...)
  160. }
  161. replacement := builtinStringReplaceRegexp.ReplaceAllFunc(replaceValue, func(part []byte) []byte {
  162. // TODO Check if match[0] or match[1] can be -1 in this scenario
  163. switch part[1] {
  164. case '$':
  165. return []byte{'$'}
  166. case '&':
  167. return target[match[0]:match[1]]
  168. case '`':
  169. return target[:match[0]]
  170. case '\'':
  171. return target[match[1]:]
  172. }
  173. matchNumberParse, err := strconv.ParseInt(string(part[1:]), 10, 64)
  174. if err != nil {
  175. return nil
  176. }
  177. matchNumber := int(matchNumberParse)
  178. if matchNumber >= matchCount {
  179. return nil
  180. }
  181. offset := 2 * matchNumber
  182. if match[offset] != -1 {
  183. return target[match[offset]:match[offset+1]]
  184. }
  185. return nil // The empty string
  186. })
  187. return append(output, replacement...)
  188. }
  189. func builtinStringReplace(call FunctionCall) Value {
  190. checkObjectCoercible(call.runtime, call.This)
  191. target := []byte(call.This.string())
  192. searchValue := call.Argument(0)
  193. searchObject := searchValue.object()
  194. // TODO If a capture is -1?
  195. var search *regexp.Regexp
  196. global := false
  197. find := 1
  198. if searchValue.IsObject() && searchObject.class == classRegExpName {
  199. regExp := searchObject.regExpValue()
  200. search = regExp.regularExpression
  201. if regExp.global {
  202. find = -1
  203. global = true
  204. }
  205. } else {
  206. search = regexp.MustCompile(regexp.QuoteMeta(searchValue.string()))
  207. }
  208. found := search.FindAllSubmatchIndex(target, find)
  209. if found == nil {
  210. return stringValue(string(target)) // !match
  211. }
  212. lastIndex := 0
  213. result := []byte{}
  214. replaceValue := call.Argument(1)
  215. if replaceValue.isCallable() {
  216. target := string(target)
  217. replace := replaceValue.object()
  218. for _, match := range found {
  219. if match[0] != lastIndex {
  220. result = append(result, target[lastIndex:match[0]]...)
  221. }
  222. matchCount := len(match) / 2
  223. argumentList := make([]Value, matchCount+2)
  224. for index := range matchCount {
  225. offset := 2 * index
  226. if match[offset] != -1 {
  227. argumentList[index] = stringValue(target[match[offset]:match[offset+1]])
  228. } else {
  229. argumentList[index] = Value{}
  230. }
  231. }
  232. // Replace expects rune offsets not byte offsets.
  233. startIndex := utf8.RuneCountInString(target[0:match[0]])
  234. argumentList[matchCount+0] = intValue(startIndex)
  235. argumentList[matchCount+1] = stringValue(target)
  236. replacement := replace.call(Value{}, argumentList, false, nativeFrame).string()
  237. result = append(result, []byte(replacement)...)
  238. lastIndex = match[1]
  239. }
  240. } else {
  241. replace := []byte(replaceValue.string())
  242. for _, match := range found {
  243. result = builtinStringFindAndReplaceString(result, lastIndex, match, target, replace)
  244. lastIndex = match[1]
  245. }
  246. }
  247. if lastIndex != len(target) {
  248. result = append(result, target[lastIndex:]...)
  249. }
  250. if global && searchObject != nil {
  251. searchObject.put("lastIndex", intValue(lastIndex), true)
  252. }
  253. return stringValue(string(result))
  254. }
  255. func builtinStringSearch(call FunctionCall) Value {
  256. checkObjectCoercible(call.runtime, call.This)
  257. target := call.This.string()
  258. searchValue := call.Argument(0)
  259. search := searchValue.object()
  260. if !searchValue.IsObject() || search.class != classRegExpName {
  261. search = call.runtime.newRegExp(searchValue, Value{})
  262. }
  263. result := search.regExpValue().regularExpression.FindStringIndex(target)
  264. if result == nil {
  265. return intValue(-1)
  266. }
  267. return intValue(result[0])
  268. }
  269. func builtinStringSplit(call FunctionCall) Value {
  270. checkObjectCoercible(call.runtime, call.This)
  271. target := call.This.string()
  272. separatorValue := call.Argument(0)
  273. limitValue := call.Argument(1)
  274. limit := -1
  275. if limitValue.IsDefined() {
  276. limit = int(toUint32(limitValue))
  277. }
  278. if limit == 0 {
  279. return objectValue(call.runtime.newArray(0))
  280. }
  281. if separatorValue.IsUndefined() {
  282. return objectValue(call.runtime.newArrayOf([]Value{stringValue(target)}))
  283. }
  284. if separatorValue.isRegExp() {
  285. targetLength := len(target)
  286. search := separatorValue.object().regExpValue().regularExpression
  287. valueArray := []Value{}
  288. result := search.FindAllStringSubmatchIndex(target, -1)
  289. lastIndex := 0
  290. found := 0
  291. for _, match := range result {
  292. if match[0] == match[1] {
  293. // FIXME Ugh, this is a hack
  294. if match[0] == 0 || match[0] == targetLength {
  295. continue
  296. }
  297. }
  298. if lastIndex != match[0] {
  299. valueArray = append(valueArray, stringValue(target[lastIndex:match[0]]))
  300. found++
  301. } else if lastIndex == match[0] {
  302. if lastIndex != -1 {
  303. valueArray = append(valueArray, stringValue(""))
  304. found++
  305. }
  306. }
  307. lastIndex = match[1]
  308. if found == limit {
  309. goto RETURN
  310. }
  311. captureCount := len(match) / 2
  312. for index := 1; index < captureCount; index++ {
  313. offset := index * 2
  314. value := Value{}
  315. if match[offset] != -1 {
  316. value = stringValue(target[match[offset]:match[offset+1]])
  317. }
  318. valueArray = append(valueArray, value)
  319. found++
  320. if found == limit {
  321. goto RETURN
  322. }
  323. }
  324. }
  325. if found != limit {
  326. if lastIndex != targetLength {
  327. valueArray = append(valueArray, stringValue(target[lastIndex:targetLength]))
  328. } else {
  329. valueArray = append(valueArray, stringValue(""))
  330. }
  331. }
  332. RETURN:
  333. return objectValue(call.runtime.newArrayOf(valueArray))
  334. } else {
  335. separator := separatorValue.string()
  336. splitLimit := limit
  337. excess := false
  338. if limit > 0 {
  339. splitLimit = limit + 1
  340. excess = true
  341. }
  342. split := strings.SplitN(target, separator, splitLimit)
  343. if excess && len(split) > limit {
  344. split = split[:limit]
  345. }
  346. valueArray := make([]Value, len(split))
  347. for index, value := range split {
  348. valueArray[index] = stringValue(value)
  349. }
  350. return objectValue(call.runtime.newArrayOf(valueArray))
  351. }
  352. }
  353. // builtinStringSlice returns the string sliced by the given values
  354. // which are rune not byte offsets, as per String.prototype.slice.
  355. func builtinStringSlice(call FunctionCall) Value {
  356. checkObjectCoercible(call.runtime, call.This)
  357. target := []rune(call.This.string())
  358. length := int64(len(target))
  359. start, end := rangeStartEnd(call.ArgumentList, length, false)
  360. if end-start <= 0 {
  361. return stringValue("")
  362. }
  363. return stringValue(string(target[start:end]))
  364. }
  365. func builtinStringSubstring(call FunctionCall) Value {
  366. checkObjectCoercible(call.runtime, call.This)
  367. target := []rune(call.This.string())
  368. length := int64(len(target))
  369. start, end := rangeStartEnd(call.ArgumentList, length, true)
  370. if start > end {
  371. start, end = end, start
  372. }
  373. return stringValue(string(target[start:end]))
  374. }
  375. func builtinStringSubstr(call FunctionCall) Value {
  376. target := []rune(call.This.string())
  377. size := int64(len(target))
  378. start, length := rangeStartLength(call.ArgumentList, size)
  379. if start >= size {
  380. return stringValue("")
  381. }
  382. if length <= 0 {
  383. return stringValue("")
  384. }
  385. if start+length >= size {
  386. // Cap length to be to the end of the string
  387. // start = 3, length = 5, size = 4 [0, 1, 2, 3]
  388. // 4 - 3 = 1
  389. // target[3:4]
  390. length = size - start
  391. }
  392. return stringValue(string(target[start : start+length]))
  393. }
  394. func builtinStringStartsWith(call FunctionCall) Value {
  395. checkObjectCoercible(call.runtime, call.This)
  396. target := call.This.string()
  397. search := call.Argument(0).string()
  398. length := len(search)
  399. if length > len(target) {
  400. return boolValue(false)
  401. }
  402. return boolValue(target[:length] == search)
  403. }
  404. func builtinStringToLowerCase(call FunctionCall) Value {
  405. checkObjectCoercible(call.runtime, call.This)
  406. return stringValue(strings.ToLower(call.This.string()))
  407. }
  408. func builtinStringToUpperCase(call FunctionCall) Value {
  409. checkObjectCoercible(call.runtime, call.This)
  410. return stringValue(strings.ToUpper(call.This.string()))
  411. }
  412. // 7.2 Table 2 — Whitespace Characters & 7.3 Table 3 - Line Terminator Characters.
  413. 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"
  414. func builtinStringTrim(call FunctionCall) Value {
  415. checkObjectCoercible(call.runtime, call.This)
  416. return toValue(strings.Trim(call.This.string(),
  417. builtinStringTrimWhitespace))
  418. }
  419. func builtinStringTrimStart(call FunctionCall) Value {
  420. return builtinStringTrimLeft(call)
  421. }
  422. func builtinStringTrimEnd(call FunctionCall) Value {
  423. return builtinStringTrimRight(call)
  424. }
  425. // Mozilla extension, not ECMAScript 5.
  426. func builtinStringTrimLeft(call FunctionCall) Value {
  427. checkObjectCoercible(call.runtime, call.This)
  428. return toValue(strings.TrimLeft(call.This.string(),
  429. builtinStringTrimWhitespace))
  430. }
  431. // Mozilla extension, not ECMAScript 5.
  432. func builtinStringTrimRight(call FunctionCall) Value {
  433. checkObjectCoercible(call.runtime, call.This)
  434. return toValue(strings.TrimRight(call.This.string(),
  435. builtinStringTrimWhitespace))
  436. }
  437. func builtinStringLocaleCompare(call FunctionCall) Value {
  438. checkObjectCoercible(call.runtime, call.This)
  439. this := call.This.string() //nolint:ifshort
  440. that := call.Argument(0).string()
  441. if this < that {
  442. return intValue(-1)
  443. } else if this == that {
  444. return intValue(0)
  445. }
  446. return intValue(1)
  447. }
  448. func builtinStringToLocaleLowerCase(call FunctionCall) Value {
  449. return builtinStringToLowerCase(call)
  450. }
  451. func builtinStringToLocaleUpperCase(call FunctionCall) Value {
  452. return builtinStringToUpperCase(call)
  453. }