JavaScriptCore:
[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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  */
22
23 #include "value.h"
24 #include "object.h"
25 #include "types.h"
26 #include "interpreter.h"
27 #include "operations.h"
28 #include "regexp.h"
29 #include "regexp_object.h"
30 #include "string_object.h"
31 #include "error_object.h"
32 #include <stdio.h>
33 #include "string_object.lut.h"
34
35 using namespace KJS;
36
37 // ------------------------------ StringInstanceImp ----------------------------
38
39 const ClassInfo StringInstanceImp::info = {"String", 0, 0, 0};
40
41 StringInstanceImp::StringInstanceImp(ObjectImp *proto)
42   : ObjectImp(proto)
43 {
44   setInternalValue(String(""));
45 }
46
47 StringInstanceImp::StringInstanceImp(ObjectImp *proto, const UString &string)
48   : ObjectImp(proto)
49 {
50   setInternalValue(String(string));
51 }
52
53 Value StringInstanceImp::get(ExecState *exec, const Identifier &propertyName) const
54 {
55   if (propertyName == lengthPropertyName)
56     return Number(internalValue().toString(exec).size());
57
58   bool ok;
59   const unsigned index = propertyName.toArrayIndex(&ok);
60   if (ok) {
61     const UString s = internalValue().toString(exec);
62     const unsigned length = s.size();
63     if (index >= length)
64       return Undefined();
65     const UChar c = s[index];
66     return String(UString(&c, 1));
67   }
68
69   return ObjectImp::get(exec, propertyName);
70 }
71
72 void StringInstanceImp::put(ExecState *exec, const Identifier &propertyName, const Value &value, int attr)
73 {
74   if (propertyName == lengthPropertyName)
75     return;
76   ObjectImp::put(exec, propertyName, value, attr);
77 }
78
79 bool StringInstanceImp::hasProperty(ExecState *exec, const Identifier &propertyName) const
80 {
81   if (propertyName == lengthPropertyName)
82     return true;
83
84   bool ok;
85   const unsigned index = propertyName.toArrayIndex(&ok);
86   if (ok) {
87     const unsigned length = internalValue().toString(exec).size();
88     if (index < length)
89       return true;
90   }
91
92   return ObjectImp::hasProperty(exec, propertyName);
93 }
94
95 bool StringInstanceImp::deleteProperty(ExecState *exec, const Identifier &propertyName)
96 {
97   if (propertyName == lengthPropertyName)
98     return false;
99   return ObjectImp::deleteProperty(exec, propertyName);
100 }
101
102 // ------------------------------ StringPrototypeImp ---------------------------
103 const ClassInfo StringPrototypeImp::info = {"String", &StringInstanceImp::info, &stringTable, 0};
104 /* Source for string_object.lut.h
105 @begin stringTable 26
106   toString              StringProtoFuncImp::ToString    DontEnum|Function       0
107   valueOf               StringProtoFuncImp::ValueOf     DontEnum|Function       0
108   charAt                StringProtoFuncImp::CharAt      DontEnum|Function       1
109   charCodeAt            StringProtoFuncImp::CharCodeAt  DontEnum|Function       1
110   concat                StringProtoFuncImp::Concat      DontEnum|Function       1
111   indexOf               StringProtoFuncImp::IndexOf     DontEnum|Function       1
112   lastIndexOf           StringProtoFuncImp::LastIndexOf DontEnum|Function       1
113   match                 StringProtoFuncImp::Match       DontEnum|Function       1
114   replace               StringProtoFuncImp::Replace     DontEnum|Function       2
115   search                StringProtoFuncImp::Search      DontEnum|Function       1
116   slice                 StringProtoFuncImp::Slice       DontEnum|Function       2
117   split                 StringProtoFuncImp::Split       DontEnum|Function       2
118   substr                StringProtoFuncImp::Substr      DontEnum|Function       2
119   substring             StringProtoFuncImp::Substring   DontEnum|Function       2
120   toLowerCase           StringProtoFuncImp::ToLowerCase DontEnum|Function       0
121   toUpperCase           StringProtoFuncImp::ToUpperCase DontEnum|Function       0
122   toLocaleLowerCase     StringProtoFuncImp::ToLocaleLowerCase DontEnum|Function 0
123   toLocaleUpperCase     StringProtoFuncImp::ToLocaleUpperCase DontEnum|Function 0
124 #
125 # Under here: html extension, should only exist if KJS_PURE_ECMA is not defined
126 # I guess we need to generate two hashtables in the .lut.h file, and use #ifdef
127 # to select the right one... TODO. #####
128   big                   StringProtoFuncImp::Big         DontEnum|Function       0
129   small                 StringProtoFuncImp::Small       DontEnum|Function       0
130   blink                 StringProtoFuncImp::Blink       DontEnum|Function       0
131   bold                  StringProtoFuncImp::Bold        DontEnum|Function       0
132   fixed                 StringProtoFuncImp::Fixed       DontEnum|Function       0
133   italics               StringProtoFuncImp::Italics     DontEnum|Function       0
134   strike                StringProtoFuncImp::Strike      DontEnum|Function       0
135   sub                   StringProtoFuncImp::Sub         DontEnum|Function       0
136   sup                   StringProtoFuncImp::Sup         DontEnum|Function       0
137   fontcolor             StringProtoFuncImp::Fontcolor   DontEnum|Function       1
138   fontsize              StringProtoFuncImp::Fontsize    DontEnum|Function       1
139   anchor                StringProtoFuncImp::Anchor      DontEnum|Function       1
140   link                  StringProtoFuncImp::Link        DontEnum|Function       1
141 @end
142 */
143 // ECMA 15.5.4
144 StringPrototypeImp::StringPrototypeImp(ExecState *exec,
145                                        ObjectPrototypeImp *objProto)
146   : StringInstanceImp(objProto)
147 {
148   Value protect(this);
149   // The constructor will be added later, after StringObjectImp has been built
150   putDirect(lengthPropertyName, NumberImp::zero(), DontDelete|ReadOnly|DontEnum);
151
152 }
153
154 Value StringPrototypeImp::get(ExecState *exec, const Identifier &propertyName) const
155 {
156   return lookupGetFunction<StringProtoFuncImp, StringInstanceImp>( exec, propertyName, &stringTable, this );
157 }
158
159 // ------------------------------ StringProtoFuncImp ---------------------------
160
161 StringProtoFuncImp::StringProtoFuncImp(ExecState *exec, int i, int len)
162   : InternalFunctionImp(
163     static_cast<FunctionPrototypeImp*>(exec->lexicalInterpreter()->builtinFunctionPrototype().imp())
164     ), id(i)
165 {
166   Value protect(this);
167   putDirect(lengthPropertyName, len, DontDelete|ReadOnly|DontEnum);
168 }
169
170 bool StringProtoFuncImp::implementsCall() const
171 {
172   return true;
173 }
174
175 static inline bool regExpIsGlobal(RegExpImp *regExp, ExecState *exec)
176 {
177     Value globalProperty = regExp->get(exec,"global");
178     return globalProperty.type() != UndefinedType && globalProperty.toBoolean(exec);
179 }
180
181 static inline void expandSourceRanges(UString::Range * & array, int& count, int& capacity)
182 {
183   int newCapacity;
184   if (capacity == 0) {
185     newCapacity = 16;
186   } else {
187     newCapacity = capacity * 2;
188   }
189
190   UString::Range *newArray = new UString::Range[newCapacity];
191   for (int i = 0; i < count; i++) {
192     newArray[i] = array[i];
193   }
194
195   delete [] array;
196
197   capacity = newCapacity;
198   array = newArray;
199 }
200
201 static void pushSourceRange(UString::Range * & array, int& count, int& capacity, UString::Range range)
202 {
203   if (count + 1 > capacity)
204     expandSourceRanges(array, count, capacity);
205
206   array[count] = range;
207   count++;
208 }
209
210 static inline void expandReplacements(UString * & array, int& count, int& capacity)
211 {
212   int newCapacity;
213   if (capacity == 0) {
214     newCapacity = 16;
215   } else {
216     newCapacity = capacity * 2;
217   }
218
219   UString *newArray = new UString[newCapacity];
220   for (int i = 0; i < count; i++) {
221     newArray[i] = array[i];
222   }
223   
224   delete [] array;
225
226   capacity = newCapacity;
227   array = newArray;
228 }
229
230 static void pushReplacement(UString * & array, int& count, int& capacity, UString replacement)
231 {
232   if (count + 1 > capacity)
233     expandReplacements(array, count, capacity);
234
235   array[count] = replacement;
236   count++;
237 }
238
239 static inline UString substituteBackreferences(const UString &replacement, const UString &source, int **ovector, RegExp *reg)
240 {
241   UString substitutedReplacement = replacement;
242
243   bool converted;
244
245   for (int i = 0; (i = substitutedReplacement.find(UString("$"), i)) != -1; i++) {
246     if (i+1 < substitutedReplacement.size() && substitutedReplacement[i+1] == '$') {  // "$$" -> "$"
247       substitutedReplacement = substitutedReplacement.substr(0,i) + "$" + substitutedReplacement.substr(i+2);
248       continue;
249     }
250     // Assume number part is one char exactly
251     unsigned long backrefIndex = substitutedReplacement.substr(i+1,1).toULong(&converted, false /* tolerate empty string */);
252     if (converted && backrefIndex <= (unsigned)reg->subPatterns()) {
253       int backrefStart = (*ovector)[2*backrefIndex];
254       int backrefLength = (*ovector)[2*backrefIndex+1] - backrefStart;
255       substitutedReplacement = substitutedReplacement.substr(0,i)
256         + source.substr(backrefStart, backrefLength)
257         + substitutedReplacement.substr(i+2);
258       i += backrefLength - 1; // -1 offsets i++
259     }
260   }
261
262   return substitutedReplacement;
263 }
264
265 static Value replace(ExecState *exec, const UString &source, const Value &pattern, const Value &replacement)
266 {
267   if (pattern.type() == ObjectType && pattern.toObject(exec).inherits(&RegExpImp::info)) {
268     RegExpImp* imp = static_cast<RegExpImp *>( pattern.toObject(exec).imp() );
269     RegExp *reg = imp->regExp();
270     bool global = regExpIsGlobal(imp, exec);
271
272     RegExpObjectImp* regExpObj = static_cast<RegExpObjectImp*>(exec->lexicalInterpreter()->builtinRegExp().imp());
273
274     UString replacementString = replacement.toString(exec);
275
276     int matchIndex = 0;
277     int lastIndex = 0;
278     int startPosition = 0;
279
280     UString::Range *sourceRanges = 0;
281     int sourceRangeCount = 0;
282     int sourceRangeCapacity = 0;
283     UString *replacements = 0;
284     int replacementCount = 0;
285     int replacementCapacity = 0;
286
287     // This is either a loop (if global is set) or a one-way (if not).
288     do {
289       int **ovector = regExpObj->registerRegexp( reg, source );
290       UString matchString = reg->match(source, startPosition, &matchIndex, ovector);
291       regExpObj->setSubPatterns(reg->subPatterns());
292       if (matchIndex == -1)
293         break;
294       int matchLen = matchString.size();
295
296       pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, matchIndex - lastIndex));
297
298       UString substitutedReplacement = substituteBackreferences(replacementString, source, ovector, reg);
299       pushReplacement(replacements, replacementCount, replacementCapacity, substitutedReplacement);
300
301       lastIndex = matchIndex + matchLen;
302       startPosition = lastIndex;
303
304       // special case of empty match
305       if (matchLen == 0) {
306         startPosition++;
307         if (startPosition > source.size())
308           break;
309       }
310     } while (global);
311
312     if (lastIndex < source.size())
313       pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, source.size() - lastIndex));
314
315     UString result = source.spliceSubstringsWithSeparators(sourceRanges, sourceRangeCount, replacements, replacementCount);
316
317     delete [] sourceRanges;
318     delete [] replacements;
319
320     return String(result);
321   } else { // First arg is a string
322     UString patternString = pattern.toString(exec);
323     int matchPos = source.find(patternString);
324     int matchLen = patternString.size();
325     // Do the replacement
326     if (matchPos == -1)
327       return String(source);
328     else {
329       return String(source.substr(0, matchPos) + replacement.toString(exec) + source.substr(matchPos + matchLen));
330     }
331   }
332 }
333
334 // ECMA 15.5.4.2 - 15.5.4.20
335 Value StringProtoFuncImp::call(ExecState *exec, Object &thisObj, const List &args)
336 {
337   Value result;
338
339   // toString and valueOf are no generic function.
340   if (id == ToString || id == ValueOf) {
341     if (thisObj.isNull() || !thisObj.inherits(&StringInstanceImp::info)) {
342       Object err = Error::create(exec,TypeError);
343       exec->setException(err);
344       return err;
345     }
346
347     return String(thisObj.internalValue().toString(exec));
348   }
349
350   UString u, u2, u3;
351   int pos, p0, i;
352   double dpos;
353   double d = 0.0;
354
355   UString s = thisObj.toString(exec);
356
357   int len = s.size();
358   Value a0 = args[0];
359   Value a1 = args[1];
360
361   switch (id) {
362   case ToString:
363   case ValueOf:
364     // handled above
365     break;
366   case CharAt:
367     // Other browsers treat an omitted parameter as 0 rather than NaN.
368     // That doesn't match the ECMA standard, but is needed for site compatibility.
369     dpos = a0.isA(UndefinedType) ? 0 : a0.toInteger(exec);
370     if (dpos >= 0 && dpos < len) // false for NaN
371       u = s.substr(static_cast<int>(dpos), 1);
372     else
373       u = "";
374     result = String(u);
375     break;
376   case CharCodeAt:
377     // Other browsers treat an omitted parameter as 0 rather than NaN.
378     // That doesn't match the ECMA standard, but is needed for site compatibility.
379     dpos = a0.isA(UndefinedType) ? 0 : a0.toInteger(exec);
380     if (dpos >= 0 && dpos < len) // false for NaN
381       result = Number(s[static_cast<int>(dpos)].unicode());
382     else
383       result = Number(NaN);
384     break;
385   case Concat: {
386     ListIterator it = args.begin();
387     for ( ; it != args.end() ; ++it) {
388         s += it->dispatchToString(exec);
389     }
390     result = String(s);
391     break;
392   }
393   case IndexOf:
394     u2 = a0.toString(exec);
395     if (a1.type() == UndefinedType)
396       dpos = 0;
397     else {
398       dpos = a1.toInteger(exec);
399       if (dpos >= 0) { // false for NaN
400         if (dpos > len)
401           dpos = len;
402       } else
403         dpos = 0;
404     }
405     result = Number(s.find(u2, static_cast<int>(dpos)));
406     break;
407   case LastIndexOf:
408     u2 = a0.toString(exec);
409     d = a1.toNumber(exec);
410     if (a1.type() == UndefinedType || KJS::isNaN(d))
411       dpos = len;
412     else {
413       dpos = a1.toInteger(exec);
414       if (dpos >= 0) { // false for NaN
415         if (dpos > len)
416           dpos = len;
417       } else
418         dpos = 0;
419     }
420     result = Number(s.rfind(u2, static_cast<int>(dpos)));
421     break;
422   case Match:
423   case Search: {
424     u = s;
425     RegExp *reg, *tmpReg = 0;
426     RegExpImp *imp = 0;
427     if (a0.isA(ObjectType) && a0.toObject(exec).inherits(&RegExpImp::info))
428     {
429       imp = static_cast<RegExpImp *>( a0.toObject(exec).imp() );
430       reg = imp->regExp();
431     }
432     else
433     { /*
434        *  ECMA 15.5.4.12 String.prototype.search (regexp)
435        *  If regexp is not an object whose [[Class]] property is "RegExp", it is
436        *  replaced with the result of the expression new RegExp(regexp).
437        */
438       reg = tmpReg = new RegExp(a0.toString(exec), RegExp::None);
439     }
440     RegExpObjectImp* regExpObj = static_cast<RegExpObjectImp*>(exec->lexicalInterpreter()->builtinRegExp().imp());
441     int **ovector = regExpObj->registerRegexp(reg, u);
442     UString mstr = reg->match(u, -1, &pos, ovector);
443     if (id == Search) {
444       result = Number(pos);
445     } else {
446       // Exec
447       if ((reg->flags() & RegExp::Global) == 0) {
448         // case without 'g' flag is handled like RegExp.prototype.exec
449         if (mstr.isNull()) {
450           result = Null();
451         } else {
452           regExpObj->setSubPatterns(reg->subPatterns());
453           result = regExpObj->arrayOfMatches(exec,mstr);
454         }
455       } else {
456         // return array of matches
457         List list;
458         int lastIndex = 0;
459         while (pos >= 0) {
460           if (mstr.isNull())
461             list.append(UndefinedImp::staticUndefined);
462           else
463             list.append(String(mstr));
464           lastIndex = pos;
465           pos += mstr.isEmpty() ? 1 : mstr.size();
466           delete [] *ovector;
467           mstr = reg->match(u, pos, &pos, ovector);
468         }
469         if (imp)
470           imp->put(exec, "lastIndex", Number(lastIndex), DontDelete|DontEnum);
471         if (list.isEmpty()) {
472           // if there are no matches at all, it's important to return
473           // Null instead of an empty array, because this matches
474           // other browsers and because Null is a false value.
475           result = Null(); 
476         } else {
477           result = exec->lexicalInterpreter()->builtinArray().construct(exec, list);
478         }
479       }
480     }
481     delete tmpReg;
482     break;
483   }
484   case Replace:
485     result = replace(exec, s, a0, a1);
486     break;
487   case Slice: // http://developer.netscape.com/docs/manuals/js/client/jsref/string.htm#1194366
488     {
489         // The arg processing is very much like ArrayProtoFunc::Slice
490         double begin = args[0].toInteger(exec);
491         if (begin >= 0) { // false for NaN
492           if (begin > len)
493             begin = len;
494         } else {
495           begin += len;
496           if (!(begin >= 0)) // true for NaN
497             begin = 0;
498         }
499         double end = len;
500         if (args[1].type() != UndefinedType) {
501           end = args[1].toInteger(exec);
502           if (end >= 0) { // false for NaN
503             if (end > len)
504               end = len;
505           } else {
506             end += len;
507             if (!(end >= 0)) // true for NaN
508               end = 0;
509           }
510         }
511         //printf( "Slicing from %d to %d \n", begin, end );
512         result = String(s.substr(static_cast<int>(begin), static_cast<int>(end-begin)));
513         break;
514     }
515     case Split: {
516     Object constructor = exec->lexicalInterpreter()->builtinArray();
517     Object res = Object::dynamicCast(constructor.construct(exec,List::empty()));
518     result = res;
519     u = s;
520     i = p0 = 0;
521     uint32_t limit = a1.type() == UndefinedType ? 0xFFFFFFFFU : a1.toUInt32(exec);
522     if (a0.type() == ObjectType && Object::dynamicCast(a0).inherits(&RegExpImp::info)) {
523       Object obj0 = Object::dynamicCast(a0);
524       RegExp reg(obj0.get(exec,"source").toString(exec));
525       if (u.isEmpty() && !reg.match(u, 0).isNull()) {
526         // empty string matched by regexp -> empty array
527         res.put(exec,lengthPropertyName, Number(0));
528         break;
529       }
530       pos = 0;
531       while (static_cast<uint32_t>(i) != limit && pos < u.size()) {
532         // TODO: back references
533         int mpos;
534         int *ovector = 0L;
535         UString mstr = reg.match(u, pos, &mpos, &ovector);
536         delete [] ovector; ovector = 0L;
537         if (mpos < 0)
538           break;
539         pos = mpos + (mstr.isEmpty() ? 1 : mstr.size());
540         if (mpos != p0 || !mstr.isEmpty()) {
541           res.put(exec,i, String(u.substr(p0, mpos-p0)));
542           p0 = mpos + mstr.size();
543           i++;
544         }
545       }
546     } else {
547       u2 = a0.toString(exec);
548       if (u2.isEmpty()) {
549         if (u.isEmpty()) {
550           // empty separator matches empty string -> empty array
551           put(exec,lengthPropertyName, Number(0));
552           break;
553         } else {
554           while (static_cast<uint32_t>(i) != limit && i < u.size()-1)
555             res.put(exec, i++, String(u.substr(p0++, 1)));
556         }
557       } else {
558         while (static_cast<uint32_t>(i) != limit && (pos = u.find(u2, p0)) >= 0) {
559           res.put(exec, i, String(u.substr(p0, pos-p0)));
560           p0 = pos + u2.size();
561           i++;
562         }
563       }
564     }
565     // add remaining string, if any
566     if (static_cast<uint32_t>(i) != limit)
567       res.put(exec, i++, String(u.substr(p0)));
568     res.put(exec,lengthPropertyName, Number(i));
569     }
570     break;
571   case Substr: {
572     double d = a0.toInteger(exec);
573     double d2 = a1.toInteger(exec);
574     if (!(d >= 0)) { // true for NaN
575       d += len;
576       if (!(d >= 0)) // true for NaN
577         d = 0;
578     }
579     if (isNaN(d2))
580       d2 = len - d;
581     else {
582       if (d2 < 0)
583         d2 = 0;
584       if (d2 > len - d)
585         d2 = len - d;
586     }
587     result = String(s.substr(static_cast<int>(d), static_cast<int>(d2)));
588     break;
589   }
590   case Substring: {
591     double start = a0.toNumber(exec);
592     double end = a1.toNumber(exec);
593     if (KJS::isNaN(start))
594       start = 0;
595     if (KJS::isNaN(end))
596       end = 0;
597     if (start < 0)
598       start = 0;
599     if (end < 0)
600       end = 0;
601     if (start > len)
602       start = len;
603     if (end > len)
604       end = len;
605     if (a1.type() == UndefinedType)
606       end = len;
607     if (start > end) {
608       double temp = end;
609       end = start;
610       start = temp;
611     }
612     result = String(s.substr((int)start, (int)end-(int)start));
613     }
614     break;
615   case ToLowerCase:
616   case ToLocaleLowerCase: // FIXME: To get this 100% right we need to detect Turkish and change I to lowercase i without a dot.
617     u = s;
618     for (i = 0; i < len; i++)
619       u[i] = u[i].toLower();
620     result = String(u);
621     break;
622   case ToUpperCase:
623   case ToLocaleUpperCase: // FIXME: To get this 100% right we need to detect Turkish and change i to uppercase I with a dot.
624     u = s;
625     for (i = 0; i < len; i++)
626       u[i] = u[i].toUpper();
627     result = String(u);
628     break;
629 #ifndef KJS_PURE_ECMA
630   case Big:
631     result = String("<big>" + s + "</big>");
632     break;
633   case Small:
634     result = String("<small>" + s + "</small>");
635     break;
636   case Blink:
637     result = String("<blink>" + s + "</blink>");
638     break;
639   case Bold:
640     result = String("<b>" + s + "</b>");
641     break;
642   case Fixed:
643     result = String("<tt>" + s + "</tt>");
644     break;
645   case Italics:
646     result = String("<i>" + s + "</i>");
647     break;
648   case Strike:
649     result = String("<strike>" + s + "</strike>");
650     break;
651   case Sub:
652     result = String("<sub>" + s + "</sub>");
653     break;
654   case Sup:
655     result = String("<sup>" + s + "</sup>");
656     break;
657   case Fontcolor:
658     result = String("<font color=\"" + a0.toString(exec) + "\">" + s + "</font>");
659     break;
660   case Fontsize:
661     result = String("<font size=\"" + a0.toString(exec) + "\">" + s + "</font>");
662     break;
663   case Anchor:
664     result = String("<a name=\"" + a0.toString(exec) + "\">" + s + "</a>");
665     break;
666   case Link:
667     result = String("<a href=\"" + a0.toString(exec) + "\">" + s + "</a>");
668     break;
669 #endif
670   }
671
672   return result;
673 }
674
675 // ------------------------------ StringObjectImp ------------------------------
676
677 StringObjectImp::StringObjectImp(ExecState *exec,
678                                  FunctionPrototypeImp *funcProto,
679                                  StringPrototypeImp *stringProto)
680   : InternalFunctionImp(funcProto)
681 {
682   Value protect(this);
683   // ECMA 15.5.3.1 String.prototype
684   putDirect(prototypePropertyName, stringProto, DontEnum|DontDelete|ReadOnly);
685
686   static Identifier fromCharCode("fromCharCode");
687   putDirect(fromCharCode, new StringObjectFuncImp(exec,funcProto), DontEnum);
688
689   // no. of arguments for constructor
690   putDirect(lengthPropertyName, NumberImp::one(), ReadOnly|DontDelete|DontEnum);
691 }
692
693
694 bool StringObjectImp::implementsConstruct() const
695 {
696   return true;
697 }
698
699 // ECMA 15.5.2
700 Object StringObjectImp::construct(ExecState *exec, const List &args)
701 {
702   ObjectImp *proto = exec->lexicalInterpreter()->builtinStringPrototype().imp();
703   if (args.size() == 0)
704     return Object(new StringInstanceImp(proto));
705   return Object(new StringInstanceImp(proto, args.begin()->dispatchToString(exec)));
706 }
707
708 bool StringObjectImp::implementsCall() const
709 {
710   return true;
711 }
712
713 // ECMA 15.5.1
714 Value StringObjectImp::call(ExecState *exec, Object &/*thisObj*/, const List &args)
715 {
716   if (args.isEmpty())
717     return String("");
718   else {
719     Value v = args[0];
720     return String(v.toString(exec));
721   }
722 }
723
724 // ------------------------------ StringObjectFuncImp --------------------------
725
726 // ECMA 15.5.3.2 fromCharCode()
727 StringObjectFuncImp::StringObjectFuncImp(ExecState *exec, FunctionPrototypeImp *funcProto)
728   : InternalFunctionImp(funcProto)
729 {
730   Value protect(this);
731   putDirect(lengthPropertyName, NumberImp::one(), DontDelete|ReadOnly|DontEnum);
732 }
733
734 bool StringObjectFuncImp::implementsCall() const
735 {
736   return true;
737 }
738
739 Value StringObjectFuncImp::call(ExecState *exec, Object &/*thisObj*/, const List &args)
740 {
741   UString s;
742   if (args.size()) {
743     UChar *buf = static_cast<UChar *>(kjs_fast_malloc(args.size() * sizeof(UChar)));
744     UChar *p = buf;
745     ListIterator it = args.begin();
746     while (it != args.end()) {
747       unsigned short u = it->toUInt16(exec);
748       *p++ = UChar(u);
749       it++;
750     }
751     s = UString(buf, args.size(), false);
752   } else
753     s = "";
754
755   return String(s);
756 }