builtin_string.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  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 := 0; index < matchCount; index++ {
  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 := 0; index < matchCount; index++ {
  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. argumentList[matchCount+0] = intValue(match[0])
  233. argumentList[matchCount+1] = stringValue(target)
  234. replacement := replace.call(Value{}, argumentList, false, nativeFrame).string()
  235. result = append(result, []byte(replacement)...)
  236. lastIndex = match[1]
  237. }
  238. } else {
  239. replace := []byte(replaceValue.string())
  240. for _, match := range found {
  241. result = builtinStringFindAndReplaceString(result, lastIndex, match, target, replace)
  242. lastIndex = match[1]
  243. }
  244. }
  245. if lastIndex != len(target) {
  246. result = append(result, target[lastIndex:]...)
  247. }
  248. if global && searchObject != nil {
  249. searchObject.put("lastIndex", intValue(lastIndex), true)
  250. }
  251. return stringValue(string(result))
  252. }
  253. func builtinStringSearch(call FunctionCall) Value {
  254. checkObjectCoercible(call.runtime, call.This)
  255. target := call.This.string()
  256. searchValue := call.Argument(0)
  257. search := searchValue.object()
  258. if !searchValue.IsObject() || search.class != classRegExpName {
  259. search = call.runtime.newRegExp(searchValue, Value{})
  260. }
  261. result := search.regExpValue().regularExpression.FindStringIndex(target)
  262. if result == nil {
  263. return intValue(-1)
  264. }
  265. return intValue(result[0])
  266. }
  267. func builtinStringSplit(call FunctionCall) Value {
  268. checkObjectCoercible(call.runtime, call.This)
  269. target := call.This.string()
  270. separatorValue := call.Argument(0)
  271. limitValue := call.Argument(1)
  272. limit := -1
  273. if limitValue.IsDefined() {
  274. limit = int(toUint32(limitValue))
  275. }
  276. if limit == 0 {
  277. return objectValue(call.runtime.newArray(0))
  278. }
  279. if separatorValue.IsUndefined() {
  280. return objectValue(call.runtime.newArrayOf([]Value{stringValue(target)}))
  281. }
  282. if separatorValue.isRegExp() {
  283. targetLength := len(target)
  284. search := separatorValue.object().regExpValue().regularExpression
  285. valueArray := []Value{}
  286. result := search.FindAllStringSubmatchIndex(target, -1)
  287. lastIndex := 0
  288. found := 0
  289. for _, match := range result {
  290. if match[0] == match[1] {
  291. // FIXME Ugh, this is a hack
  292. if match[0] == 0 || match[0] == targetLength {
  293. continue
  294. }
  295. }
  296. if lastIndex != match[0] {
  297. valueArray = append(valueArray, stringValue(target[lastIndex:match[0]]))
  298. found++
  299. } else if lastIndex == match[0] {
  300. if lastIndex != -1 {
  301. valueArray = append(valueArray, stringValue(""))
  302. found++
  303. }
  304. }
  305. lastIndex = match[1]
  306. if found == limit {
  307. goto RETURN
  308. }
  309. captureCount := len(match) / 2
  310. for index := 1; index < captureCount; index++ {
  311. offset := index * 2
  312. value := Value{}
  313. if match[offset] != -1 {
  314. value = stringValue(target[match[offset]:match[offset+1]])
  315. }
  316. valueArray = append(valueArray, value)
  317. found++
  318. if found == limit {
  319. goto RETURN
  320. }
  321. }
  322. }
  323. if found != limit {
  324. if lastIndex != targetLength {
  325. valueArray = append(valueArray, stringValue(target[lastIndex:targetLength]))
  326. } else {
  327. valueArray = append(valueArray, stringValue(""))
  328. }
  329. }
  330. RETURN:
  331. return objectValue(call.runtime.newArrayOf(valueArray))
  332. } else {
  333. separator := separatorValue.string()
  334. splitLimit := limit
  335. excess := false
  336. if limit > 0 {
  337. splitLimit = limit + 1
  338. excess = true
  339. }
  340. split := strings.SplitN(target, separator, splitLimit)
  341. if excess && len(split) > limit {
  342. split = split[:limit]
  343. }
  344. valueArray := make([]Value, len(split))
  345. for index, value := range split {
  346. valueArray[index] = stringValue(value)
  347. }
  348. return objectValue(call.runtime.newArrayOf(valueArray))
  349. }
  350. }
  351. func builtinStringSlice(call FunctionCall) Value {
  352. checkObjectCoercible(call.runtime, call.This)
  353. target := call.This.string()
  354. length := int64(len(target))
  355. start, end := rangeStartEnd(call.ArgumentList, length, false)
  356. if end-start <= 0 {
  357. return stringValue("")
  358. }
  359. return stringValue(target[start:end])
  360. }
  361. func builtinStringSubstring(call FunctionCall) Value {
  362. checkObjectCoercible(call.runtime, call.This)
  363. target := []rune(call.This.string())
  364. length := int64(len(target))
  365. start, end := rangeStartEnd(call.ArgumentList, length, true)
  366. if start > end {
  367. start, end = end, start
  368. }
  369. return stringValue(string(target[start:end]))
  370. }
  371. func builtinStringSubstr(call FunctionCall) Value {
  372. target := []rune(call.This.string())
  373. size := int64(len(target))
  374. start, length := rangeStartLength(call.ArgumentList, size)
  375. if start >= size {
  376. return stringValue("")
  377. }
  378. if length <= 0 {
  379. return stringValue("")
  380. }
  381. if start+length >= size {
  382. // Cap length to be to the end of the string
  383. // start = 3, length = 5, size = 4 [0, 1, 2, 3]
  384. // 4 - 3 = 1
  385. // target[3:4]
  386. length = size - start
  387. }
  388. return stringValue(string(target[start : start+length]))
  389. }
  390. func builtinStringStartsWith(call FunctionCall) Value {
  391. checkObjectCoercible(call.runtime, call.This)
  392. target := call.This.string()
  393. search := call.Argument(0).string()
  394. length := len(search)
  395. if length > len(target) {
  396. return boolValue(false)
  397. }
  398. return boolValue(target[:length] == search)
  399. }
  400. func builtinStringToLowerCase(call FunctionCall) Value {
  401. checkObjectCoercible(call.runtime, call.This)
  402. return stringValue(strings.ToLower(call.This.string()))
  403. }
  404. func builtinStringToUpperCase(call FunctionCall) Value {
  405. checkObjectCoercible(call.runtime, call.This)
  406. return stringValue(strings.ToUpper(call.This.string()))
  407. }
  408. // 7.2 Table 2 — Whitespace Characters & 7.3 Table 3 - Line Terminator Characters.
  409. 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"
  410. func builtinStringTrim(call FunctionCall) Value {
  411. checkObjectCoercible(call.runtime, call.This)
  412. return toValue(strings.Trim(call.This.string(),
  413. builtinStringTrimWhitespace))
  414. }
  415. // Mozilla extension, not ECMAScript 5.
  416. func builtinStringTrimLeft(call FunctionCall) Value {
  417. checkObjectCoercible(call.runtime, call.This)
  418. return toValue(strings.TrimLeft(call.This.string(),
  419. builtinStringTrimWhitespace))
  420. }
  421. // Mozilla extension, not ECMAScript 5.
  422. func builtinStringTrimRight(call FunctionCall) Value {
  423. checkObjectCoercible(call.runtime, call.This)
  424. return toValue(strings.TrimRight(call.This.string(),
  425. builtinStringTrimWhitespace))
  426. }
  427. func builtinStringLocaleCompare(call FunctionCall) Value {
  428. checkObjectCoercible(call.runtime, call.This)
  429. this := call.This.string() //nolint: ifshort
  430. that := call.Argument(0).string()
  431. if this < that {
  432. return intValue(-1)
  433. } else if this == that {
  434. return intValue(0)
  435. }
  436. return intValue(1)
  437. }
  438. func builtinStringToLocaleLowerCase(call FunctionCall) Value {
  439. return builtinStringToLowerCase(call)
  440. }
  441. func builtinStringToLocaleUpperCase(call FunctionCall) Value {
  442. return builtinStringToUpperCase(call)
  443. }