JavaScriptCore:
[WebKit-https.git] / JavaScriptCore / kjs / string_object.cpp
1 // -*- c-basic-offset: 2 -*-
2 /*
3  *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
4  *  Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
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 "string_object.h"
24 #include "string_object.lut.h"
25
26 #include "JSWrapperObject.h"
27 #include "error_object.h"
28 #include "operations.h"
29 #include "PropertyNameArray.h"
30 #include "regexp_object.h"
31 #include <wtf/MathExtras.h>
32 #include <wtf/unicode/Unicode.h>
33
34 #if PLATFORM(CF)
35 #include <CoreFoundation/CoreFoundation.h>
36 #elif PLATFORM(WIN_OS)
37 #include <windows.h>
38 #endif
39
40 using namespace WTF;
41
42 namespace KJS {
43
44 // ------------------------------ StringInstance ----------------------------
45
46 const ClassInfo StringInstance::info = { "String", 0, 0 };
47
48 StringInstance::StringInstance(JSObject *proto)
49   : JSWrapperObject(proto)
50 {
51   setInternalValue(jsString(""));
52 }
53
54 StringInstance::StringInstance(JSObject *proto, StringImp* string)
55   : JSWrapperObject(proto)
56 {
57   setInternalValue(string);
58 }
59
60 StringInstance::StringInstance(JSObject *proto, const UString &string)
61   : JSWrapperObject(proto)
62 {
63   setInternalValue(jsString(string));
64 }
65
66 JSValue *StringInstance::lengthGetter(ExecState*, JSObject*, const Identifier&, const PropertySlot &slot)
67 {
68     return jsNumber(static_cast<StringInstance*>(slot.slotBase())->internalValue()->value().size());
69 }
70
71 JSValue *StringInstance::indexGetter(ExecState*, JSObject*, const Identifier&, const PropertySlot &slot)
72 {
73     const UChar c = static_cast<StringInstance*>(slot.slotBase())->internalValue()->value()[slot.index()];
74     return jsString(UString(&c, 1));
75 }
76
77 bool StringInstance::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
78 {
79     if (propertyName == exec->propertyNames().length) {
80         slot.setCustom(this, lengthGetter);
81         return true;
82     }
83
84     bool isStrictUInt32;
85     unsigned i = propertyName.toStrictUInt32(&isStrictUInt32);
86     unsigned length = internalValue()->value().size();
87     if (isStrictUInt32 && i < length) {
88         slot.setCustomIndex(this, i, indexGetter);
89         return true;
90     }
91     
92     return JSObject::getOwnPropertySlot(exec, propertyName, slot);
93 }
94     
95 bool StringInstance::getOwnPropertySlot(ExecState* exec, unsigned propertyName, PropertySlot& slot)
96 {
97     unsigned length = internalValue()->value().size();
98     if (propertyName < length) {
99         slot.setCustomIndex(this, propertyName, indexGetter);
100         return true;
101     }
102     
103     return JSObject::getOwnPropertySlot(exec, Identifier::from(propertyName), slot);
104 }
105
106 void StringInstance::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
107 {
108   if (propertyName == exec->propertyNames().length)
109     return;
110   JSObject::put(exec, propertyName, value, attr);
111 }
112
113 bool StringInstance::deleteProperty(ExecState *exec, const Identifier &propertyName)
114 {
115   if (propertyName == exec->propertyNames().length)
116     return false;
117   return JSObject::deleteProperty(exec, propertyName);
118 }
119
120 void StringInstance::getPropertyNames(ExecState* exec, PropertyNameArray& propertyNames)
121 {
122   int size = internalValue()->getString().size();
123   for (int i = 0; i < size; i++)
124     propertyNames.add(Identifier(UString::from(i)));
125   return JSObject::getPropertyNames(exec, propertyNames);
126 }
127
128 // ------------------------------ StringPrototype ---------------------------
129 const ClassInfo StringPrototype::info = { "String", &StringInstance::info, &stringTable };
130 /* Source for string_object.lut.h
131 @begin stringTable 26
132   toString              StringProtoFunc::ToString       DontEnum|Function       0
133   valueOf               StringProtoFunc::ValueOf        DontEnum|Function       0
134   charAt                StringProtoFunc::CharAt         DontEnum|Function       1
135   charCodeAt            StringProtoFunc::CharCodeAt     DontEnum|Function       1
136   concat                StringProtoFunc::Concat         DontEnum|Function       1
137   indexOf               StringProtoFunc::IndexOf        DontEnum|Function       1
138   lastIndexOf           StringProtoFunc::LastIndexOf    DontEnum|Function       1
139   match                 StringProtoFunc::Match          DontEnum|Function       1
140   replace               StringProtoFunc::Replace        DontEnum|Function       2
141   search                StringProtoFunc::Search         DontEnum|Function       1
142   slice                 StringProtoFunc::Slice          DontEnum|Function       2
143   split                 StringProtoFunc::Split          DontEnum|Function       2
144   substr                StringProtoFunc::Substr         DontEnum|Function       2
145   substring             StringProtoFunc::Substring      DontEnum|Function       2
146   toLowerCase           StringProtoFunc::ToLowerCase    DontEnum|Function       0
147   toUpperCase           StringProtoFunc::ToUpperCase    DontEnum|Function       0
148   toLocaleLowerCase     StringProtoFunc::ToLocaleLowerCase DontEnum|Function    0
149   toLocaleUpperCase     StringProtoFunc::ToLocaleUpperCase DontEnum|Function    0
150   localeCompare         StringProtoFunc::LocaleCompare  DontEnum|Function       1
151 #
152 # Under here: html extension, should only exist if KJS_PURE_ECMA is not defined
153 # I guess we need to generate two hashtables in the .lut.h file, and use #ifdef
154 # to select the right one... TODO. #####
155   big                   StringProtoFunc::Big            DontEnum|Function       0
156   small                 StringProtoFunc::Small          DontEnum|Function       0
157   blink                 StringProtoFunc::Blink          DontEnum|Function       0
158   bold                  StringProtoFunc::Bold           DontEnum|Function       0
159   fixed                 StringProtoFunc::Fixed          DontEnum|Function       0
160   italics               StringProtoFunc::Italics        DontEnum|Function       0
161   strike                StringProtoFunc::Strike         DontEnum|Function       0
162   sub                   StringProtoFunc::Sub            DontEnum|Function       0
163   sup                   StringProtoFunc::Sup            DontEnum|Function       0
164   fontcolor             StringProtoFunc::Fontcolor      DontEnum|Function       1
165   fontsize              StringProtoFunc::Fontsize       DontEnum|Function       1
166   anchor                StringProtoFunc::Anchor         DontEnum|Function       1
167   link                  StringProtoFunc::Link           DontEnum|Function       1
168 @end
169 */
170 // ECMA 15.5.4
171 StringPrototype::StringPrototype(ExecState* exec, ObjectPrototype* objProto)
172   : StringInstance(objProto)
173 {
174   // The constructor will be added later, after StringObjectImp has been built
175   putDirect(exec->propertyNames().length, jsNumber(0), DontDelete | ReadOnly | DontEnum);
176 }
177
178 bool StringPrototype::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot &slot)
179 {
180   return getStaticFunctionSlot<StringProtoFunc, StringInstance>(exec, &stringTable, this, propertyName, slot);
181 }
182
183 // ------------------------------ StringProtoFunc ---------------------------
184
185 StringProtoFunc::StringProtoFunc(ExecState* exec, int i, int len, const Identifier& name)
186   : InternalFunctionImp(static_cast<FunctionPrototype*>(exec->lexicalInterpreter()->builtinFunctionPrototype()), name)
187   , id(i)
188 {
189   putDirect(exec->propertyNames().length, len, DontDelete | ReadOnly | DontEnum);
190 }
191
192 static inline void expandSourceRanges(UString::Range * & array, int& count, int& capacity)
193 {
194   int newCapacity;
195   if (capacity == 0) {
196     newCapacity = 16;
197   } else {
198     newCapacity = capacity * 2;
199   }
200
201   UString::Range *newArray = new UString::Range[newCapacity];
202   for (int i = 0; i < count; i++) {
203     newArray[i] = array[i];
204   }
205
206   delete [] array;
207
208   capacity = newCapacity;
209   array = newArray;
210 }
211
212 static void pushSourceRange(UString::Range * & array, int& count, int& capacity, UString::Range range)
213 {
214   if (count + 1 > capacity)
215     expandSourceRanges(array, count, capacity);
216
217   array[count] = range;
218   count++;
219 }
220
221 static inline void expandReplacements(UString * & array, int& count, int& capacity)
222 {
223   int newCapacity;
224   if (capacity == 0) {
225     newCapacity = 16;
226   } else {
227     newCapacity = capacity * 2;
228   }
229
230   UString *newArray = new UString[newCapacity];
231   for (int i = 0; i < count; i++) {
232     newArray[i] = array[i];
233   }
234   
235   delete [] array;
236
237   capacity = newCapacity;
238   array = newArray;
239 }
240
241 static void pushReplacement(UString * & array, int& count, int& capacity, UString replacement)
242 {
243   if (count + 1 > capacity)
244     expandReplacements(array, count, capacity);
245
246   array[count] = replacement;
247   count++;
248 }
249
250 static inline UString substituteBackreferences(const UString &replacement, const UString &source, int *ovector, RegExp *reg)
251 {
252   UString substitutedReplacement = replacement;
253
254   int i = -1;
255   while ((i = substitutedReplacement.find(UString("$"), i + 1)) != -1) {
256     if (i+1 == substitutedReplacement.size())
257         break;
258
259     unsigned short ref = substitutedReplacement[i+1].unicode();
260     int backrefStart = 0;
261     int backrefLength = 0;
262     int advance = 0;
263
264     if (ref == '$') {  // "$$" -> "$"
265         substitutedReplacement = substitutedReplacement.substr(0, i + 1) + substitutedReplacement.substr(i + 2);
266         continue;
267     } else if (ref == '&') {
268         backrefStart = ovector[0];
269         backrefLength = ovector[1] - backrefStart;
270     } else if (ref == '`') {
271         backrefStart = 0;
272         backrefLength = ovector[0];
273     } else if (ref == '\'') {
274         backrefStart = ovector[1];
275         backrefLength = source.size() - backrefStart;
276     } else if (ref >= '0' && ref <= '9') {
277         // 1- and 2-digit back references are allowed
278         unsigned backrefIndex = ref - '0';
279         if (backrefIndex > reg->numSubpatterns())
280             continue;
281         if (substitutedReplacement.size() > i + 2) {
282             ref = substitutedReplacement[i+2].unicode();
283             if (ref >= '0' && ref <= '9') {
284                 backrefIndex = 10 * backrefIndex + ref - '0';
285                 if (backrefIndex > reg->numSubpatterns())
286                     backrefIndex = backrefIndex / 10;   // Fall back to the 1-digit reference
287                 else
288                     advance = 1;
289             }
290         }
291         backrefStart = ovector[2 * backrefIndex];
292         backrefLength = ovector[2 * backrefIndex + 1] - backrefStart;
293     } else
294         continue;
295
296     substitutedReplacement = substitutedReplacement.substr(0, i) + source.substr(backrefStart, backrefLength) + substitutedReplacement.substr(i + 2 + advance);
297     i += backrefLength - 1; // - 1 offsets 'i + 1'
298   }
299
300   return substitutedReplacement;
301 }
302 static inline int localeCompare(const UString& a, const UString& b)
303 {
304 #if PLATFORM(WIN_OS)
305     int retval = CompareStringW(LOCALE_USER_DEFAULT, 0,
306                                 reinterpret_cast<LPCWSTR>(a.data()), a.size(),
307                                 reinterpret_cast<LPCWSTR>(b.data()), b.size());
308     return !retval ? retval : retval - 2;
309 #elif PLATFORM(CF)
310     CFStringRef sa = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar*>(a.data()), a.size(), kCFAllocatorNull);
311     CFStringRef sb = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar*>(b.data()), b.size(), kCFAllocatorNull);
312
313     int retval = CFStringCompare(sa, sb, kCFCompareLocalized);
314
315     CFRelease(sa);
316     CFRelease(sb);
317
318     return retval;
319 #else
320     return compare(a, b);
321 #endif
322 }
323
324 static JSValue *replace(ExecState *exec, StringImp* sourceVal, JSValue *pattern, JSValue *replacement)
325 {
326   UString source = sourceVal->value();
327   JSObject *replacementFunction = 0;
328   UString replacementString;
329
330   if (replacement->isObject() && replacement->toObject(exec)->implementsCall())
331     replacementFunction = replacement->toObject(exec);
332   else
333     replacementString = replacement->toString(exec);
334
335   if (pattern->isObject() && static_cast<JSObject *>(pattern)->inherits(&RegExpImp::info)) {
336     RegExp *reg = static_cast<RegExpImp *>(pattern)->regExp();
337     bool global = reg->global();
338
339     RegExpObjectImp* regExpObj = static_cast<RegExpObjectImp*>(exec->lexicalInterpreter()->builtinRegExp());
340
341     int lastIndex = 0;
342     int startPosition = 0;
343
344     UString::Range *sourceRanges = 0;
345     int sourceRangeCount = 0;
346     int sourceRangeCapacity = 0;
347     UString *replacements = 0;
348     int replacementCount = 0;
349     int replacementCapacity = 0;
350
351     // This is either a loop (if global is set) or a one-way (if not).
352     do {
353       int matchIndex;
354       int matchLen;
355       int* ovector;
356       regExpObj->performMatch(reg, source, startPosition, matchIndex, matchLen, &ovector);
357       if (matchIndex < 0)
358         break;
359
360       pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, matchIndex - lastIndex));
361
362       UString substitutedReplacement;
363       if (replacementFunction) {
364           int completeMatchStart = ovector[0];
365           List args;
366
367           for (unsigned i = 0; i < reg->numSubpatterns() + 1; i++) {
368               int matchStart = ovector[i * 2];
369               int matchLen = ovector[i * 2 + 1] - matchStart;
370
371               if (matchStart < 0)
372                 args.append(jsUndefined());
373               else
374                 args.append(jsString(source.substr(matchStart, matchLen)));
375           }
376           
377           args.append(jsNumber(completeMatchStart));
378           args.append(sourceVal);
379
380           substitutedReplacement = replacementFunction->call(exec, exec->dynamicInterpreter()->globalObject(), 
381                                                              args)->toString(exec);
382       } else
383           substitutedReplacement = substituteBackreferences(replacementString, source, ovector, reg);
384
385       pushReplacement(replacements, replacementCount, replacementCapacity, substitutedReplacement);
386
387       lastIndex = matchIndex + matchLen;
388       startPosition = lastIndex;
389
390       // special case of empty match
391       if (matchLen == 0) {
392         startPosition++;
393         if (startPosition > source.size())
394           break;
395       }
396     } while (global);
397
398     if (lastIndex < source.size())
399       pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, source.size() - lastIndex));
400
401     UString result;
402
403     if (sourceRanges)
404         result = source.spliceSubstringsWithSeparators(sourceRanges, sourceRangeCount, replacements, replacementCount);
405
406     delete [] sourceRanges;
407     delete [] replacements;
408
409     if (result == source)
410       return sourceVal;
411
412     return jsString(result);
413   }
414   
415   // First arg is a string
416   UString patternString = pattern->toString(exec);
417   int matchPos = source.find(patternString);
418   int matchLen = patternString.size();
419   // Do the replacement
420   if (matchPos == -1)
421     return sourceVal;
422   
423   if (replacementFunction) {
424       List args;
425       
426       args.append(jsString(source.substr(matchPos, matchLen)));
427       args.append(jsNumber(matchPos));
428       args.append(sourceVal);
429       
430       replacementString = replacementFunction->call(exec, exec->dynamicInterpreter()->globalObject(), 
431                                                     args)->toString(exec);
432   }
433
434   return jsString(source.substr(0, matchPos) + replacementString + source.substr(matchPos + matchLen));
435 }
436
437 // ECMA 15.5.4.2 - 15.5.4.20
438 JSValue* StringProtoFunc::callAsFunction(ExecState* exec, JSObject* thisObj, const List& args)
439 {
440   JSValue* result = NULL;
441
442   // toString and valueOf are no generic function.
443   if (id == ToString || id == ValueOf) {
444     if (!thisObj->inherits(&StringInstance::info))
445       return throwError(exec, TypeError);
446
447     return static_cast<StringInstance*>(thisObj)->internalValue();
448   }
449
450   UString u, u2, u3;
451   int pos, p0, i;
452   double dpos;
453   double d = 0.0;
454
455   // This optimizes the common case that thisObj is a StringInstance
456   UString s = thisObj->inherits(&StringInstance::info) ? static_cast<StringInstance*>(thisObj)->internalValue()->value() : thisObj->toString(exec);
457
458   int len = s.size();
459   JSValue *a0 = args[0];
460   JSValue *a1 = args[1];
461
462   switch (id) {
463   case ToString:
464   case ValueOf:
465     // handled above
466     break;
467   case CharAt:
468     dpos = a0->toInteger(exec);
469     if (dpos >= 0 && dpos < len)
470       u = s.substr(static_cast<int>(dpos), 1);
471     else
472       u = "";
473     result = jsString(u);
474     break;
475   case CharCodeAt:
476     dpos = a0->toInteger(exec);
477     if (dpos >= 0 && dpos < len)
478       result = jsNumber(s[static_cast<int>(dpos)].unicode());
479     else
480       result = jsNaN();
481     break;
482   case Concat: {
483     List::const_iterator end = args.end();
484     for (List::const_iterator it = args.begin(); it != end; ++it) {
485         s += (*it)->toString(exec);
486     }
487     result = jsString(s);
488     break;
489   }
490   case IndexOf:
491     u2 = a0->toString(exec);
492     dpos = a1->toInteger(exec);
493     if (dpos < 0)
494       dpos = 0;
495     else if (dpos > len)
496       dpos = len;
497     result = jsNumber(s.find(u2, static_cast<int>(dpos)));
498     break;
499   case LastIndexOf:
500     u2 = a0->toString(exec);
501     d = a1->toNumber(exec);
502     dpos = a1->toIntegerPreserveNaN(exec);
503     if (dpos < 0)
504       dpos = 0;
505     else if (!(dpos <= len)) // true for NaN
506       dpos = len;
507     result = jsNumber(s.rfind(u2, static_cast<int>(dpos)));
508     break;
509   case Match:
510   case Search: {
511     u = s;
512     RegExp *reg, *tmpReg = 0;
513     RegExpImp *imp = 0;
514     if (a0->isObject() && static_cast<JSObject *>(a0)->inherits(&RegExpImp::info)) {
515       reg = static_cast<RegExpImp *>(a0)->regExp();
516     } else { 
517       /*
518        *  ECMA 15.5.4.12 String.prototype.search (regexp)
519        *  If regexp is not an object whose [[Class]] property is "RegExp", it is
520        *  replaced with the result of the expression new RegExp(regexp).
521        */
522       reg = tmpReg = new RegExp(a0->toString(exec));
523     }
524     RegExpObjectImp* regExpObj = static_cast<RegExpObjectImp*>(exec->lexicalInterpreter()->builtinRegExp());
525     int pos;
526     int matchLength;
527     regExpObj->performMatch(reg, u, 0, pos, matchLength);
528     if (id == Search) {
529       result = jsNumber(pos);
530     } else {
531       // Match
532       if (!(reg->global())) {
533         // case without 'g' flag is handled like RegExp.prototype.exec
534         if (pos < 0)
535           result = jsNull();
536         else
537           result = regExpObj->arrayOfMatches(exec);
538       } else {
539         // return array of matches
540         List list;
541         int lastIndex = 0;
542         while (pos >= 0) {
543           list.append(jsString(u.substr(pos, matchLength)));
544           lastIndex = pos;
545           pos += matchLength == 0 ? 1 : matchLength;
546           regExpObj->performMatch(reg, u, pos, pos, matchLength);
547         }
548         if (imp)
549           imp->put(exec, exec->propertyNames().lastIndex, jsNumber(lastIndex), DontDelete|DontEnum);
550         if (list.isEmpty()) {
551           // if there are no matches at all, it's important to return
552           // Null instead of an empty array, because this matches
553           // other browsers and because Null is a false value.
554           result = jsNull();
555         } else {
556           result = exec->lexicalInterpreter()->builtinArray()->construct(exec, list);
557         }
558       }
559     }
560     delete tmpReg;
561     break;
562   }
563   case Replace: {
564     StringImp* sVal = thisObj->inherits(&StringInstance::info) ?
565       static_cast<StringInstance*>(thisObj)->internalValue() :
566       static_cast<StringImp*>(jsString(s));
567
568     result = replace(exec, sVal, a0, a1);
569     break;
570   }
571   case Slice:
572     {
573       // The arg processing is very much like ArrayProtoFunc::Slice
574       double start = a0->toInteger(exec);
575       double end = a1->isUndefined() ? len : a1->toInteger(exec);
576       double from = start < 0 ? len + start : start;
577       double to = end < 0 ? len + end : end;
578       if (to > from && to > 0 && from < len) {
579         if (from < 0)
580           from = 0;
581         if (to > len)
582           to = len;
583         result = jsString(s.substr(static_cast<int>(from), static_cast<int>(to - from)));
584       } else {
585         result = jsString("");
586       }
587       break;
588     }
589     case Split: {
590     JSObject *constructor = exec->lexicalInterpreter()->builtinArray();
591     JSObject *res = static_cast<JSObject *>(constructor->construct(exec,List::empty()));
592     result = res;
593     u = s;
594     i = p0 = 0;
595     uint32_t limit = a1->isUndefined() ? 0xFFFFFFFFU : a1->toUInt32(exec);
596     if (a0->isObject() && static_cast<JSObject *>(a0)->inherits(&RegExpImp::info)) {
597       RegExp *reg = static_cast<RegExpImp *>(a0)->regExp();
598       if (u.isEmpty() && reg->match(u, 0) >= 0) {
599         // empty string matched by regexp -> empty array
600         res->put(exec, exec->propertyNames().length, jsNumber(0));
601         break;
602       }
603       pos = 0;
604       while (static_cast<uint32_t>(i) != limit && pos < u.size()) {
605         OwnArrayPtr<int> ovector;
606         int mpos = reg->match(u, pos, &ovector);
607         if (mpos < 0)
608           break;
609         int mlen = ovector[1] - ovector[0];
610         pos = mpos + (mlen == 0 ? 1 : mlen);
611         if (mpos != p0 || mlen) {
612           res->put(exec,i, jsString(u.substr(p0, mpos-p0)));
613           p0 = mpos + mlen;
614           i++;
615         }
616         for (unsigned si = 1; si <= reg->numSubpatterns(); ++si) {
617           int spos = ovector[si * 2];
618           if (spos < 0)
619             res->put(exec, i++, jsUndefined());
620           else
621             res->put(exec, i++, jsString(u.substr(spos, ovector[si * 2 + 1] - spos)));
622         }
623       }
624     } else {
625       u2 = a0->toString(exec);
626       if (u2.isEmpty()) {
627         if (u.isEmpty()) {
628           // empty separator matches empty string -> empty array
629           put(exec, exec->propertyNames().length, jsNumber(0));
630           break;
631         } else {
632           while (static_cast<uint32_t>(i) != limit && i < u.size()-1)
633             res->put(exec, i++, jsString(u.substr(p0++, 1)));
634         }
635       } else {
636         while (static_cast<uint32_t>(i) != limit && (pos = u.find(u2, p0)) >= 0) {
637           res->put(exec, i, jsString(u.substr(p0, pos-p0)));
638           p0 = pos + u2.size();
639           i++;
640         }
641       }
642     }
643     // add remaining string, if any
644     if (static_cast<uint32_t>(i) != limit)
645       res->put(exec, i++, jsString(u.substr(p0)));
646     res->put(exec, exec->propertyNames().length, jsNumber(i));
647     }
648     break;
649   case Substr: {
650     double start = a0->toInteger(exec);
651     double length = a1->isUndefined() ? len : a1->toInteger(exec);
652     if (start >= len)
653       return jsString("");
654     if (length < 0)
655       return jsString("");
656     if (start < 0) {
657       start += len;
658       if (start < 0)
659         start = 0;
660     }
661     if (length > len - d)
662       length = len - d;
663     result = jsString(s.substr(static_cast<int>(start), static_cast<int>(length)));
664     break;
665   }
666   case Substring: {
667     double start = a0->toNumber(exec);
668     double end = a1->toNumber(exec);
669     if (isnan(start))
670       start = 0;
671     if (isnan(end))
672       end = 0;
673     if (start < 0)
674       start = 0;
675     if (end < 0)
676       end = 0;
677     if (start > len)
678       start = len;
679     if (end > len)
680       end = len;
681     if (a1->isUndefined())
682       end = len;
683     if (start > end) {
684       double temp = end;
685       end = start;
686       start = temp;
687     }
688     result = jsString(s.substr((int)start, (int)end-(int)start));
689     }
690     break;
691   case ToLowerCase:
692   case ToLocaleLowerCase: { // FIXME: See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt for locale-sensitive mappings that aren't implemented.
693     StringImp* sVal = thisObj->inherits(&StringInstance::info)
694         ? static_cast<StringInstance*>(thisObj)->internalValue()
695         : static_cast<StringImp*>(jsString(s));
696     int ssize = s.size();
697     if (!ssize)
698         return sVal;
699     Vector< ::UChar> buffer(ssize);
700     bool error;
701     int length = Unicode::toLower(buffer.data(), ssize, reinterpret_cast<const ::UChar*>(s.data()), ssize, &error);
702     if (error) {
703         buffer.resize(length);
704         length = Unicode::toLower(buffer.data(), length, reinterpret_cast<const ::UChar*>(s.data()), ssize, &error);
705         if (error)
706             return sVal;
707     }
708     if (length == ssize && memcmp(buffer.data(), s.data(), length * sizeof(UChar)) == 0)
709         return sVal;
710     return jsString(UString(reinterpret_cast<UChar*>(buffer.releaseBuffer()), length, false));
711   }
712   case ToUpperCase:
713   case ToLocaleUpperCase: { // FIXME: See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt for locale-sensitive mappings that aren't implemented.
714     StringImp* sVal = thisObj->inherits(&StringInstance::info)
715         ? static_cast<StringInstance*>(thisObj)->internalValue()
716         : static_cast<StringImp*>(jsString(s));
717     int ssize = s.size();
718     if (!ssize)
719         return sVal;
720     Vector< ::UChar> buffer(ssize);
721     bool error;
722     int length = Unicode::toUpper(buffer.data(), ssize, reinterpret_cast<const ::UChar*>(s.data()), ssize, &error);
723     if (error) {
724         buffer.resize(length);
725         length = Unicode::toUpper(buffer.data(), length, reinterpret_cast<const ::UChar*>(s.data()), ssize, &error);
726         if (error)
727             return sVal;
728     }
729     if (length == ssize && memcmp(buffer.data(), s.data(), length * sizeof(UChar)) == 0)
730         return sVal;
731     return jsString(UString(reinterpret_cast<UChar*>(buffer.releaseBuffer()), length, false));
732   }
733   case LocaleCompare:
734     if (args.size() < 1)
735       return jsNumber(0);
736     return jsNumber(localeCompare(s, a0->toString(exec)));
737 #ifndef KJS_PURE_ECMA
738   case Big:
739     result = jsString("<big>" + s + "</big>");
740     break;
741   case Small:
742     result = jsString("<small>" + s + "</small>");
743     break;
744   case Blink:
745     result = jsString("<blink>" + s + "</blink>");
746     break;
747   case Bold:
748     result = jsString("<b>" + s + "</b>");
749     break;
750   case Fixed:
751     result = jsString("<tt>" + s + "</tt>");
752     break;
753   case Italics:
754     result = jsString("<i>" + s + "</i>");
755     break;
756   case Strike:
757     result = jsString("<strike>" + s + "</strike>");
758     break;
759   case Sub:
760     result = jsString("<sub>" + s + "</sub>");
761     break;
762   case Sup:
763     result = jsString("<sup>" + s + "</sup>");
764     break;
765   case Fontcolor:
766     result = jsString("<font color=\"" + a0->toString(exec) + "\">" + s + "</font>");
767     break;
768   case Fontsize:
769     result = jsString("<font size=\"" + a0->toString(exec) + "\">" + s + "</font>");
770     break;
771   case Anchor:
772     result = jsString("<a name=\"" + a0->toString(exec) + "\">" + s + "</a>");
773     break;
774   case Link:
775     result = jsString("<a href=\"" + a0->toString(exec) + "\">" + s + "</a>");
776     break;
777 #endif
778   }
779
780   return result;
781 }
782
783 // ------------------------------ StringObjectImp ------------------------------
784
785 StringObjectImp::StringObjectImp(ExecState* exec,
786                                  FunctionPrototype* funcProto,
787                                  StringPrototype* stringProto)
788   : InternalFunctionImp(funcProto)
789 {
790   // ECMA 15.5.3.1 String.prototype
791   putDirect(exec->propertyNames().prototype, stringProto, DontEnum|DontDelete|ReadOnly);
792
793   putDirectFunction(new StringObjectFuncImp(exec, funcProto, exec->propertyNames().fromCharCode), DontEnum);
794
795   // no. of arguments for constructor
796   putDirect(exec->propertyNames().length, jsNumber(1), ReadOnly|DontDelete|DontEnum);
797 }
798
799
800 bool StringObjectImp::implementsConstruct() const
801 {
802   return true;
803 }
804
805 // ECMA 15.5.2
806 JSObject *StringObjectImp::construct(ExecState *exec, const List &args)
807 {
808   JSObject *proto = exec->lexicalInterpreter()->builtinStringPrototype();
809   if (args.size() == 0)
810     return new StringInstance(proto);
811   return new StringInstance(proto, args[0]->toString(exec));
812 }
813
814 // ECMA 15.5.1
815 JSValue *StringObjectImp::callAsFunction(ExecState *exec, JSObject* /*thisObj*/, const List &args)
816 {
817   if (args.isEmpty())
818     return jsString("");
819   else {
820     JSValue *v = args[0];
821     return jsString(v->toString(exec));
822   }
823 }
824
825 // ------------------------------ StringObjectFuncImp --------------------------
826
827 // ECMA 15.5.3.2 fromCharCode()
828 StringObjectFuncImp::StringObjectFuncImp(ExecState* exec, FunctionPrototype* funcProto, const Identifier& name)
829   : InternalFunctionImp(funcProto, name)
830 {
831   putDirect(exec->propertyNames().length, jsNumber(1), DontDelete|ReadOnly|DontEnum);
832 }
833
834 JSValue *StringObjectFuncImp::callAsFunction(ExecState *exec, JSObject* /*thisObj*/, const List &args)
835 {
836   UString s;
837   if (args.size()) {
838     UChar *buf = static_cast<UChar *>(fastMalloc(args.size() * sizeof(UChar)));
839     UChar *p = buf;
840     List::const_iterator end = args.end();
841     for (List::const_iterator it = args.begin(); it != end; ++it) {
842       unsigned short u = static_cast<unsigned short>((*it)->toUInt32(exec));
843       *p++ = UChar(u);
844     }
845     s = UString(buf, args.size(), false);
846   } else
847     s = "";
848
849   return jsString(s);
850 }
851
852 }