builtin_string.go 13 KB

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