1 /* 2 * Copyright (C) 1999-2001 Harri Porten (porten (at) kde.org) 3 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. 4 * Copyright (C) 2009 Torch Mobile, Inc. 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 * 20 */ 21 22 #include "config.h" 23 #include "StringPrototype.h" 24 25 #include "CachedCall.h" 26 #include "Error.h" 27 #include "Executable.h" 28 #include "JSGlobalObjectFunctions.h" 29 #include "JSArray.h" 30 #include "JSFunction.h" 31 #include "JSStringBuilder.h" 32 #include "ObjectPrototype.h" 33 #include "Operations.h" 34 #include "PropertyNameArray.h" 35 #include "RegExpConstructor.h" 36 #include "RegExpObject.h" 37 #include <wtf/ASCIICType.h> 38 #include <wtf/MathExtras.h> 39 #include <wtf/unicode/Collator.h> 40 41 using namespace WTF; 42 43 namespace JSC { 44 45 ASSERT_CLASS_FITS_IN_CELL(StringPrototype); 46 47 static JSValue JSC_HOST_CALL stringProtoFuncToString(ExecState*, JSObject*, JSValue, const ArgList&); 48 static JSValue JSC_HOST_CALL stringProtoFuncCharAt(ExecState*, JSObject*, JSValue, const ArgList&); 49 static JSValue JSC_HOST_CALL stringProtoFuncCharCodeAt(ExecState*, JSObject*, JSValue, const ArgList&); 50 static JSValue JSC_HOST_CALL stringProtoFuncConcat(ExecState*, JSObject*, JSValue, const ArgList&); 51 static JSValue JSC_HOST_CALL stringProtoFuncIndexOf(ExecState*, JSObject*, JSValue, const ArgList&); 52 static JSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(ExecState*, JSObject*, JSValue, const ArgList&); 53 static JSValue JSC_HOST_CALL stringProtoFuncMatch(ExecState*, JSObject*, JSValue, const ArgList&); 54 static JSValue JSC_HOST_CALL stringProtoFuncReplace(ExecState*, JSObject*, JSValue, const ArgList&); 55 static JSValue JSC_HOST_CALL stringProtoFuncSearch(ExecState*, JSObject*, JSValue, const ArgList&); 56 static JSValue JSC_HOST_CALL stringProtoFuncSlice(ExecState*, JSObject*, JSValue, const ArgList&); 57 static JSValue JSC_HOST_CALL stringProtoFuncSplit(ExecState*, JSObject*, JSValue, const ArgList&); 58 static JSValue JSC_HOST_CALL stringProtoFuncSubstr(ExecState*, JSObject*, JSValue, const ArgList&); 59 static JSValue JSC_HOST_CALL stringProtoFuncSubstring(ExecState*, JSObject*, JSValue, const ArgList&); 60 static JSValue JSC_HOST_CALL stringProtoFuncToLowerCase(ExecState*, JSObject*, JSValue, const ArgList&); 61 static JSValue JSC_HOST_CALL stringProtoFuncToUpperCase(ExecState*, JSObject*, JSValue, const ArgList&); 62 static JSValue JSC_HOST_CALL stringProtoFuncLocaleCompare(ExecState*, JSObject*, JSValue, const ArgList&); 63 64 static JSValue JSC_HOST_CALL stringProtoFuncBig(ExecState*, JSObject*, JSValue, const ArgList&); 65 static JSValue JSC_HOST_CALL stringProtoFuncSmall(ExecState*, JSObject*, JSValue, const ArgList&); 66 static JSValue JSC_HOST_CALL stringProtoFuncBlink(ExecState*, JSObject*, JSValue, const ArgList&); 67 static JSValue JSC_HOST_CALL stringProtoFuncBold(ExecState*, JSObject*, JSValue, const ArgList&); 68 static JSValue JSC_HOST_CALL stringProtoFuncFixed(ExecState*, JSObject*, JSValue, const ArgList&); 69 static JSValue JSC_HOST_CALL stringProtoFuncItalics(ExecState*, JSObject*, JSValue, const ArgList&); 70 static JSValue JSC_HOST_CALL stringProtoFuncStrike(ExecState*, JSObject*, JSValue, const ArgList&); 71 static JSValue JSC_HOST_CALL stringProtoFuncSub(ExecState*, JSObject*, JSValue, const ArgList&); 72 static JSValue JSC_HOST_CALL stringProtoFuncSup(ExecState*, JSObject*, JSValue, const ArgList&); 73 static JSValue JSC_HOST_CALL stringProtoFuncFontcolor(ExecState*, JSObject*, JSValue, const ArgList&); 74 static JSValue JSC_HOST_CALL stringProtoFuncFontsize(ExecState*, JSObject*, JSValue, const ArgList&); 75 static JSValue JSC_HOST_CALL stringProtoFuncAnchor(ExecState*, JSObject*, JSValue, const ArgList&); 76 static JSValue JSC_HOST_CALL stringProtoFuncLink(ExecState*, JSObject*, JSValue, const ArgList&); 77 78 static JSValue JSC_HOST_CALL stringProtoFuncTrim(ExecState*, JSObject*, JSValue, const ArgList&); 79 static JSValue JSC_HOST_CALL stringProtoFuncTrimLeft(ExecState*, JSObject*, JSValue, const ArgList&); 80 static JSValue JSC_HOST_CALL stringProtoFuncTrimRight(ExecState*, JSObject*, JSValue, const ArgList&); 81 82 } 83 84 #include "StringPrototype.lut.h" 85 86 namespace JSC { 87 88 const ClassInfo StringPrototype::info = { "String", &StringObject::info, 0, ExecState::stringTable }; 89 90 /* Source for StringPrototype.lut.h 91 @begin stringTable 26 92 toString stringProtoFuncToString DontEnum|Function 0 93 valueOf stringProtoFuncToString DontEnum|Function 0 94 charAt stringProtoFuncCharAt DontEnum|Function 1 95 charCodeAt stringProtoFuncCharCodeAt DontEnum|Function 1 96 concat stringProtoFuncConcat DontEnum|Function 1 97 indexOf stringProtoFuncIndexOf DontEnum|Function 1 98 lastIndexOf stringProtoFuncLastIndexOf DontEnum|Function 1 99 match stringProtoFuncMatch DontEnum|Function 1 100 replace stringProtoFuncReplace DontEnum|Function 2 101 search stringProtoFuncSearch DontEnum|Function 1 102 slice stringProtoFuncSlice DontEnum|Function 2 103 split stringProtoFuncSplit DontEnum|Function 2 104 substr stringProtoFuncSubstr DontEnum|Function 2 105 substring stringProtoFuncSubstring DontEnum|Function 2 106 toLowerCase stringProtoFuncToLowerCase DontEnum|Function 0 107 toUpperCase stringProtoFuncToUpperCase DontEnum|Function 0 108 localeCompare stringProtoFuncLocaleCompare DontEnum|Function 1 109 110 # toLocaleLowerCase and toLocaleUpperCase are currently identical to toLowerCase and toUpperCase 111 toLocaleLowerCase stringProtoFuncToLowerCase DontEnum|Function 0 112 toLocaleUpperCase stringProtoFuncToUpperCase DontEnum|Function 0 113 114 big stringProtoFuncBig DontEnum|Function 0 115 small stringProtoFuncSmall DontEnum|Function 0 116 blink stringProtoFuncBlink DontEnum|Function 0 117 bold stringProtoFuncBold DontEnum|Function 0 118 fixed stringProtoFuncFixed DontEnum|Function 0 119 italics stringProtoFuncItalics DontEnum|Function 0 120 strike stringProtoFuncStrike DontEnum|Function 0 121 sub stringProtoFuncSub DontEnum|Function 0 122 sup stringProtoFuncSup DontEnum|Function 0 123 fontcolor stringProtoFuncFontcolor DontEnum|Function 1 124 fontsize stringProtoFuncFontsize DontEnum|Function 1 125 anchor stringProtoFuncAnchor DontEnum|Function 1 126 link stringProtoFuncLink DontEnum|Function 1 127 trim stringProtoFuncTrim DontEnum|Function 0 128 trimLeft stringProtoFuncTrimLeft DontEnum|Function 0 129 trimRight stringProtoFuncTrimRight DontEnum|Function 0 130 @end 131 */ 132 133 // ECMA 15.5.4 134 StringPrototype::StringPrototype(ExecState* exec, NonNullPassRefPtr<Structure> structure) 135 : StringObject(exec, structure) 136 { 137 // The constructor will be added later, after StringConstructor has been built 138 putDirectWithoutTransition(exec->propertyNames().length, jsNumber(exec, 0), DontDelete | ReadOnly | DontEnum); 139 } 140 141 bool StringPrototype::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot &slot) 142 { 143 return getStaticFunctionSlot<StringObject>(exec, ExecState::stringTable(exec), this, propertyName, slot); 144 } 145 146 bool StringPrototype::getOwnPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor) 147 { 148 return getStaticFunctionDescriptor<StringObject>(exec, ExecState::stringTable(exec), this, propertyName, descriptor); 149 } 150 151 // ------------------------------ Functions -------------------------- 152 153 static NEVER_INLINE UString substituteBackreferencesSlow(const UString& replacement, const UString& source, const int* ovector, RegExp* reg, int i) 154 { 155 Vector<UChar> substitutedReplacement; 156 int offset = 0; 157 do { 158 if (i + 1 == replacement.size()) 159 break; 160 161 UChar ref = replacement[i + 1]; 162 if (ref == '$') { 163 // "$$" -> "$" 164 ++i; 165 substitutedReplacement.append(replacement.data() + offset, i - offset); 166 offset = i + 1; 167 continue; 168 } 169 170 int backrefStart; 171 int backrefLength; 172 int advance = 0; 173 if (ref == '&') { 174 backrefStart = ovector[0]; 175 backrefLength = ovector[1] - backrefStart; 176 } else if (ref == '`') { 177 backrefStart = 0; 178 backrefLength = ovector[0]; 179 } else if (ref == '\'') { 180 backrefStart = ovector[1]; 181 backrefLength = source.size() - backrefStart; 182 } else if (reg && ref >= '0' && ref <= '9') { 183 // 1- and 2-digit back references are allowed 184 unsigned backrefIndex = ref - '0'; 185 if (backrefIndex > reg->numSubpatterns()) 186 continue; 187 if (replacement.size() > i + 2) { 188 ref = replacement[i + 2]; 189 if (ref >= '0' && ref <= '9') { 190 backrefIndex = 10 * backrefIndex + ref - '0'; 191 if (backrefIndex > reg->numSubpatterns()) 192 backrefIndex = backrefIndex / 10; // Fall back to the 1-digit reference 193 else 194 advance = 1; 195 } 196 } 197 if (!backrefIndex) 198 continue; 199 backrefStart = ovector[2 * backrefIndex]; 200 backrefLength = ovector[2 * backrefIndex + 1] - backrefStart; 201 } else 202 continue; 203 204 if (i - offset) 205 substitutedReplacement.append(replacement.data() + offset, i - offset); 206 i += 1 + advance; 207 offset = i + 1; 208 substitutedReplacement.append(source.data() + backrefStart, backrefLength); 209 } while ((i = replacement.find('$', i + 1)) != -1); 210 211 if (replacement.size() - offset) 212 substitutedReplacement.append(replacement.data() + offset, replacement.size() - offset); 213 214 substitutedReplacement.shrinkToFit(); 215 return UString::adopt(substitutedReplacement); 216 } 217 218 static inline UString substituteBackreferences(const UString& replacement, const UString& source, const int* ovector, RegExp* reg) 219 { 220 int i = replacement.find('$', 0); 221 if (UNLIKELY(i != -1)) 222 return substituteBackreferencesSlow(replacement, source, ovector, reg, i); 223 return replacement; 224 } 225 226 static inline int localeCompare(const UString& a, const UString& b) 227 { 228 return Collator::userDefault()->collate(reinterpret_cast<const ::UChar*>(a.data()), a.size(), reinterpret_cast<const ::UChar*>(b.data()), b.size()); 229 } 230 231 struct StringRange { 232 public: 233 StringRange(int pos, int len) 234 : position(pos) 235 , length(len) 236 { 237 } 238 239 StringRange() 240 { 241 } 242 243 int position; 244 int length; 245 }; 246 247 JSValue jsSpliceSubstringsWithSeparators(ExecState* exec, JSString* sourceVal, const UString& source, const StringRange* substringRanges, int rangeCount, const UString* separators, int separatorCount); 248 JSValue jsSpliceSubstringsWithSeparators(ExecState* exec, JSString* sourceVal, const UString& source, const StringRange* substringRanges, int rangeCount, const UString* separators, int separatorCount) 249 { 250 if (rangeCount == 1 && separatorCount == 0) { 251 int sourceSize = source.size(); 252 int position = substringRanges[0].position; 253 int length = substringRanges[0].length; 254 if (position <= 0 && length >= sourceSize) 255 return sourceVal; 256 // We could call UString::substr, but this would result in redundant checks 257 return jsString(exec, UStringImpl::create(source.rep(), max(0, position), min(sourceSize, length))); 258 } 259 260 int totalLength = 0; 261 for (int i = 0; i < rangeCount; i++) 262 totalLength += substringRanges[i].length; 263 for (int i = 0; i < separatorCount; i++) 264 totalLength += separators[i].size(); 265 266 if (totalLength == 0) 267 return jsString(exec, ""); 268 269 UChar* buffer; 270 PassRefPtr<UStringImpl> impl = UStringImpl::tryCreateUninitialized(totalLength, buffer); 271 if (!impl) 272 return throwOutOfMemoryError(exec); 273 274 int maxCount = max(rangeCount, separatorCount); 275 int bufferPos = 0; 276 for (int i = 0; i < maxCount; i++) { 277 if (i < rangeCount) { 278 UStringImpl::copyChars(buffer + bufferPos, source.data() + substringRanges[i].position, substringRanges[i].length); 279 bufferPos += substringRanges[i].length; 280 } 281 if (i < separatorCount) { 282 UStringImpl::copyChars(buffer + bufferPos, separators[i].data(), separators[i].size()); 283 bufferPos += separators[i].size(); 284 } 285 } 286 287 return jsString(exec, impl); 288 } 289 290 JSValue jsReplaceRange(ExecState* exec, const UString& source, int rangeStart, int rangeLength, const UString& replacement); 291 JSValue jsReplaceRange(ExecState* exec, const UString& source, int rangeStart, int rangeLength, const UString& replacement) 292 { 293 int replacementLength = replacement.size(); 294 int totalLength = source.size() - rangeLength + replacementLength; 295 if (totalLength == 0) 296 return jsString(exec, ""); 297 298 UChar* buffer; 299 PassRefPtr<UStringImpl> impl = UStringImpl::tryCreateUninitialized(totalLength, buffer); 300 if (!impl) 301 return throwOutOfMemoryError(exec); 302 303 UStringImpl::copyChars(buffer, source.data(), rangeStart); 304 UStringImpl::copyChars(buffer + rangeStart, replacement.data(), replacementLength); 305 int rangeEnd = rangeStart + rangeLength; 306 UStringImpl::copyChars(buffer + rangeStart + replacementLength, source.data() + rangeEnd, source.size() - rangeEnd); 307 308 return jsString(exec, impl); 309 } 310 311 JSValue JSC_HOST_CALL stringProtoFuncReplace(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 312 { 313 JSString* sourceVal = thisValue.toThisJSString(exec); 314 const UString& source = sourceVal->value(exec); 315 316 JSValue pattern = args.at(0); 317 318 JSValue replacement = args.at(1); 319 UString replacementString; 320 CallData callData; 321 CallType callType = replacement.getCallData(callData); 322 if (callType == CallTypeNone) 323 replacementString = replacement.toString(exec); 324 325 if (pattern.inherits(&RegExpObject::info)) { 326 RegExp* reg = asRegExpObject(pattern)->regExp(); 327 bool global = reg->global(); 328 329 RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor(); 330 331 int lastIndex = 0; 332 int startPosition = 0; 333 334 Vector<StringRange, 16> sourceRanges; 335 Vector<UString, 16> replacements; 336 337 // This is either a loop (if global is set) or a one-way (if not). 338 if (global && callType == CallTypeJS) { 339 // reg->numSubpatterns() + 1 for pattern args, + 2 for match start and sourceValue 340 int argCount = reg->numSubpatterns() + 1 + 2; 341 JSFunction* func = asFunction(replacement); 342 CachedCall cachedCall(exec, func, argCount, exec->exceptionSlot()); 343 if (exec->hadException()) 344 return jsNull(); 345 while (true) { 346 int matchIndex; 347 int matchLen = 0; 348 int* ovector; 349 regExpConstructor->performMatch(reg, source, startPosition, matchIndex, matchLen, &ovector); 350 if (matchIndex < 0) 351 break; 352 353 sourceRanges.append(StringRange(lastIndex, matchIndex - lastIndex)); 354 355 int completeMatchStart = ovector[0]; 356 unsigned i = 0; 357 for (; i < reg->numSubpatterns() + 1; ++i) { 358 int matchStart = ovector[i * 2]; 359 int matchLen = ovector[i * 2 + 1] - matchStart; 360 361 if (matchStart < 0) 362 cachedCall.setArgument(i, jsUndefined()); 363 else 364 cachedCall.setArgument(i, jsSubstring(exec, source, matchStart, matchLen)); 365 } 366 367 cachedCall.setArgument(i++, jsNumber(exec, completeMatchStart)); 368 cachedCall.setArgument(i++, sourceVal); 369 370 cachedCall.setThis(exec->globalThisValue()); 371 JSValue result = cachedCall.call(); 372 replacements.append(result.toString(cachedCall.newCallFrame(exec))); 373 if (exec->hadException()) 374 break; 375 376 lastIndex = matchIndex + matchLen; 377 startPosition = lastIndex; 378 379 // special case of empty match 380 if (matchLen == 0) { 381 startPosition++; 382 if (startPosition > source.size()) 383 break; 384 } 385 } 386 } else { 387 do { 388 int matchIndex; 389 int matchLen = 0; 390 int* ovector; 391 regExpConstructor->performMatch(reg, source, startPosition, matchIndex, matchLen, &ovector); 392 if (matchIndex < 0) 393 break; 394 395 sourceRanges.append(StringRange(lastIndex, matchIndex - lastIndex)); 396 397 if (callType != CallTypeNone) { 398 int completeMatchStart = ovector[0]; 399 MarkedArgumentBuffer args; 400 401 for (unsigned i = 0; i < reg->numSubpatterns() + 1; ++i) { 402 int matchStart = ovector[i * 2]; 403 int matchLen = ovector[i * 2 + 1] - matchStart; 404 405 if (matchStart < 0) 406 args.append(jsUndefined()); 407 else 408 args.append(jsSubstring(exec, source, matchStart, matchLen)); 409 } 410 411 args.append(jsNumber(exec, completeMatchStart)); 412 args.append(sourceVal); 413 414 replacements.append(call(exec, replacement, callType, callData, exec->globalThisValue(), args).toString(exec)); 415 if (exec->hadException()) 416 break; 417 } else 418 replacements.append(substituteBackreferences(replacementString, source, ovector, reg)); 419 420 lastIndex = matchIndex + matchLen; 421 startPosition = lastIndex; 422 423 // special case of empty match 424 if (matchLen == 0) { 425 startPosition++; 426 if (startPosition > source.size()) 427 break; 428 } 429 } while (global); 430 } 431 432 if (!lastIndex && replacements.isEmpty()) 433 return sourceVal; 434 435 if (lastIndex < source.size()) 436 sourceRanges.append(StringRange(lastIndex, source.size() - lastIndex)); 437 438 return jsSpliceSubstringsWithSeparators(exec, sourceVal, source, sourceRanges.data(), sourceRanges.size(), replacements.data(), replacements.size()); 439 } 440 441 // Not a regular expression, so treat the pattern as a string. 442 443 UString patternString = pattern.toString(exec); 444 int matchPos = source.find(patternString); 445 446 if (matchPos == -1) 447 return sourceVal; 448 449 int matchLen = patternString.size(); 450 if (callType != CallTypeNone) { 451 MarkedArgumentBuffer args; 452 args.append(jsSubstring(exec, source, matchPos, matchLen)); 453 args.append(jsNumber(exec, matchPos)); 454 args.append(sourceVal); 455 456 replacementString = call(exec, replacement, callType, callData, exec->globalThisValue(), args).toString(exec); 457 } 458 459 int ovector[2] = { matchPos, matchPos + matchLen }; 460 return jsReplaceRange(exec, source, matchPos, matchLen, substituteBackreferences(replacementString, source, ovector, 0)); 461 } 462 463 JSValue JSC_HOST_CALL stringProtoFuncToString(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 464 { 465 // Also used for valueOf. 466 467 if (thisValue.isString()) 468 return thisValue; 469 470 if (thisValue.inherits(&StringObject::info)) 471 return asStringObject(thisValue)->internalValue(); 472 473 return throwError(exec, TypeError); 474 } 475 476 JSValue JSC_HOST_CALL stringProtoFuncCharAt(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 477 { 478 UString s = thisValue.toThisString(exec); 479 unsigned len = s.size(); 480 JSValue a0 = args.at(0); 481 if (a0.isUInt32()) { 482 uint32_t i = a0.asUInt32(); 483 if (i < len) 484 return jsSingleCharacterSubstring(exec, s, i); 485 return jsEmptyString(exec); 486 } 487 double dpos = a0.toInteger(exec); 488 if (dpos >= 0 && dpos < len) 489 return jsSingleCharacterSubstring(exec, s, static_cast<unsigned>(dpos)); 490 return jsEmptyString(exec); 491 } 492 493 JSValue JSC_HOST_CALL stringProtoFuncCharCodeAt(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 494 { 495 UString s = thisValue.toThisString(exec); 496 unsigned len = s.size(); 497 JSValue a0 = args.at(0); 498 if (a0.isUInt32()) { 499 uint32_t i = a0.asUInt32(); 500 if (i < len) 501 return jsNumber(exec, s.data()[i]); 502 return jsNaN(exec); 503 } 504 double dpos = a0.toInteger(exec); 505 if (dpos >= 0 && dpos < len) 506 return jsNumber(exec, s[static_cast<int>(dpos)]); 507 return jsNaN(exec); 508 } 509 510 JSValue JSC_HOST_CALL stringProtoFuncConcat(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 511 { 512 if (thisValue.isString() && (args.size() == 1)) { 513 JSValue v = args.at(0); 514 return v.isString() 515 ? jsString(exec, asString(thisValue), asString(v)) 516 : jsString(exec, asString(thisValue), v.toString(exec)); 517 } 518 519 return jsString(exec, thisValue, args); 520 } 521 522 JSValue JSC_HOST_CALL stringProtoFuncIndexOf(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 523 { 524 UString s = thisValue.toThisString(exec); 525 int len = s.size(); 526 527 JSValue a0 = args.at(0); 528 JSValue a1 = args.at(1); 529 UString u2 = a0.toString(exec); 530 int pos; 531 if (a1.isUndefined()) 532 pos = 0; 533 else if (a1.isUInt32()) 534 pos = min<uint32_t>(a1.asUInt32(), len); 535 else { 536 double dpos = a1.toInteger(exec); 537 if (dpos < 0) 538 dpos = 0; 539 else if (dpos > len) 540 dpos = len; 541 pos = static_cast<int>(dpos); 542 } 543 544 return jsNumber(exec, s.find(u2, pos)); 545 } 546 547 JSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 548 { 549 UString s = thisValue.toThisString(exec); 550 int len = s.size(); 551 552 JSValue a0 = args.at(0); 553 JSValue a1 = args.at(1); 554 555 UString u2 = a0.toString(exec); 556 double dpos = a1.toIntegerPreserveNaN(exec); 557 if (dpos < 0) 558 dpos = 0; 559 else if (!(dpos <= len)) // true for NaN 560 dpos = len; 561 #if OS(SYMBIAN) 562 // Work around for broken NaN compare operator 563 else if (isnan(dpos)) 564 dpos = len; 565 #endif 566 return jsNumber(exec, s.rfind(u2, static_cast<int>(dpos))); 567 } 568 569 JSValue JSC_HOST_CALL stringProtoFuncMatch(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 570 { 571 UString s = thisValue.toThisString(exec); 572 573 JSValue a0 = args.at(0); 574 575 UString u = s; 576 RefPtr<RegExp> reg; 577 RegExpObject* imp = 0; 578 if (a0.inherits(&RegExpObject::info)) 579 reg = asRegExpObject(a0)->regExp(); 580 else { 581 /* 582 * ECMA 15.5.4.12 String.prototype.search (regexp) 583 * If regexp is not an object whose [[Class]] property is "RegExp", it is 584 * replaced with the result of the expression new RegExp(regexp). 585 */ 586 reg = RegExp::create(&exec->globalData(), a0.toString(exec)); 587 } 588 RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor(); 589 int pos; 590 int matchLength = 0; 591 regExpConstructor->performMatch(reg.get(), u, 0, pos, matchLength); 592 if (!(reg->global())) { 593 // case without 'g' flag is handled like RegExp.prototype.exec 594 if (pos < 0) 595 return jsNull(); 596 return regExpConstructor->arrayOfMatches(exec); 597 } 598 599 // return array of matches 600 MarkedArgumentBuffer list; 601 int lastIndex = 0; 602 while (pos >= 0) { 603 list.append(jsSubstring(exec, u, pos, matchLength)); 604 lastIndex = pos; 605 pos += matchLength == 0 ? 1 : matchLength; 606 regExpConstructor->performMatch(reg.get(), u, pos, pos, matchLength); 607 } 608 if (imp) 609 imp->setLastIndex(lastIndex); 610 if (list.isEmpty()) { 611 // if there are no matches at all, it's important to return 612 // Null instead of an empty array, because this matches 613 // other browsers and because Null is a false value. 614 return jsNull(); 615 } 616 617 return constructArray(exec, list); 618 } 619 620 JSValue JSC_HOST_CALL stringProtoFuncSearch(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 621 { 622 UString s = thisValue.toThisString(exec); 623 624 JSValue a0 = args.at(0); 625 626 UString u = s; 627 RefPtr<RegExp> reg; 628 if (a0.inherits(&RegExpObject::info)) 629 reg = asRegExpObject(a0)->regExp(); 630 else { 631 /* 632 * ECMA 15.5.4.12 String.prototype.search (regexp) 633 * If regexp is not an object whose [[Class]] property is "RegExp", it is 634 * replaced with the result of the expression new RegExp(regexp). 635 */ 636 reg = RegExp::create(&exec->globalData(), a0.toString(exec)); 637 } 638 RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor(); 639 int pos; 640 int matchLength = 0; 641 regExpConstructor->performMatch(reg.get(), u, 0, pos, matchLength); 642 return jsNumber(exec, pos); 643 } 644 645 JSValue JSC_HOST_CALL stringProtoFuncSlice(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 646 { 647 UString s = thisValue.toThisString(exec); 648 int len = s.size(); 649 650 JSValue a0 = args.at(0); 651 JSValue a1 = args.at(1); 652 653 // The arg processing is very much like ArrayProtoFunc::Slice 654 double start = a0.toInteger(exec); 655 double end = a1.isUndefined() ? len : a1.toInteger(exec); 656 double from = start < 0 ? len + start : start; 657 double to = end < 0 ? len + end : end; 658 if (to > from && to > 0 && from < len) { 659 if (from < 0) 660 from = 0; 661 if (to > len) 662 to = len; 663 return jsSubstring(exec, s, static_cast<unsigned>(from), static_cast<unsigned>(to) - static_cast<unsigned>(from)); 664 } 665 666 return jsEmptyString(exec); 667 } 668 669 JSValue JSC_HOST_CALL stringProtoFuncSplit(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 670 { 671 UString s = thisValue.toThisString(exec); 672 673 JSValue a0 = args.at(0); 674 JSValue a1 = args.at(1); 675 676 JSArray* result = constructEmptyArray(exec); 677 unsigned i = 0; 678 int p0 = 0; 679 unsigned limit = a1.isUndefined() ? 0xFFFFFFFFU : a1.toUInt32(exec); 680 if (a0.inherits(&RegExpObject::info)) { 681 RegExp* reg = asRegExpObject(a0)->regExp(); 682 if (s.isEmpty() && reg->match(s, 0) >= 0) { 683 // empty string matched by regexp -> empty array 684 return result; 685 } 686 int pos = 0; 687 while (i != limit && pos < s.size()) { 688 Vector<int, 32> ovector; 689 int mpos = reg->match(s, pos, &ovector); 690 if (mpos < 0) 691 break; 692 int mlen = ovector[1] - ovector[0]; 693 pos = mpos + (mlen == 0 ? 1 : mlen); 694 if (mpos != p0 || mlen) { 695 result->put(exec, i++, jsSubstring(exec, s, p0, mpos - p0)); 696 p0 = mpos + mlen; 697 } 698 for (unsigned si = 1; si <= reg->numSubpatterns(); ++si) { 699 int spos = ovector[si * 2]; 700 if (spos < 0) 701 result->put(exec, i++, jsUndefined()); 702 else 703 result->put(exec, i++, jsSubstring(exec, s, spos, ovector[si * 2 + 1] - spos)); 704 } 705 } 706 } else { 707 UString u2 = a0.toString(exec); 708 if (u2.isEmpty()) { 709 if (s.isEmpty()) { 710 // empty separator matches empty string -> empty array 711 return result; 712 } 713 while (i != limit && p0 < s.size() - 1) 714 result->put(exec, i++, jsSingleCharacterSubstring(exec, s, p0++)); 715 } else { 716 int pos; 717 while (i != limit && (pos = s.find(u2, p0)) >= 0) { 718 result->put(exec, i++, jsSubstring(exec, s, p0, pos - p0)); 719 p0 = pos + u2.size(); 720 } 721 } 722 } 723 724 // add remaining string 725 if (i != limit) 726 result->put(exec, i++, jsSubstring(exec, s, p0, s.size() - p0)); 727 728 return result; 729 } 730 731 JSValue JSC_HOST_CALL stringProtoFuncSubstr(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 732 { 733 UString s = thisValue.toThisString(exec); 734 int len = s.size(); 735 736 JSValue a0 = args.at(0); 737 JSValue a1 = args.at(1); 738 739 double start = a0.toInteger(exec); 740 double length = a1.isUndefined() ? len : a1.toInteger(exec); 741 if (start >= len || length <= 0) 742 return jsEmptyString(exec); 743 if (start < 0) { 744 start += len; 745 if (start < 0) 746 start = 0; 747 } 748 if (start + length > len) 749 length = len - start; 750 return jsSubstring(exec, s, static_cast<unsigned>(start), static_cast<unsigned>(length)); 751 } 752 753 JSValue JSC_HOST_CALL stringProtoFuncSubstring(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 754 { 755 UString s = thisValue.toThisString(exec); 756 int len = s.size(); 757 758 JSValue a0 = args.at(0); 759 JSValue a1 = args.at(1); 760 761 double start = a0.toNumber(exec); 762 double end = a1.toNumber(exec); 763 if (isnan(start)) 764 start = 0; 765 if (isnan(end)) 766 end = 0; 767 if (start < 0) 768 start = 0; 769 if (end < 0) 770 end = 0; 771 if (start > len) 772 start = len; 773 if (end > len) 774 end = len; 775 if (a1.isUndefined()) 776 end = len; 777 if (start > end) { 778 double temp = end; 779 end = start; 780 start = temp; 781 } 782 return jsSubstring(exec, s, static_cast<unsigned>(start), static_cast<unsigned>(end) - static_cast<unsigned>(start)); 783 } 784 785 JSValue JSC_HOST_CALL stringProtoFuncToLowerCase(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 786 { 787 JSString* sVal = thisValue.toThisJSString(exec); 788 const UString& s = sVal->value(exec); 789 790 int sSize = s.size(); 791 if (!sSize) 792 return sVal; 793 794 const UChar* sData = s.data(); 795 Vector<UChar> buffer(sSize); 796 797 UChar ored = 0; 798 for (int i = 0; i < sSize; i++) { 799 UChar c = sData[i]; 800 ored |= c; 801 buffer[i] = toASCIILower(c); 802 } 803 if (!(ored & ~0x7f)) 804 return jsString(exec, UString::adopt(buffer)); 805 806 bool error; 807 int length = Unicode::toLower(buffer.data(), sSize, sData, sSize, &error); 808 if (error) { 809 buffer.resize(length); 810 length = Unicode::toLower(buffer.data(), length, sData, sSize, &error); 811 if (error) 812 return sVal; 813 } 814 if (length == sSize) { 815 if (memcmp(buffer.data(), sData, length * sizeof(UChar)) == 0) 816 return sVal; 817 } else 818 buffer.resize(length); 819 return jsString(exec, UString::adopt(buffer)); 820 } 821 822 JSValue JSC_HOST_CALL stringProtoFuncToUpperCase(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 823 { 824 JSString* sVal = thisValue.toThisJSString(exec); 825 const UString& s = sVal->value(exec); 826 827 int sSize = s.size(); 828 if (!sSize) 829 return sVal; 830 831 const UChar* sData = s.data(); 832 Vector<UChar> buffer(sSize); 833 834 UChar ored = 0; 835 for (int i = 0; i < sSize; i++) { 836 UChar c = sData[i]; 837 ored |= c; 838 buffer[i] = toASCIIUpper(c); 839 } 840 if (!(ored & ~0x7f)) 841 return jsString(exec, UString::adopt(buffer)); 842 843 bool error; 844 int length = Unicode::toUpper(buffer.data(), sSize, sData, sSize, &error); 845 if (error) { 846 buffer.resize(length); 847 length = Unicode::toUpper(buffer.data(), length, sData, sSize, &error); 848 if (error) 849 return sVal; 850 } 851 if (length == sSize) { 852 if (memcmp(buffer.data(), sData, length * sizeof(UChar)) == 0) 853 return sVal; 854 } else 855 buffer.resize(length); 856 return jsString(exec, UString::adopt(buffer)); 857 } 858 859 JSValue JSC_HOST_CALL stringProtoFuncLocaleCompare(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 860 { 861 if (args.size() < 1) 862 return jsNumber(exec, 0); 863 864 UString s = thisValue.toThisString(exec); 865 JSValue a0 = args.at(0); 866 return jsNumber(exec, localeCompare(s, a0.toString(exec))); 867 } 868 869 JSValue JSC_HOST_CALL stringProtoFuncBig(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 870 { 871 UString s = thisValue.toThisString(exec); 872 return jsMakeNontrivialString(exec, "<big>", s, "</big>"); 873 } 874 875 JSValue JSC_HOST_CALL stringProtoFuncSmall(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 876 { 877 UString s = thisValue.toThisString(exec); 878 return jsMakeNontrivialString(exec, "<small>", s, "</small>"); 879 } 880 881 JSValue JSC_HOST_CALL stringProtoFuncBlink(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 882 { 883 UString s = thisValue.toThisString(exec); 884 return jsMakeNontrivialString(exec, "<blink>", s, "</blink>"); 885 } 886 887 JSValue JSC_HOST_CALL stringProtoFuncBold(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 888 { 889 UString s = thisValue.toThisString(exec); 890 return jsMakeNontrivialString(exec, "<b>", s, "</b>"); 891 } 892 893 JSValue JSC_HOST_CALL stringProtoFuncFixed(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 894 { 895 UString s = thisValue.toThisString(exec); 896 return jsMakeNontrivialString(exec, "<tt>", s, "</tt>"); 897 } 898 899 JSValue JSC_HOST_CALL stringProtoFuncItalics(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 900 { 901 UString s = thisValue.toThisString(exec); 902 return jsMakeNontrivialString(exec, "<i>", s, "</i>"); 903 } 904 905 JSValue JSC_HOST_CALL stringProtoFuncStrike(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 906 { 907 UString s = thisValue.toThisString(exec); 908 return jsMakeNontrivialString(exec, "<strike>", s, "</strike>"); 909 } 910 911 JSValue JSC_HOST_CALL stringProtoFuncSub(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 912 { 913 UString s = thisValue.toThisString(exec); 914 return jsMakeNontrivialString(exec, "<sub>", s, "</sub>"); 915 } 916 917 JSValue JSC_HOST_CALL stringProtoFuncSup(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 918 { 919 UString s = thisValue.toThisString(exec); 920 return jsMakeNontrivialString(exec, "<sup>", s, "</sup>"); 921 } 922 923 JSValue JSC_HOST_CALL stringProtoFuncFontcolor(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 924 { 925 UString s = thisValue.toThisString(exec); 926 JSValue a0 = args.at(0); 927 return jsMakeNontrivialString(exec, "<font color=\"", a0.toString(exec), "\">", s, "</font>"); 928 } 929 930 JSValue JSC_HOST_CALL stringProtoFuncFontsize(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 931 { 932 UString s = thisValue.toThisString(exec); 933 JSValue a0 = args.at(0); 934 935 uint32_t smallInteger; 936 if (a0.getUInt32(smallInteger) && smallInteger <= 9) { 937 unsigned stringSize = s.size(); 938 unsigned bufferSize = 22 + stringSize; 939 UChar* buffer; 940 PassRefPtr<UStringImpl> impl = UStringImpl::tryCreateUninitialized(bufferSize, buffer); 941 if (!impl) 942 return jsUndefined(); 943 buffer[0] = '<'; 944 buffer[1] = 'f'; 945 buffer[2] = 'o'; 946 buffer[3] = 'n'; 947 buffer[4] = 't'; 948 buffer[5] = ' '; 949 buffer[6] = 's'; 950 buffer[7] = 'i'; 951 buffer[8] = 'z'; 952 buffer[9] = 'e'; 953 buffer[10] = '='; 954 buffer[11] = '"'; 955 buffer[12] = '0' + smallInteger; 956 buffer[13] = '"'; 957 buffer[14] = '>'; 958 memcpy(&buffer[15], s.data(), stringSize * sizeof(UChar)); 959 buffer[15 + stringSize] = '<'; 960 buffer[16 + stringSize] = '/'; 961 buffer[17 + stringSize] = 'f'; 962 buffer[18 + stringSize] = 'o'; 963 buffer[19 + stringSize] = 'n'; 964 buffer[20 + stringSize] = 't'; 965 buffer[21 + stringSize] = '>'; 966 return jsNontrivialString(exec, impl); 967 } 968 969 return jsMakeNontrivialString(exec, "<font size=\"", a0.toString(exec), "\">", s, "</font>"); 970 } 971 972 JSValue JSC_HOST_CALL stringProtoFuncAnchor(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 973 { 974 UString s = thisValue.toThisString(exec); 975 JSValue a0 = args.at(0); 976 return jsMakeNontrivialString(exec, "<a name=\"", a0.toString(exec), "\">", s, "</a>"); 977 } 978 979 JSValue JSC_HOST_CALL stringProtoFuncLink(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 980 { 981 UString s = thisValue.toThisString(exec); 982 JSValue a0 = args.at(0); 983 UString linkText = a0.toString(exec); 984 985 unsigned linkTextSize = linkText.size(); 986 unsigned stringSize = s.size(); 987 unsigned bufferSize = 15 + linkTextSize + stringSize; 988 UChar* buffer; 989 PassRefPtr<UStringImpl> impl = UStringImpl::tryCreateUninitialized(bufferSize, buffer); 990 if (!impl) 991 return jsUndefined(); 992 buffer[0] = '<'; 993 buffer[1] = 'a'; 994 buffer[2] = ' '; 995 buffer[3] = 'h'; 996 buffer[4] = 'r'; 997 buffer[5] = 'e'; 998 buffer[6] = 'f'; 999 buffer[7] = '='; 1000 buffer[8] = '"'; 1001 memcpy(&buffer[9], linkText.data(), linkTextSize * sizeof(UChar)); 1002 buffer[9 + linkTextSize] = '"'; 1003 buffer[10 + linkTextSize] = '>'; 1004 memcpy(&buffer[11 + linkTextSize], s.data(), stringSize * sizeof(UChar)); 1005 buffer[11 + linkTextSize + stringSize] = '<'; 1006 buffer[12 + linkTextSize + stringSize] = '/'; 1007 buffer[13 + linkTextSize + stringSize] = 'a'; 1008 buffer[14 + linkTextSize + stringSize] = '>'; 1009 return jsNontrivialString(exec, impl); 1010 } 1011 1012 enum { 1013 TrimLeft = 1, 1014 TrimRight = 2 1015 }; 1016 1017 static inline bool isTrimWhitespace(UChar c) 1018 { 1019 return isStrWhiteSpace(c) || c == 0x200b; 1020 } 1021 1022 static inline JSValue trimString(ExecState* exec, JSValue thisValue, int trimKind) 1023 { 1024 UString str = thisValue.toThisString(exec); 1025 int left = 0; 1026 if (trimKind & TrimLeft) { 1027 while (left < str.size() && isTrimWhitespace(str[left])) 1028 left++; 1029 } 1030 int right = str.size(); 1031 if (trimKind & TrimRight) { 1032 while (right > left && isTrimWhitespace(str[right - 1])) 1033 right--; 1034 } 1035 1036 // Don't gc allocate a new string if we don't have to. 1037 if (left == 0 && right == str.size() && thisValue.isString()) 1038 return thisValue; 1039 1040 return jsString(exec, str.substr(left, right - left)); 1041 } 1042 1043 JSValue JSC_HOST_CALL stringProtoFuncTrim(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 1044 { 1045 return trimString(exec, thisValue, TrimLeft | TrimRight); 1046 } 1047 1048 JSValue JSC_HOST_CALL stringProtoFuncTrimLeft(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 1049 { 1050 return trimString(exec, thisValue, TrimLeft); 1051 } 1052 1053 JSValue JSC_HOST_CALL stringProtoFuncTrimRight(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 1054 { 1055 return trimString(exec, thisValue, TrimRight); 1056 } 1057 1058 1059 } // namespace JSC 1060