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