16d68776d3f3b9f0bd262a04fc43fc2644bde233
[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) 2003 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 #
123 # Under here: html extension, should only exist if KJS_PURE_ECMA is not defined
124 # I guess we need to generate two hashtables in the .lut.h file, and use #ifdef
125 # to select the right one... TODO. #####
126   big                   StringProtoFuncImp::Big         DontEnum|Function       0
127   small                 StringProtoFuncImp::Small       DontEnum|Function       0
128   blink                 StringProtoFuncImp::Blink       DontEnum|Function       0
129   bold                  StringProtoFuncImp::Bold        DontEnum|Function       0
130   fixed                 StringProtoFuncImp::Fixed       DontEnum|Function       0
131   italics               StringProtoFuncImp::Italics     DontEnum|Function       0
132   strike                StringProtoFuncImp::Strike      DontEnum|Function       0
133   sub                   StringProtoFuncImp::Sub         DontEnum|Function       0
134   sup                   StringProtoFuncImp::Sup         DontEnum|Function       0
135   fontcolor             StringProtoFuncImp::Fontcolor   DontEnum|Function       1
136   fontsize              StringProtoFuncImp::Fontsize    DontEnum|Function       1
137   anchor                StringProtoFuncImp::Anchor      DontEnum|Function       1
138   link                  StringProtoFuncImp::Link        DontEnum|Function       1
139 @end
140 */
141 // ECMA 15.5.4
142 StringPrototypeImp::StringPrototypeImp(ExecState *exec,
143                                        ObjectPrototypeImp *objProto)
144   : StringInstanceImp(objProto)
145 {
146   Value protect(this);
147   // The constructor will be added later, after StringObjectImp has been built
148   putDirect(lengthPropertyName, NumberImp::zero(), DontDelete|ReadOnly|DontEnum);
149
150 }
151
152 Value StringPrototypeImp::get(ExecState *exec, const Identifier &propertyName) const
153 {
154   return lookupGetFunction<StringProtoFuncImp, StringInstanceImp>( exec, propertyName, &stringTable, this );
155 }
156
157 // ------------------------------ StringProtoFuncImp ---------------------------
158
159 StringProtoFuncImp::StringProtoFuncImp(ExecState *exec, int i, int len)
160   : InternalFunctionImp(
161     static_cast<FunctionPrototypeImp*>(exec->lexicalInterpreter()->builtinFunctionPrototype().imp())
162     ), id(i)
163 {
164   Value protect(this);
165   putDirect(lengthPropertyName, len, DontDelete|ReadOnly|DontEnum);
166 }
167
168 bool StringProtoFuncImp::implementsCall() const
169 {
170   return true;
171 }
172
173 // ECMA 15.5.4.2 - 15.5.4.20
174 Value StringProtoFuncImp::call(ExecState *exec, Object &thisObj, const List &args)
175 {
176   Value result;
177
178   // toString and valueOf are no generic function.
179   if (id == ToString || id == ValueOf) {
180     if (thisObj.isNull() || !thisObj.inherits(&StringInstanceImp::info)) {
181       Object err = Error::create(exec,TypeError);
182       exec->setException(err);
183       return err;
184     }
185
186     return String(thisObj.internalValue().toString(exec));
187   }
188
189   UString u, u2, u3;
190   int pos, p0, i;
191   double dpos;
192   double d = 0.0;
193
194   UString s = thisObj.toString(exec);
195
196   int len = s.size();
197   Value a0 = args[0];
198   Value a1 = args[1];
199
200   switch (id) {
201   case ToString:
202   case ValueOf:
203     // handled above
204     break;
205   case CharAt:
206     dpos = a0.toInteger(exec);
207     if (dpos >= 0 && dpos < len) // false for NaN
208       u = s.substr(static_cast<int>(dpos), 1);
209     else
210       u = "";
211     result = String(u);
212     break;
213   case CharCodeAt:
214     dpos = a0.toInteger(exec);
215     if (dpos >= 0 && dpos < len) {// false for NaN
216       UChar c = s[static_cast<int>(dpos)];
217       d = (c.high() << 8) + c.low();
218     } else
219       d = NaN;
220     result = Number(d);
221     break;
222   case Concat: {
223     ListIterator it = args.begin();
224     for ( ; it != args.end() ; ++it) {
225         s += it->dispatchToString(exec);
226     }
227     result = String(s);
228     break;
229   }
230   case IndexOf:
231     u2 = a0.toString(exec);
232     if (a1.type() == UndefinedType)
233       dpos = 0;
234     else {
235       dpos = a1.toInteger(exec);
236       if (dpos >= 0) { // false for NaN
237         if (dpos > len)
238           dpos = len;
239       } else
240         dpos = 0;
241     }
242     d = s.find(u2, static_cast<int>(dpos));
243     result = Number(d);
244     break;
245   case LastIndexOf:
246     u2 = a0.toString(exec);
247     d = a1.toNumber(exec);
248     if (a1.type() == UndefinedType || KJS::isNaN(d))
249       dpos = len;
250     else {
251       dpos = a1.toInteger(exec);
252       if (dpos >= 0) { // false for NaN
253         if (dpos > len)
254           dpos = len;
255       } else
256         dpos = 0;
257     }
258     d = s.rfind(u2, static_cast<int>(dpos));
259     result = Number(d);
260     break;
261   case Match:
262   case Search: {
263     u = s;
264     RegExp *reg, *tmpReg = 0;
265     RegExpImp *imp = 0;
266     if (a0.isA(ObjectType) && a0.toObject(exec).inherits(&RegExpImp::info))
267     {
268       imp = static_cast<RegExpImp *>( a0.toObject(exec).imp() );
269       reg = imp->regExp();
270     }
271     else
272     { /*
273        *  ECMA 15.5.4.12 String.prototype.search (regexp)
274        *  If regexp is not an object whose [[Class]] property is "RegExp", it is
275        *  replaced with the result of the expression new RegExp(regexp).
276        */
277       reg = tmpReg = new RegExp(a0.toString(exec), RegExp::None);
278     }
279     RegExpObjectImp* regExpObj = static_cast<RegExpObjectImp*>(exec->lexicalInterpreter()->builtinRegExp().imp());
280     int **ovector = regExpObj->registerRegexp(reg, u);
281     UString mstr = reg->match(u, -1, &pos, ovector);
282     if (id == Search) {
283       result = Number(pos);
284     } else {
285       // Exec
286       if ((reg->flags() & RegExp::Global) == 0) {
287         // case without 'g' flag is handled like RegExp.prototype.exec
288         if (mstr.isNull()) {
289           result = Null();
290         } else {
291           regExpObj->setSubPatterns(reg->subPatterns());
292           result = regExpObj->arrayOfMatches(exec,mstr);
293         }
294       } else {
295         // return array of matches
296         List list;
297         int lastIndex = 0;
298         while (pos >= 0) {
299           if (mstr.isNull())
300             list.append(UndefinedImp::staticUndefined);
301           else
302             list.append(String(mstr));
303           lastIndex = pos;
304           pos += mstr.isEmpty() ? 1 : mstr.size();
305           delete [] *ovector;
306           mstr = reg->match(u, pos, &pos, ovector);
307         }
308         if (imp)
309           imp->put(exec, "lastIndex", Number(lastIndex), DontDelete|DontEnum);
310         if (list.isEmpty()) {
311           // if there are no matches at all, it's important to return
312           // Null instead of an empty array, because this matches
313           // other browsers and because Null is a false value.
314           result = Null(); 
315         } else {
316           result = exec->lexicalInterpreter()->builtinArray().construct(exec, list);
317         }
318       }
319     }
320     delete tmpReg;
321     break;
322   }
323   case Replace:
324     u = s;
325     if (a0.type() == ObjectType && a0.toObject(exec).inherits(&RegExpImp::info)) {
326       RegExpImp* imp = static_cast<RegExpImp *>( a0.toObject(exec).imp() );
327       RegExp *reg = imp->regExp();
328       bool global = false;
329       Value tmp = imp->get(exec,"global");
330       if (tmp.type() != UndefinedType && tmp.toBoolean(exec) == true)
331         global = true;
332
333       RegExpObjectImp* regExpObj = static_cast<RegExpObjectImp*>(exec->lexicalInterpreter()->builtinRegExp().imp());
334       int lastIndex = 0;
335       u3 = a1.toString(exec); // replacement string
336       // This is either a loop (if global is set) or a one-way (if not).
337       do {
338         int **ovector = regExpObj->registerRegexp( reg, u );
339         UString mstr = reg->match(u, lastIndex, &pos, ovector);
340         regExpObj->setSubPatterns(reg->subPatterns());
341         if (pos == -1)
342           break;
343         len = mstr.size();
344         UString rstr(u3);
345         bool ok;
346         // check if u3 matches $1 or $2 etc
347         for (int i = 0; (i = rstr.find(UString("$"), i)) != -1; i++) {
348           if (i+1<rstr.size() && rstr[i+1] == '$') {  // "$$" -> "$"
349             rstr = rstr.substr(0,i) + "$" + rstr.substr(i+2);
350             continue;
351           }
352           // Assume number part is one char exactly
353           unsigned long pos = rstr.substr(i+1,1).toULong(&ok, false /* tolerate empty string */);
354           if (ok && pos <= (unsigned)reg->subPatterns()) {
355             rstr = rstr.substr(0,i)
356                       + u.substr((*ovector)[2*pos],
357                                      (*ovector)[2*pos+1]-(*ovector)[2*pos])
358                       + rstr.substr(i+2);
359             i += (*ovector)[2*pos+1]-(*ovector)[2*pos] - 1; // -1 offsets i++
360           }
361         }
362         lastIndex = pos + rstr.size();
363         u = u.substr(0, pos) + rstr + u.substr(pos + len);
364         //fprintf(stderr,"pos=%d,len=%d,lastIndex=%d,u=%s\n",pos,len,lastIndex,u.ascii());
365
366         // special case of empty match
367         if (len == 0) {
368           lastIndex = lastIndex + 1;
369           if (lastIndex > u.size())
370             break;
371         }
372       } while (global);
373
374       result = String(u);
375     } else { // First arg is a string
376       u2 = a0.toString(exec);
377       pos = u.find(u2);
378       len = u2.size();
379       // Do the replacement
380       if (pos == -1)
381         result = String(s);
382       else {
383         u3 = u.substr(0, pos) + a1.toString(exec) +
384              u.substr(pos + len);
385         result = String(u3);
386       }
387     }
388     break;
389   case Slice: // http://developer.netscape.com/docs/manuals/js/client/jsref/string.htm#1194366
390     {
391         // The arg processing is very much like ArrayProtoFunc::Slice
392         double begin = args[0].toInteger(exec);
393         if (begin >= 0) { // false for NaN
394           if (begin > len)
395             begin = len;
396         } else {
397           begin += len;
398           if (!(begin >= 0)) // true for NaN
399             begin = 0;
400         }
401         double end = len;
402         if (args[1].type() != UndefinedType) {
403           end = args[1].toInteger(exec);
404           if (end >= 0) { // false for NaN
405             if (end > len)
406               end = len;
407           } else {
408             end += len;
409             if (!(end >= 0)) // true for NaN
410               end = 0;
411           }
412         }
413         //printf( "Slicing from %d to %d \n", begin, end );
414         result = String(s.substr(static_cast<int>(begin), static_cast<int>(end-begin)));
415         break;
416     }
417     case Split: {
418     Object constructor = exec->lexicalInterpreter()->builtinArray();
419     Object res = Object::dynamicCast(constructor.construct(exec,List::empty()));
420     result = res;
421     u = s;
422     i = p0 = 0;
423     uint32_t limit = a1.type() == UndefinedType ? 0xFFFFFFFFU : a1.toUInt32(exec);
424     if (a0.type() == ObjectType && Object::dynamicCast(a0).inherits(&RegExpImp::info)) {
425       Object obj0 = Object::dynamicCast(a0);
426       RegExp reg(obj0.get(exec,"source").toString(exec));
427       if (u.isEmpty() && !reg.match(u, 0).isNull()) {
428         // empty string matched by regexp -> empty array
429         res.put(exec,lengthPropertyName, Number(0));
430         break;
431       }
432       pos = 0;
433       while (static_cast<uint32_t>(i) != limit && pos < u.size()) {
434         // TODO: back references
435         int mpos;
436         int *ovector = 0L;
437         UString mstr = reg.match(u, pos, &mpos, &ovector);
438         delete [] ovector; ovector = 0L;
439         if (mpos < 0)
440           break;
441         pos = mpos + (mstr.isEmpty() ? 1 : mstr.size());
442         if (mpos != p0 || !mstr.isEmpty()) {
443           res.put(exec,i, String(u.substr(p0, mpos-p0)));
444           p0 = mpos + mstr.size();
445           i++;
446         }
447       }
448     } else {
449       u2 = a0.toString(exec);
450       if (u2.isEmpty()) {
451         if (u.isEmpty()) {
452           // empty separator matches empty string -> empty array
453           put(exec,lengthPropertyName, Number(0));
454           break;
455         } else {
456           while (static_cast<uint32_t>(i) != limit && i < u.size()-1)
457             res.put(exec, i++, String(u.substr(p0++, 1)));
458         }
459       } else {
460         while (static_cast<uint32_t>(i) != limit && (pos = u.find(u2, p0)) >= 0) {
461           res.put(exec, i, String(u.substr(p0, pos-p0)));
462           p0 = pos + u2.size();
463           i++;
464         }
465       }
466     }
467     // add remaining string, if any
468     if (static_cast<uint32_t>(i) != limit)
469       res.put(exec, i++, String(u.substr(p0)));
470     res.put(exec,lengthPropertyName, Number(i));
471     }
472     break;
473   case Substr: {
474     double d = a0.toInteger(exec);
475     double d2 = a1.toInteger(exec);
476     if (!(d >= 0)) { // true for NaN
477       d += len;
478       if (!(d >= 0)) // true for NaN
479         d = 0;
480     }
481     if (isNaN(d2))
482       d2 = len - d;
483     else {
484       if (d2 < 0)
485         d2 = 0;
486       if (d2 > len - d)
487         d2 = len - d;
488     }
489     result = String(s.substr(static_cast<int>(d), static_cast<int>(d2)));
490     break;
491   }
492   case Substring: {
493     double start = a0.toNumber(exec);
494     double end = a1.toNumber(exec);
495     if (KJS::isNaN(start))
496       start = 0;
497     if (KJS::isNaN(end))
498       end = 0;
499     if (start < 0)
500       start = 0;
501     if (end < 0)
502       end = 0;
503     if (start > len)
504       start = len;
505     if (end > len)
506       end = len;
507     if (a1.type() == UndefinedType)
508       end = len;
509     if (start > end) {
510       double temp = end;
511       end = start;
512       start = temp;
513     }
514     result = String(s.substr((int)start, (int)end-(int)start));
515     }
516     break;
517   case ToLowerCase:
518     u = s;
519     for (i = 0; i < len; i++)
520       u[i] = u[i].toLower();
521     result = String(u);
522     break;
523   case ToUpperCase:
524     u = s;
525     for (i = 0; i < len; i++)
526       u[i] = u[i].toUpper();
527     result = String(u);
528     break;
529 #ifndef KJS_PURE_ECMA
530   case Big:
531     result = String("<big>" + s + "</big>");
532     break;
533   case Small:
534     result = String("<small>" + s + "</small>");
535     break;
536   case Blink:
537     result = String("<blink>" + s + "</blink>");
538     break;
539   case Bold:
540     result = String("<b>" + s + "</b>");
541     break;
542   case Fixed:
543     result = String("<tt>" + s + "</tt>");
544     break;
545   case Italics:
546     result = String("<i>" + s + "</i>");
547     break;
548   case Strike:
549     result = String("<strike>" + s + "</strike>");
550     break;
551   case Sub:
552     result = String("<sub>" + s + "</sub>");
553     break;
554   case Sup:
555     result = String("<sup>" + s + "</sup>");
556     break;
557   case Fontcolor:
558     result = String("<font color=" + a0.toString(exec) + ">"
559                     + s + "</font>");
560     break;
561   case Fontsize:
562     result = String("<font size=" + a0.toString(exec) + ">"
563                     + s + "</font>");
564     break;
565   case Anchor:
566     result = String("<a name=" + a0.toString(exec) + ">"
567                     + s + "</a>");
568     break;
569   case Link:
570     result = String("<a href=" + a0.toString(exec) + ">"
571                     + s + "</a>");
572     break;
573 #endif
574   }
575
576   return result;
577 }
578
579 // ------------------------------ StringObjectImp ------------------------------
580
581 StringObjectImp::StringObjectImp(ExecState *exec,
582                                  FunctionPrototypeImp *funcProto,
583                                  StringPrototypeImp *stringProto)
584   : InternalFunctionImp(funcProto)
585 {
586   Value protect(this);
587   // ECMA 15.5.3.1 String.prototype
588   putDirect(prototypePropertyName, stringProto, DontEnum|DontDelete|ReadOnly);
589
590   static Identifier fromCharCode("fromCharCode");
591   putDirect(fromCharCode, new StringObjectFuncImp(exec,funcProto), DontEnum);
592
593   // no. of arguments for constructor
594   putDirect(lengthPropertyName, NumberImp::one(), ReadOnly|DontDelete|DontEnum);
595 }
596
597
598 bool StringObjectImp::implementsConstruct() const
599 {
600   return true;
601 }
602
603 // ECMA 15.5.2
604 Object StringObjectImp::construct(ExecState *exec, const List &args)
605 {
606   ObjectImp *proto = exec->lexicalInterpreter()->builtinStringPrototype().imp();
607   if (args.size() == 0)
608     return Object(new StringInstanceImp(proto));
609   return Object(new StringInstanceImp(proto, args.begin()->dispatchToString(exec)));
610 }
611
612 bool StringObjectImp::implementsCall() const
613 {
614   return true;
615 }
616
617 // ECMA 15.5.1
618 Value StringObjectImp::call(ExecState *exec, Object &/*thisObj*/, const List &args)
619 {
620   if (args.isEmpty())
621     return String("");
622   else {
623     Value v = args[0];
624     return String(v.toString(exec));
625   }
626 }
627
628 // ------------------------------ StringObjectFuncImp --------------------------
629
630 // ECMA 15.5.3.2 fromCharCode()
631 StringObjectFuncImp::StringObjectFuncImp(ExecState *exec, FunctionPrototypeImp *funcProto)
632   : InternalFunctionImp(funcProto)
633 {
634   Value protect(this);
635   putDirect(lengthPropertyName, NumberImp::one(), DontDelete|ReadOnly|DontEnum);
636 }
637
638 bool StringObjectFuncImp::implementsCall() const
639 {
640   return true;
641 }
642
643 Value StringObjectFuncImp::call(ExecState *exec, Object &/*thisObj*/, const List &args)
644 {
645   UString s;
646   if (args.size()) {
647     UChar *buf = new UChar[args.size()];
648     UChar *p = buf;
649     ListIterator it = args.begin();
650     while (it != args.end()) {
651       unsigned short u = it->toUInt16(exec);
652       *p++ = UChar(u);
653       it++;
654     }
655     s = UString(buf, args.size(), false);
656   } else
657     s = "";
658
659   return String(s);
660 }