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