File indexing completed on 2024-05-12 15:43:35
0001 /* 0002 * This file is part of the KDE libraries 0003 * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) 0004 * Copyright (C) 2004 Apple Computer, Inc. 0005 * 0006 * This library is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU Lesser General Public 0008 * License as published by the Free Software Foundation; either 0009 * version 2 of the License, or (at your option) any later version. 0010 * 0011 * This library is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 * Lesser General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU Lesser General Public 0017 * License along with this library; if not, write to the Free Software 0018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 0019 * 0020 */ 0021 0022 #include "string_object.h" 0023 #include "string_object.lut.h" 0024 0025 #include "error_object.h" 0026 #include "operations.h" 0027 #include "PropertyNameArray.h" 0028 #include "regexp_object.h" 0029 #include "commonunicode.h" 0030 #include <wtf/unicode/libc/UnicodeLibC.h> 0031 0032 #if PLATFORM(WIN_OS) 0033 #include <windows.h> 0034 #endif 0035 0036 using namespace WTF; 0037 0038 namespace KJS 0039 { 0040 0041 // ------------------------------ StringInstance ---------------------------- 0042 0043 const ClassInfo StringInstance::info = {"String", nullptr, nullptr, nullptr}; 0044 0045 StringInstance::StringInstance(JSObject *proto) 0046 : JSWrapperObject(proto), m_conversionsCustomized(false) 0047 { 0048 setInternalValue(jsString("")); 0049 } 0050 0051 StringInstance::StringInstance(JSObject *proto, StringImp *string) 0052 : JSWrapperObject(proto), m_conversionsCustomized(false) 0053 { 0054 setInternalValue(string); 0055 } 0056 0057 StringInstance::StringInstance(JSObject *proto, const UString &string) 0058 : JSWrapperObject(proto), m_conversionsCustomized(false) 0059 { 0060 setInternalValue(jsString(string)); 0061 } 0062 0063 JSValue *StringInstance::lengthGetter(ExecState *, JSObject *, const Identifier &, const PropertySlot &slot) 0064 { 0065 return jsNumber(static_cast<StringInstance *>(slot.slotBase())->internalValue()->value().size()); 0066 } 0067 0068 JSValue *StringInstance::indexGetter(ExecState *, JSObject *, unsigned, const PropertySlot &slot) 0069 { 0070 const UChar c = static_cast<StringInstance *>(slot.slotBase())->internalValue()->value()[slot.index()]; 0071 return jsString(UString(&c, 1)); 0072 } 0073 0074 bool StringInstance::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot) 0075 { 0076 if (propertyName == exec->propertyNames().length) { 0077 slot.setCustom(this, lengthGetter); 0078 return true; 0079 } 0080 0081 bool isStrictUInt32; 0082 unsigned i = propertyName.toStrictUInt32(&isStrictUInt32); 0083 unsigned length = internalValue()->value().size(); 0084 if (isStrictUInt32 && i < length) { 0085 slot.setCustomIndex(this, i, indexGetter); 0086 return true; 0087 } 0088 0089 return JSObject::getOwnPropertySlot(exec, propertyName, slot); 0090 } 0091 0092 bool StringInstance::getOwnPropertySlot(ExecState *exec, unsigned propertyName, PropertySlot &slot) 0093 { 0094 unsigned length = internalValue()->value().size(); 0095 if (propertyName < length) { 0096 slot.setCustomIndex(this, propertyName, indexGetter); 0097 return true; 0098 } 0099 0100 return JSObject::getOwnPropertySlot(exec, Identifier::from(propertyName), slot); 0101 } 0102 0103 bool StringInstance::getOwnPropertyDescriptor(ExecState *exec, const Identifier &propertyName, PropertyDescriptor &desc) 0104 { 0105 if (propertyName == exec->propertyNames().length) { 0106 desc.setPropertyDescriptorValues(exec, jsNumber(internalValue()->value().size()), ReadOnly | DontDelete | DontEnum); 0107 return true; 0108 } 0109 0110 return KJS::JSObject::getOwnPropertyDescriptor(exec, propertyName, desc); 0111 } 0112 0113 void StringInstance::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr) 0114 { 0115 if (propertyName == exec->propertyNames().length) { 0116 return; 0117 } 0118 0119 if (propertyName == exec->propertyNames().valueOf || propertyName == exec->propertyNames().toString) { 0120 m_conversionsCustomized = true; 0121 } 0122 0123 JSObject::put(exec, propertyName, value, attr); 0124 } 0125 0126 bool StringInstance::deleteProperty(ExecState *exec, const Identifier &propertyName) 0127 { 0128 if (propertyName == exec->propertyNames().length) { 0129 return false; 0130 } 0131 return JSObject::deleteProperty(exec, propertyName); 0132 } 0133 0134 void StringInstance::getOwnPropertyNames(ExecState *exec, PropertyNameArray &propertyNames, PropertyMap::PropertyMode mode) 0135 { 0136 int size = internalValue()->getString().size(); 0137 for (int i = 0; i < size; i++) { 0138 propertyNames.add(Identifier(UString::from(i))); 0139 } 0140 0141 if (mode == PropertyMap::IncludeDontEnumProperties) { 0142 propertyNames.add(exec->propertyNames().length); 0143 } 0144 0145 return JSObject::getOwnPropertyNames(exec, propertyNames, mode); 0146 } 0147 0148 UString StringInstance::toString(ExecState *exec) const 0149 { 0150 if (prototype() == originalProto() && !conversionsCustomized() && 0151 !static_cast<StringPrototype *>(prototype())->conversionsCustomized()) { 0152 return internalValue()->value(); 0153 } else { 0154 return JSObject::toString(exec); 0155 } 0156 } 0157 0158 JSObject *StringInstance::valueClone(Interpreter *targetCtx) const 0159 { 0160 return new StringInstance(targetCtx->builtinStringPrototype(), internalValue()); 0161 } 0162 0163 // ------------------------------ StringPrototype --------------------------- 0164 const ClassInfo StringPrototype::info = {"String", &StringInstance::info, &stringTable, nullptr}; 0165 /* Source for string_object.lut.h 0166 @begin stringTable 26 0167 toString StringProtoFunc::ToString DontEnum|Function 0 0168 valueOf StringProtoFunc::ValueOf DontEnum|Function 0 0169 charAt StringProtoFunc::CharAt DontEnum|Function 1 0170 charCodeAt StringProtoFunc::CharCodeAt DontEnum|Function 1 0171 concat StringProtoFunc::Concat DontEnum|Function 1 0172 endsWith StringProtoFunc::EndsWith DontEnum|Function 1 0173 includes StringProtoFunc::Includes DontEnum|Function 1 0174 indexOf StringProtoFunc::IndexOf DontEnum|Function 1 0175 lastIndexOf StringProtoFunc::LastIndexOf DontEnum|Function 1 0176 match StringProtoFunc::Match DontEnum|Function 1 0177 replace StringProtoFunc::Replace DontEnum|Function 2 0178 search StringProtoFunc::Search DontEnum|Function 1 0179 slice StringProtoFunc::Slice DontEnum|Function 2 0180 split StringProtoFunc::Split DontEnum|Function 2 0181 startsWith StringProtoFunc::StartsWith DontEnum|Function 1 0182 substr StringProtoFunc::Substr DontEnum|Function 2 0183 substring StringProtoFunc::Substring DontEnum|Function 2 0184 toLowerCase StringProtoFunc::ToLowerCase DontEnum|Function 0 0185 toUpperCase StringProtoFunc::ToUpperCase DontEnum|Function 0 0186 toLocaleLowerCase StringProtoFunc::ToLocaleLowerCase DontEnum|Function 0 0187 toLocaleUpperCase StringProtoFunc::ToLocaleUpperCase DontEnum|Function 0 0188 trim StringProtoFunc::Trim DontEnum|Function 0 0189 localeCompare StringProtoFunc::LocaleCompare DontEnum|Function 1 0190 repeat StringProtoFunc::Repeat DontEnum|Function 1 0191 0192 # 0193 # Under here: html extension, should only exist if KJS_PURE_ECMA is not defined 0194 # I guess we need to generate two hashtables in the .lut.h file, and use #ifdef 0195 # to select the right one... TODO. ##### 0196 big StringProtoFunc::Big DontEnum|Function 0 0197 small StringProtoFunc::Small DontEnum|Function 0 0198 blink StringProtoFunc::Blink DontEnum|Function 0 0199 bold StringProtoFunc::Bold DontEnum|Function 0 0200 fixed StringProtoFunc::Fixed DontEnum|Function 0 0201 italics StringProtoFunc::Italics DontEnum|Function 0 0202 strike StringProtoFunc::Strike DontEnum|Function 0 0203 sub StringProtoFunc::Sub DontEnum|Function 0 0204 sup StringProtoFunc::Sup DontEnum|Function 0 0205 fontcolor StringProtoFunc::Fontcolor DontEnum|Function 1 0206 fontsize StringProtoFunc::Fontsize DontEnum|Function 1 0207 anchor StringProtoFunc::Anchor DontEnum|Function 1 0208 link StringProtoFunc::Link DontEnum|Function 1 0209 trimLeft StringProtoFunc::TrimLeft DontEnum|Function 0 0210 trimRight StringProtoFunc::TrimRight DontEnum|Function 0 0211 @end 0212 */ 0213 // ECMA 15.5.4 0214 StringPrototype::StringPrototype(ExecState *exec, ObjectPrototype *objProto) 0215 : StringInstance(objProto) 0216 { 0217 // The constructor will be added later, after StringObjectImp has been built 0218 putDirect(exec->propertyNames().length, jsNumber(0), DontDelete | ReadOnly | DontEnum); 0219 } 0220 0221 bool StringPrototype::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot) 0222 { 0223 return getStaticFunctionSlot<StringProtoFunc, StringInstance>(exec, &stringTable, this, propertyName, slot); 0224 } 0225 0226 // ------------------------------ StringProtoFunc --------------------------- 0227 0228 StringProtoFunc::StringProtoFunc(ExecState *exec, int i, int len, const Identifier &name) 0229 : InternalFunctionImp(static_cast<FunctionPrototype *>(exec->lexicalInterpreter()->builtinFunctionPrototype()), name) 0230 , id(i) 0231 { 0232 putDirect(exec->propertyNames().length, len, DontDelete | ReadOnly | DontEnum); 0233 } 0234 0235 static inline void expandSourceRanges(UString::Range *&array, int &count, int &capacity) 0236 { 0237 int newCapacity; 0238 if (capacity == 0) { 0239 newCapacity = 16; 0240 } else { 0241 newCapacity = capacity * 2; 0242 } 0243 0244 UString::Range *newArray = new UString::Range[newCapacity]; 0245 for (int i = 0; i < count; i++) { 0246 newArray[i] = array[i]; 0247 } 0248 0249 delete [] array; 0250 0251 capacity = newCapacity; 0252 array = newArray; 0253 } 0254 0255 static void pushSourceRange(UString::Range *&array, int &count, int &capacity, UString::Range range) 0256 { 0257 if (count + 1 > capacity) { 0258 expandSourceRanges(array, count, capacity); 0259 } 0260 0261 array[count] = range; 0262 count++; 0263 } 0264 0265 static inline void expandReplacements(UString *&array, int &count, int &capacity) 0266 { 0267 int newCapacity; 0268 if (capacity == 0) { 0269 newCapacity = 16; 0270 } else { 0271 newCapacity = capacity * 2; 0272 } 0273 0274 UString *newArray = new UString[newCapacity]; 0275 for (int i = 0; i < count; i++) { 0276 newArray[i] = array[i]; 0277 } 0278 0279 delete [] array; 0280 0281 capacity = newCapacity; 0282 array = newArray; 0283 } 0284 0285 static void pushReplacement(UString *&array, int &count, int &capacity, UString replacement) 0286 { 0287 if (count + 1 > capacity) { 0288 expandReplacements(array, count, capacity); 0289 } 0290 0291 array[count] = replacement; 0292 count++; 0293 } 0294 0295 static inline UString substituteBackreferences(const UString &replacement, const UString &source, int *ovector, RegExp *reg) 0296 { 0297 UString substitutedReplacement = replacement; 0298 0299 int i = -1; 0300 while ((i = substitutedReplacement.find(UString("$"), i + 1)) != -1) { 0301 if (i + 1 == substitutedReplacement.size()) { 0302 break; 0303 } 0304 0305 unsigned short ref = substitutedReplacement[i + 1].unicode(); 0306 int backrefStart = 0; 0307 int backrefLength = 0; 0308 int advance = 0; 0309 0310 if (ref == '$') { // "$$" -> "$" 0311 substitutedReplacement = substitutedReplacement.substr(0, i + 1) + substitutedReplacement.substr(i + 2); 0312 continue; 0313 } else if (ref == '&') { 0314 backrefStart = ovector[0]; 0315 backrefLength = ovector[1] - backrefStart; 0316 } else if (ref == '`') { 0317 backrefStart = 0; 0318 backrefLength = ovector[0]; 0319 } else if (ref == '\'') { 0320 backrefStart = ovector[1]; 0321 backrefLength = source.size() - backrefStart; 0322 } else if (ref >= '0' && ref <= '9') { 0323 // 1- and 2-digit back references are allowed 0324 unsigned backrefIndex = ref - '0'; 0325 if (backrefIndex > (unsigned)reg->subPatterns()) { 0326 continue; 0327 } 0328 if (substitutedReplacement.size() > i + 2) { 0329 ref = substitutedReplacement[i + 2].unicode(); 0330 if (ref >= '0' && ref <= '9') { 0331 backrefIndex = 10 * backrefIndex + ref - '0'; 0332 if (backrefIndex > (unsigned)reg->subPatterns()) { 0333 backrefIndex = backrefIndex / 10; // Fall back to the 1-digit reference 0334 } else { 0335 advance = 1; 0336 } 0337 } 0338 } 0339 backrefStart = ovector[2 * backrefIndex]; 0340 backrefLength = ovector[2 * backrefIndex + 1] - backrefStart; 0341 } else { 0342 continue; 0343 } 0344 0345 substitutedReplacement = substitutedReplacement.substr(0, i) + source.substr(backrefStart, backrefLength) + substitutedReplacement.substr(i + 2 + advance); 0346 i += backrefLength - 1; // - 1 offsets 'i + 1' 0347 } 0348 0349 return substitutedReplacement; 0350 } 0351 0352 static inline int localeCompare(const UString &a, const UString &b) 0353 { 0354 #if PLATFORM(WIN_OS) 0355 int retval = CompareStringW(LOCALE_USER_DEFAULT, 0, 0356 reinterpret_cast<LPCWSTR>(a.data()), a.size(), 0357 reinterpret_cast<LPCWSTR>(b.data()), b.size()); 0358 return !retval ? retval : retval - 2; 0359 #elif PLATFORM(CF) 0360 CFStringRef sa = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar *>(a.data()), a.size(), kCFAllocatorNull); 0361 CFStringRef sb = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar *>(b.data()), b.size(), kCFAllocatorNull); 0362 0363 int retval = CFStringCompare(sa, sb, kCFCompareLocalized); 0364 0365 CFRelease(sa); 0366 CFRelease(sb); 0367 0368 return retval; 0369 #else 0370 // ### use as fallback only. implement locale aware version. 0371 // ### other browsers have more detailed return values than -1, 0 and 1 0372 return compare(a, b); 0373 #endif 0374 } 0375 0376 static JSValue *replace(ExecState *exec, const UString &source, JSValue *pattern, JSValue *replacement) 0377 { 0378 JSObject *replacementFunction = nullptr; 0379 UString replacementString; 0380 0381 if (JSValue::isObject(replacement) && JSValue::toObject(replacement, exec)->implementsCall()) { 0382 replacementFunction = JSValue::toObject(replacement, exec); 0383 } else { 0384 replacementString = JSValue::toString(replacement, exec); 0385 } 0386 0387 if (JSValue::isObject(pattern) && static_cast<JSObject *>(pattern)->inherits(&RegExpImp::info)) { 0388 RegExp *reg = static_cast<RegExpImp *>(pattern)->regExp(); 0389 bool global = reg->flags() & RegExp::Global; 0390 0391 RegExpObjectImp *regExpObj = static_cast<RegExpObjectImp *>(exec->lexicalInterpreter()->builtinRegExp()); 0392 0393 int matchIndex = 0; 0394 int lastIndex = 0; 0395 int startPosition = 0; 0396 0397 UString::Range *sourceRanges = nullptr; 0398 int sourceRangeCount = 0; 0399 int sourceRangeCapacity = 0; 0400 UString *replacements = nullptr; 0401 int replacementCount = 0; 0402 int replacementCapacity = 0; 0403 0404 // This is either a loop (if global is set) or a one-way (if not). 0405 RegExpStringContext ctx(source); 0406 do { 0407 int *ovector; 0408 UString matchString = regExpObj->performMatch(reg, exec, ctx, source, startPosition, &matchIndex, &ovector); 0409 if (matchIndex == -1) { 0410 break; 0411 } 0412 int matchLen = matchString.size(); 0413 0414 pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, matchIndex - lastIndex)); 0415 UString substitutedReplacement; 0416 if (replacementFunction) { 0417 int completeMatchStart = ovector[0]; 0418 List args; 0419 0420 args.append(jsString(matchString)); 0421 0422 for (unsigned i = 0; i < reg->subPatterns(); i++) { 0423 int matchStart = ovector[(i + 1) * 2]; 0424 int matchLen = ovector[(i + 1) * 2 + 1] - matchStart; 0425 0426 args.append(jsString(source.substr(matchStart, matchLen))); 0427 } 0428 0429 args.append(jsNumber(completeMatchStart)); 0430 args.append(jsString(source)); 0431 0432 substitutedReplacement = JSValue::toString(replacementFunction->call(exec, exec->dynamicInterpreter()->globalObject(), args), exec); 0433 } else { 0434 substitutedReplacement = substituteBackreferences(replacementString, source, ovector, reg); 0435 } 0436 pushReplacement(replacements, replacementCount, replacementCapacity, substitutedReplacement); 0437 0438 lastIndex = matchIndex + matchLen; 0439 startPosition = lastIndex; 0440 0441 // special case of empty match 0442 if (matchLen == 0) { 0443 startPosition++; 0444 if (startPosition > source.size()) { 0445 break; 0446 } 0447 } 0448 } while (global); 0449 0450 if (lastIndex < source.size()) { 0451 pushSourceRange(sourceRanges, sourceRangeCount, sourceRangeCapacity, UString::Range(lastIndex, source.size() - lastIndex)); 0452 } 0453 0454 UString result; 0455 if (sourceRanges) { 0456 result = source.spliceSubstringsWithSeparators(sourceRanges, sourceRangeCount, replacements, replacementCount); 0457 } 0458 0459 delete [] sourceRanges; 0460 delete [] replacements; 0461 0462 return jsString(result); 0463 } 0464 0465 // First arg is a string 0466 UString patternString = JSValue::toString(pattern, exec); 0467 int matchPos = source.find(patternString); 0468 int matchLen = patternString.size(); 0469 // Do the replacement 0470 if (matchPos == -1) { 0471 return jsString(source); 0472 } 0473 0474 if (replacementFunction) { 0475 List args; 0476 0477 args.append(jsString(source.substr(matchPos, matchLen))); 0478 args.append(jsNumber(matchPos)); 0479 args.append(jsString(source)); 0480 0481 replacementString = JSValue::toString(replacementFunction->call(exec, exec->dynamicInterpreter()->globalObject(), args), exec); 0482 } 0483 0484 return jsString(source.substr(0, matchPos) + replacementString + source.substr(matchPos + matchLen)); 0485 } 0486 0487 static UnicodeSupport::StringConversionFunction toUpperF = Unicode::toUpper; 0488 static UnicodeSupport::StringConversionFunction toLowerF = Unicode::toLower; 0489 0490 void StringProtoFunc::setToLowerFunction(UnicodeSupport::StringConversionFunction f) 0491 { 0492 toLowerF = f; 0493 } 0494 0495 void StringProtoFunc::setToUpperFunction(UnicodeSupport::StringConversionFunction f) 0496 { 0497 toUpperF = f; 0498 } 0499 0500 // ECMA 15.5.4.2 - 15.5.4.20 0501 JSValue *StringProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args) 0502 { 0503 JSValue *result = nullptr; 0504 0505 // toString and valueOf are no generic function. 0506 if (id == ToString || id == ValueOf) { 0507 if (!thisObj || !thisObj->inherits(&StringInstance::info)) { 0508 return throwError(exec, TypeError); 0509 } 0510 0511 return jsString(static_cast<StringInstance *>(thisObj)->internalValue()->toString(exec)); 0512 } 0513 0514 UString u, u2, u3; 0515 int pos, p0, i; 0516 double dpos; 0517 double d = 0.0; 0518 0519 UString s = thisObj->toString(exec); 0520 0521 int len = s.size(); 0522 JSValue *a0 = args[0]; 0523 JSValue *a1 = args[1]; 0524 0525 switch (id) { 0526 case ToString: 0527 case ValueOf: 0528 // handled above 0529 break; 0530 case CharAt: 0531 // Other browsers treat an omitted parameter as 0 rather than NaN. 0532 // That doesn't match the ECMA standard, but is needed for site compatibility. 0533 dpos = JSValue::isUndefined(a0) ? 0 : JSValue::toInteger(a0, exec); 0534 if (dpos >= 0 && dpos < len) { // false for NaN 0535 u = s.substr(static_cast<int>(dpos), 1); 0536 } else { 0537 u = ""; 0538 } 0539 result = jsString(u); 0540 break; 0541 case CharCodeAt: 0542 // Other browsers treat an omitted parameter as 0 rather than NaN. 0543 // That doesn't match the ECMA standard, but is needed for site compatibility. 0544 dpos = JSValue::isUndefined(a0) ? 0 : JSValue::toInteger(a0, exec); 0545 if (dpos >= 0 && dpos < len) { // false for NaN 0546 result = jsNumber(s[static_cast<int>(dpos)].unicode()); 0547 } else { 0548 result = jsNaN(); 0549 } 0550 break; 0551 case Concat: { 0552 ListIterator it = args.begin(); 0553 for (; it != args.end(); ++it) { 0554 s += JSValue::toString(*it, exec); 0555 } 0556 result = jsString(s); 0557 break; 0558 } 0559 case EndsWith: 0560 if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) { 0561 return throwError(exec, TypeError, "RegExp reserved for future"); 0562 } 0563 u2 = JSValue::toString(a0, exec); 0564 if (JSValue::isUndefined(a1)) 0565 i = s.size(); 0566 else 0567 i = minInt(s.size(), JSValue::toInteger(a1, exec)); 0568 pos = i - u2.size(); 0569 result = jsBoolean(pos >= 0 && s.substr(pos, u2.size()) == u2); 0570 break; 0571 case Includes: 0572 if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) { 0573 return throwError(exec, TypeError, "RegExp reserved for future"); 0574 } 0575 u2 = JSValue::toString(a0, exec); 0576 pos = JSValue::toInteger(a1, exec); 0577 i = s.find(u2, pos); 0578 result = jsBoolean(i >= 0); 0579 break; 0580 case IndexOf: 0581 u2 = JSValue::toString(a0, exec); 0582 if (JSValue::isUndefined(a1)) { 0583 dpos = 0; 0584 } else { 0585 dpos = JSValue::toInteger(a1, exec); 0586 if (dpos >= 0) { // false for NaN 0587 if (dpos > len) { 0588 dpos = len; 0589 } 0590 } else { 0591 dpos = 0; 0592 } 0593 } 0594 result = jsNumber(s.find(u2, static_cast<int>(dpos))); 0595 break; 0596 case LastIndexOf: 0597 u2 = JSValue::toString(a0, exec); 0598 d = JSValue::toNumber(a1, exec); 0599 if (JSValue::isUndefined(a1) || KJS::isNaN(d)) { 0600 dpos = len; 0601 } else { 0602 dpos = JSValue::toInteger(a1, exec); 0603 if (dpos >= 0) { // false for NaN 0604 if (dpos > len) { 0605 dpos = len; 0606 } 0607 } else { 0608 dpos = 0; 0609 } 0610 } 0611 result = jsNumber(s.rfind(u2, static_cast<int>(dpos))); 0612 break; 0613 case Match: 0614 case Search: { 0615 u = s; 0616 RegExp *reg, *tmpReg = nullptr; 0617 RegExpImp *imp = nullptr; 0618 if (JSValue::isObject(a0) && static_cast<JSObject *>(a0)->inherits(&RegExpImp::info)) { 0619 reg = static_cast<RegExpImp *>(a0)->regExp(); 0620 } else { 0621 /* 0622 * ECMA 15.5.4.12 String.prototype.search (regexp) 0623 * If regexp is not an object whose [[Class]] property is "RegExp", it is 0624 * replaced with the result of the expression new RegExp(regexp). 0625 */ 0626 reg = tmpReg = new RegExp(JSValue::toString(a0, exec), RegExp::None); 0627 } 0628 if (!reg->isValid()) { 0629 delete tmpReg; 0630 return throwError(exec, SyntaxError, "Invalid regular expression"); 0631 } 0632 RegExpObjectImp *regExpObj = static_cast<RegExpObjectImp *>(exec->lexicalInterpreter()->builtinRegExp()); 0633 0634 RegExpStringContext ctx(u); 0635 UString mstr = regExpObj->performMatch(reg, exec, ctx, u, 0, &pos); 0636 0637 if (id == Search) { 0638 result = jsNumber(pos); 0639 } else { 0640 // Exec 0641 if ((reg->flags() & RegExp::Global) == 0) { 0642 // case without 'g' flag is handled like RegExp.prototype.exec 0643 if (mstr.isNull()) { 0644 result = jsNull(); 0645 } else { 0646 result = regExpObj->arrayOfMatches(exec, mstr); 0647 } 0648 } else { 0649 // return array of matches 0650 List list; 0651 int lastIndex = 0; 0652 while (pos >= 0) { 0653 if (mstr.isNull()) { 0654 list.append(jsUndefined()); 0655 } else { 0656 list.append(jsString(mstr)); 0657 } 0658 lastIndex = pos; 0659 pos += mstr.isEmpty() ? 1 : mstr.size(); 0660 mstr = regExpObj->performMatch(reg, exec, ctx, u, pos, &pos); 0661 } 0662 if (imp) { 0663 imp->put(exec, "lastIndex", jsNumber(lastIndex), DontDelete | DontEnum); 0664 } 0665 if (list.isEmpty()) { 0666 // if there are no matches at all, it's important to return 0667 // Null instead of an empty array, because this matches 0668 // other browsers and because Null is a false value. 0669 result = jsNull(); 0670 } else { 0671 result = exec->lexicalInterpreter()->builtinArray()->construct(exec, list); 0672 } 0673 } 0674 } 0675 0676 delete tmpReg; 0677 break; 0678 } 0679 case Replace: 0680 result = replace(exec, s, a0, a1); 0681 break; 0682 case Slice: { 0683 // The arg processing is very much like ArrayProtoFunc::Slice 0684 double start = JSValue::toInteger(a0, exec); 0685 double end = JSValue::isUndefined(a1) ? len : JSValue::toInteger(a1, exec); 0686 double from = start < 0 ? len + start : start; 0687 double to = end < 0 ? len + end : end; 0688 if (to > from && to > 0 && from < len) { 0689 if (from < 0) { 0690 from = 0; 0691 } 0692 if (to > len) { 0693 to = len; 0694 } 0695 result = jsString(s.substr(static_cast<int>(from), static_cast<int>(to - from))); 0696 } else { 0697 result = jsString(""); 0698 } 0699 break; 0700 } 0701 case Split: { 0702 JSObject *constructor = exec->lexicalInterpreter()->builtinArray(); 0703 JSObject *res = static_cast<JSObject *>(constructor->construct(exec, List::empty())); 0704 result = res; 0705 u = s; 0706 i = p0 = 0; 0707 uint32_t limit = JSValue::isUndefined(a1) ? 0xFFFFFFFFU : JSValue::toUInt32(a1, exec); 0708 if (JSValue::isObject(a0) && static_cast<JSObject *>(a0)->inherits(&RegExpImp::info)) { 0709 RegExp *reg = static_cast<RegExpImp *>(a0)->regExp(); 0710 0711 RegExpStringContext ctx(u); 0712 bool error = false; 0713 if (u.isEmpty() && !reg->match(ctx, u, &error, 0).isNull()) { 0714 0715 // empty string matched by regexp -> empty array 0716 res->put(exec, exec->propertyNames().length, jsNumber(0)); 0717 break; 0718 } 0719 pos = 0; 0720 while (!error && static_cast<uint32_t>(i) != limit && pos < u.size()) { 0721 // TODO: back references 0722 int mpos; 0723 int *ovector = nullptr; 0724 UString mstr = reg->match(ctx, u, &error, pos, &mpos, &ovector); 0725 delete [] ovector; ovector = nullptr; 0726 if (mpos < 0) { 0727 break; 0728 } 0729 pos = mpos + (mstr.isEmpty() ? 1 : mstr.size()); 0730 if (mpos != p0 || !mstr.isEmpty()) { 0731 res->put(exec, i, jsString(u.substr(p0, mpos - p0))); 0732 p0 = mpos + mstr.size(); 0733 i++; 0734 } 0735 } 0736 0737 if (error) { 0738 RegExpObjectImp::throwRegExpError(exec); 0739 } 0740 0741 } else { 0742 u2 = JSValue::toString(a0, exec); 0743 if (u2.isEmpty()) { 0744 if (u.isEmpty()) { 0745 // empty separator matches empty string -> empty array 0746 put(exec, exec->propertyNames().length, jsNumber(0)); 0747 break; 0748 } else { 0749 while (static_cast<uint32_t>(i) != limit && i < u.size() - 1) { 0750 res->put(exec, i++, jsString(u.substr(p0++, 1))); 0751 } 0752 } 0753 } else { 0754 while (static_cast<uint32_t>(i) != limit && (pos = u.find(u2, p0)) >= 0) { 0755 res->put(exec, i, jsString(u.substr(p0, pos - p0))); 0756 p0 = pos + u2.size(); 0757 i++; 0758 } 0759 } 0760 } 0761 // add remaining string, if any 0762 if (static_cast<uint32_t>(i) != limit) { 0763 res->put(exec, i++, jsString(u.substr(p0))); 0764 } 0765 res->put(exec, exec->propertyNames().length, jsNumber(i)); 0766 } 0767 break; 0768 case StartsWith: 0769 if (JSValue::isObject(a0) && static_cast<JSObject*>(a0)->inherits(&RegExpImp::info)) { 0770 return throwError(exec, TypeError, "RegExp reserved for future"); 0771 } 0772 u2 = JSValue::toString(a0, exec); 0773 pos = maxInt(0, JSValue::toInteger(a1, exec)); 0774 result = jsBoolean(s.substr(pos, u2.size()) == u2); 0775 break; 0776 case Substr: { //B.2.3 0777 // Note: NaN is effectively handled as 0 here for both length 0778 // and start, hence toInteger does fine, and removes worries 0779 // about weird comparison results below. 0780 int len = s.size(); 0781 double start = JSValue::toInteger(a0, exec); 0782 double length = JSValue::isUndefined(a1) ? len : JSValue::toInteger(a1, exec); 0783 0784 if (start >= len) { 0785 return jsString(""); 0786 } 0787 if (length < 0) { 0788 return jsString(""); 0789 } 0790 if (start < 0) { 0791 start += s.size(); 0792 if (start < 0) { 0793 start = 0; 0794 } 0795 } 0796 0797 if (length > len - start) { 0798 length = len - start; 0799 } 0800 0801 result = jsString(s.substr(static_cast<int>(start), static_cast<int>(length))); 0802 break; 0803 } 0804 case Substring: { 0805 double start = JSValue::toNumber(a0, exec); 0806 double end = JSValue::toNumber(a1, exec); 0807 if (isNaN(start)) { 0808 start = 0; 0809 } 0810 if (isNaN(end)) { 0811 end = 0; 0812 } 0813 if (start < 0) { 0814 start = 0; 0815 } 0816 if (end < 0) { 0817 end = 0; 0818 } 0819 if (start > len) { 0820 start = len; 0821 } 0822 if (end > len) { 0823 end = len; 0824 } 0825 if (JSValue::isUndefined(a1)) { 0826 end = len; 0827 } 0828 if (start > end) { 0829 double temp = end; 0830 end = start; 0831 start = temp; 0832 } 0833 result = jsString(s.substr((int)start, (int)end - (int)start)); 0834 } 0835 break; 0836 case ToLowerCase: 0837 case ToLocaleLowerCase: { // FIXME: See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt for locale-sensitive mappings that aren't implemented. 0838 u = s; 0839 u.copyForWriting(); 0840 uint16_t *dataPtr = reinterpret_cast<uint16_t *>(u.rep()->data()); 0841 uint16_t *destIfNeeded; 0842 0843 int len = toLowerF(dataPtr, u.size(), destIfNeeded); 0844 if (len >= 0) { 0845 result = jsString(UString(reinterpret_cast<UChar *>(destIfNeeded ? destIfNeeded : dataPtr), len)); 0846 } else { 0847 result = jsString(s); 0848 } 0849 0850 free(destIfNeeded); 0851 break; 0852 } 0853 case ToUpperCase: 0854 case ToLocaleUpperCase: { // FIXME: See http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt for locale-sensitive mappings that aren't implemented. 0855 u = s; 0856 u.copyForWriting(); 0857 uint16_t *dataPtr = reinterpret_cast<uint16_t *>(u.rep()->data()); 0858 uint16_t *destIfNeeded; 0859 0860 int len = toUpperF(dataPtr, u.size(), destIfNeeded); 0861 if (len >= 0) { 0862 result = jsString(UString(reinterpret_cast<UChar *>(destIfNeeded ? destIfNeeded : dataPtr), len)); 0863 } else { 0864 result = jsString(s); 0865 } 0866 0867 free(destIfNeeded); 0868 break; 0869 } 0870 case LocaleCompare: 0871 if (args.size() < 1) { 0872 return jsNumber(0); 0873 } 0874 return jsNumber(localeCompare(s, JSValue::toString(a0, exec))); 0875 case Repeat: { 0876 double n = JSValue::toInteger(args[0], exec); 0877 if (exec->hadException()) 0878 return jsUndefined(); 0879 if (n < 0 || KJS::isPosInf(n)) 0880 return throwError(exec, RangeError, "Invalid repeat count"); 0881 0882 UString ret; 0883 for (int i = 0; i < n; ++i) 0884 { 0885 ret += s; 0886 } 0887 return jsString(ret); 0888 } 0889 case Trim: 0890 case TrimRight: 0891 case TrimLeft: { 0892 const uint16_t *dataPtr = reinterpret_cast<uint16_t *>(s.rep()->data()); 0893 0894 const int size = s.size(); 0895 int left = 0; 0896 if (id != TrimRight) 0897 while (left < size && CommonUnicode::isStrWhiteSpace(dataPtr[left])) { 0898 left++; 0899 } 0900 0901 int right = size; 0902 if (id != TrimLeft) 0903 while (right > left && CommonUnicode::isStrWhiteSpace(dataPtr[right - 1])) { 0904 right--; 0905 } 0906 0907 result = jsString(s.substr(left, right - left)); 0908 break; 0909 } 0910 #ifndef KJS_PURE_ECMA 0911 case Big: 0912 result = jsString("<big>" + s + "</big>"); 0913 break; 0914 case Small: 0915 result = jsString("<small>" + s + "</small>"); 0916 break; 0917 case Blink: 0918 result = jsString("<blink>" + s + "</blink>"); 0919 break; 0920 case Bold: 0921 result = jsString("<b>" + s + "</b>"); 0922 break; 0923 case Fixed: 0924 result = jsString("<tt>" + s + "</tt>"); 0925 break; 0926 case Italics: 0927 result = jsString("<i>" + s + "</i>"); 0928 break; 0929 case Strike: 0930 result = jsString("<strike>" + s + "</strike>"); 0931 break; 0932 case Sub: 0933 result = jsString("<sub>" + s + "</sub>"); 0934 break; 0935 case Sup: 0936 result = jsString("<sup>" + s + "</sup>"); 0937 break; 0938 case Fontcolor: 0939 result = jsString("<font color=\"" + JSValue::toString(a0, exec) + "\">" + s + "</font>"); 0940 break; 0941 case Fontsize: 0942 result = jsString("<font size=\"" + JSValue::toString(a0, exec) + "\">" + s + "</font>"); 0943 break; 0944 case Anchor: 0945 result = jsString("<a name=\"" + JSValue::toString(a0, exec) + "\">" + s + "</a>"); 0946 break; 0947 case Link: 0948 result = jsString("<a href=\"" + JSValue::toString(a0, exec) + "\">" + s + "</a>"); 0949 break; 0950 #endif 0951 } 0952 0953 return result; 0954 } 0955 0956 // ------------------------------ StringObjectImp ------------------------------ 0957 0958 StringObjectImp::StringObjectImp(ExecState *exec, 0959 FunctionPrototype *funcProto, 0960 StringPrototype *stringProto) 0961 : InternalFunctionImp(funcProto) 0962 { 0963 // ECMA 15.5.3.1 String.prototype 0964 putDirect(exec->propertyNames().prototype, stringProto, DontEnum | DontDelete | ReadOnly); 0965 0966 putDirectFunction(new StringObjectFuncImp(exec, funcProto, exec->propertyNames().fromCharCode), DontEnum); 0967 0968 // no. of arguments for constructor 0969 putDirect(exec->propertyNames().length, jsNumber(1), ReadOnly | DontDelete | DontEnum); 0970 } 0971 0972 bool StringObjectImp::implementsConstruct() const 0973 { 0974 return true; 0975 } 0976 0977 // ECMA 15.5.2 0978 JSObject *StringObjectImp::construct(ExecState *exec, const List &args) 0979 { 0980 JSObject *proto = exec->lexicalInterpreter()->builtinStringPrototype(); 0981 if (args.size() == 0) { 0982 return new StringInstance(proto); 0983 } 0984 return new StringInstance(proto, JSValue::toString(*args.begin(), exec)); 0985 } 0986 0987 // ECMA 15.5.1 0988 JSValue *StringObjectImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args) 0989 { 0990 if (args.isEmpty()) { 0991 return jsString(""); 0992 } else { 0993 JSValue *v = args[0]; 0994 return jsString(JSValue::toString(v, exec)); 0995 } 0996 } 0997 0998 // ------------------------------ StringObjectFuncImp -------------------------- 0999 1000 // ECMA 15.5.3.2 fromCharCode() 1001 StringObjectFuncImp::StringObjectFuncImp(ExecState *exec, FunctionPrototype *funcProto, const Identifier &name) 1002 : InternalFunctionImp(funcProto, name) 1003 { 1004 putDirect(exec->propertyNames().length, jsNumber(1), DontDelete | ReadOnly | DontEnum); 1005 } 1006 1007 JSValue *StringObjectFuncImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args) 1008 { 1009 UString s; 1010 if (args.size()) { 1011 UChar *buf = static_cast<UChar *>(fastMalloc(args.size() * sizeof(UChar))); 1012 UChar *p = buf; 1013 ListIterator it = args.begin(); 1014 while (it != args.end()) { 1015 unsigned short u = JSValue::toUInt16(*it, exec); 1016 *p++ = UChar(u); 1017 it++; 1018 } 1019 s = UString(buf, args.size(), false); 1020 } else { 1021 s = ""; 1022 } 1023 1024 return jsString(s); 1025 } 1026 1027 }