181ReturnedValue
StringCtor::method_fromCodePoint(
const FunctionObject *f,
const Value *,
const Value *argv,
int argc)
183 ExecutionEngine *e = f->engine();
184 QString result(argc*2, Qt::Uninitialized);
185 QChar *ch = result.data();
186 for (
int i = 0; i < argc; ++i) {
187 double num = argv[i].toNumber();
189 return Encode::undefined();
190 int cp =
static_cast<
int>(num);
191 if (cp != num || cp < 0 || cp > 0x10ffff)
192 return e->throwRangeError(QStringLiteral(
"String.fromCodePoint: argument out of range."));
194 *ch = QChar::highSurrogate(cp);
196 *ch = QChar::lowSurrogate(cp);
202 result.truncate(ch - result.constData());
203 return e->newString(result)->asReturnedValue();
206ReturnedValue
StringCtor::method_raw(
const FunctionObject *f,
const Value *,
const Value *argv,
int argc)
210 return scope.engine->throwTypeError();
212 ScopedObject cooked(scope, argv[0].toObject(scope.engine));
214 return scope.engine->throwTypeError();
215 ScopedString rawString(scope, scope.engine->newIdentifier(QStringLiteral(
"raw")));
216 ScopedValue rawValue(scope, cooked->get(rawString));
217 ScopedObject raw(scope, rawValue->toObject(scope.engine));
218 if (scope.hasException())
219 return Encode::undefined();
225 uint literalSegments = raw->getLength();
226 if (!literalSegments)
227 return scope.engine->id_empty()->asReturnedValue();
232 val = raw->get(nextIndex);
233 result += val
->toQString();
234 if (scope.hasException())
235 return Encode::undefined();
236 if (nextIndex + 1 == literalSegments)
237 return scope.engine->newString(result)->asReturnedValue();
239 if (nextIndex <
static_cast<uint>(argc))
240 result += argv[nextIndex].toQString();
241 if (scope.hasException())
242 return Encode::undefined();
253 Heap::
InternalClass *ic = scope.engine->classes[ExecutionEngine::Class_StringObject]->changePrototype(scope.engine->objectPrototype()->d());
254 d()->internalClass.set(scope.engine, ic);
255 d()->string.set(scope.engine, scope.engine->id_empty()->d());
256 setProperty(scope.engine, Heap::StringObject::LengthPropertyIndex, Value::fromInt32(0));
258 ctor->defineReadonlyProperty(engine->id_prototype(), (o =
this));
259 ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1));
260 ctor->defineDefaultProperty(QStringLiteral(
"fromCharCode"), StringCtor::method_fromCharCode, 1);
261 ctor->defineDefaultProperty(QStringLiteral(
"fromCodePoint"), StringCtor::method_fromCodePoint, 1);
262 ctor->defineDefaultProperty(QStringLiteral(
"raw"), StringCtor::method_raw, 1);
264 defineDefaultProperty(QStringLiteral(
"constructor"), (o = ctor));
265 defineDefaultProperty(engine->id_toString(), method_toString);
266 defineDefaultProperty(engine->id_valueOf(), method_toString);
267 defineDefaultProperty(QStringLiteral(
"charAt"), method_charAt, 1);
268 defineDefaultProperty(QStringLiteral(
"charCodeAt"), method_charCodeAt, 1);
269 defineDefaultProperty(QStringLiteral(
"codePointAt"), method_codePointAt, 1);
270 defineDefaultProperty(QStringLiteral(
"concat"), method_concat, 1);
271 defineDefaultProperty(QStringLiteral(
"endsWith"), method_endsWith, 1);
272 defineDefaultProperty(QStringLiteral(
"indexOf"), method_indexOf, 1);
273 defineDefaultProperty(QStringLiteral(
"includes"), method_includes, 1);
274 defineDefaultProperty(QStringLiteral(
"lastIndexOf"), method_lastIndexOf, 1);
275 defineDefaultProperty(QStringLiteral(
"localeCompare"), method_localeCompare, 1);
276 defineDefaultProperty(QStringLiteral(
"match"), method_match, 1);
277 defineDefaultProperty(QStringLiteral(
"normalize"), method_normalize, 0);
278 defineDefaultProperty(QStringLiteral(
"padEnd"), method_padEnd, 1);
279 defineDefaultProperty(QStringLiteral(
"padStart"), method_padStart, 1);
280 defineDefaultProperty(QStringLiteral(
"repeat"), method_repeat, 1);
281 defineDefaultProperty(QStringLiteral(
"replace"), method_replace, 2);
282 defineDefaultProperty(QStringLiteral(
"search"), method_search, 1);
283 defineDefaultProperty(QStringLiteral(
"slice"), method_slice, 2);
284 defineDefaultProperty(QStringLiteral(
"split"), method_split, 2);
285 defineDefaultProperty(QStringLiteral(
"startsWith"), method_startsWith, 1);
286 defineDefaultProperty(QStringLiteral(
"substr"), method_substr, 2);
287 defineDefaultProperty(QStringLiteral(
"substring"), method_substring, 2);
288 defineDefaultProperty(QStringLiteral(
"toLowerCase"), method_toLowerCase);
289 defineDefaultProperty(QStringLiteral(
"toLocaleLowerCase"), method_toLocaleLowerCase);
290 defineDefaultProperty(QStringLiteral(
"toUpperCase"), method_toUpperCase);
291 defineDefaultProperty(QStringLiteral(
"toLocaleUpperCase"), method_toLocaleUpperCase);
292 defineDefaultProperty(QStringLiteral(
"trim"), method_trim);
293 defineDefaultProperty(engine->symbol_iterator(), method_iterator);
366ReturnedValue
StringPrototype::method_codePointAt(
const FunctionObject *f,
const Value *thisObject,
const Value *argv,
int argc)
368 ExecutionEngine *v4 = f->engine();
369 QString value = getThisString(v4, thisObject);
370 if (v4->hasException)
371 return QV4::Encode::undefined();
373 double index = argc ? argv[0].toInteger() : 0.0;
374 if (v4->hasException)
375 return QV4::Encode::undefined();
377 if (index < 0 || index >= value.size())
378 return Encode::undefined();
380 uint first = value.at(index).unicode();
381 if (QChar::isHighSurrogate(first) && index + 1 < value.size()) {
382 uint second = value.at(index + 1).unicode();
383 if (QChar::isLowSurrogate(second))
384 return Encode(QChar::surrogateToUcs4(first, second));
386 return Encode(first);
456ReturnedValue
StringPrototype::method_includes(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
458 ExecutionEngine *v4 = b->engine();
459 const QString value = getThisString(v4, thisObject);
460 if (v4->hasException)
461 return QV4::Encode::undefined();
463 if (argc && argv[0].as<RegExpObject>())
464 return v4->throwTypeError();
465 QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
466 if (v4->hasException)
467 return Encode::undefined();
471 const Value &posArg = argv[1];
472 pos = posArg.toInteger();
473 if (!posArg.isInteger() && posArg.isNumber() && qIsInf(posArg.toNumber()))
480 QStringView stringToSearch = QStringView{value}.mid(pos);
481 return Encode(stringToSearch.contains(searchString));
484ReturnedValue
StringPrototype::method_lastIndexOf(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
486 ExecutionEngine *v4 = b->engine();
487 const QString value = getThisString(v4, thisObject);
488 if (v4->hasException)
489 return QV4::Encode::undefined();
491 QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
492 if (v4->hasException)
493 return Encode::undefined();
495 double position = argc > 1 ? RuntimeHelpers::toNumber(argv[1]) : +qInf();
496 if (
std::isnan(position))
499 position =
std::trunc(position);
501 int pos =
std::trunc(qMin(qMax(position, 0.0),
double(value.size())));
502 if (!searchString.isEmpty() && pos == value.size())
504 if (searchString.isNull() && pos == 0)
506 int index = value.lastIndexOf(searchString, pos);
507 return Encode(index);
521ReturnedValue
StringPrototype::method_match(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
523 ExecutionEngine *v4 = b->engine();
524 if (thisObject->isNullOrUndefined())
525 return v4->throwTypeError();
528 if (argc && !argv[0].isNullOrUndefined()) {
530 if (scope.hasException())
531 return Encode::undefined();
532 ScopedValue f(scope, r->get(scope.engine->symbol_match()));
533 if (!f
->isNullOrUndefined()) {
536 return scope.engine->throwTypeError();
537 return checkedResult(scope.engine, fo->call(r, thisObject, 1));
542 if (v4->hasException)
543 return Encode::undefined();
545 Scoped<RegExpObject> that(scope, argc ? argv[0] : Value::undefinedValue());
548 that = RegExpCtor::virtualCallAsConstructor(b, argv, argc, b);
549 if (v4->hasException)
550 return Encode::undefined();
556 return scope.engine->throwTypeError();
557 return checkedResult(scope.engine, match->call(that, s, 1));
560ReturnedValue
StringPrototype::method_normalize(
const FunctionObject *f,
const Value *thisObject,
const Value *argv,
int argc)
562 ExecutionEngine *v4 = f->engine();
563 const QString value = getThisString(v4, thisObject);
564 if (v4->hasException)
565 return Encode::undefined();
567 QString::NormalizationForm form = QString::NormalizationForm_C;
568 if (argc >= 1 && !argv[0].isUndefined()) {
569 QString f = argv[0].toQString();
570 if (v4->hasException)
571 return Encode::undefined();
572 if (f == QLatin1String(
"NFC"))
573 form = QString::NormalizationForm_C;
574 else if (f == QLatin1String(
"NFD"))
575 form = QString::NormalizationForm_D;
576 else if (f == QLatin1String(
"NFKC"))
577 form = QString::NormalizationForm_KC;
578 else if (f == QLatin1String(
"NFKD"))
579 form = QString::NormalizationForm_KD;
581 return v4->throwRangeError(QLatin1String(
"String.prototype.normalize: Invalid normalization form."));
583 QString normalized = value.normalized(form);
584 return v4->newString(normalized)->asReturnedValue();
587ReturnedValue
StringPrototype::method_padEnd(
const FunctionObject *f,
const Value *thisObject,
const Value *argv,
int argc)
589 ExecutionEngine *v4 = f->engine();
590 if (thisObject->isNullOrUndefined())
591 return v4->throwTypeError();
595 if (v4->hasException)
596 return Encode::undefined();
598 return s->asReturnedValue();
600 double maxLen = argv[0].toInteger();
601 if (maxLen <= s->d()->length())
602 return s->asReturnedValue();
603 QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : u" "_s;
604 if (v4->hasException)
605 return Encode::undefined();
607 if (fillString.isEmpty())
608 return s->asReturnedValue();
610 QString padded = s->toQString();
611 int oldLength = padded.size();
612 int toFill = maxLen - oldLength;
613 padded.resize(maxLen);
614 QChar *ch = padded.data() + oldLength;
616 int copy = qMin(fillString.size(), toFill);
617 memcpy(ch, fillString.constData(), copy*
sizeof(QChar));
623 return v4->newString(padded)->asReturnedValue();
626ReturnedValue
StringPrototype::method_padStart(
const FunctionObject *f,
const Value *thisObject,
const Value *argv,
int argc)
628 ExecutionEngine *v4 = f->engine();
629 if (thisObject->isNullOrUndefined())
630 return v4->throwTypeError();
634 if (v4->hasException)
635 return Encode::undefined();
637 return s->asReturnedValue();
639 double maxLen = argv[0].toInteger();
640 if (maxLen <= s->d()->length())
641 return s->asReturnedValue();
642 QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : u" "_s;
643 if (v4->hasException)
644 return Encode::undefined();
646 if (fillString.isEmpty())
647 return s->asReturnedValue();
649 QString original = s->toQString();
650 int oldLength = original.size();
651 int toFill = maxLen - oldLength;
653 padded.resize(maxLen);
654 QChar *ch = padded.data();
656 int copy = qMin(fillString.size(), toFill);
657 memcpy(ch, fillString.constData(), copy*
sizeof(QChar));
661 memcpy(ch, original.constData(), oldLength*
sizeof(QChar));
665 return v4->newString(padded)->asReturnedValue();
684static void appendReplacementString(QString *result,
const QString &input,
const QString& replaceValue, uint* matchOffsets,
int captureCount)
686 result->reserve(result->size() + replaceValue.size());
687 for (
int i = 0; i < replaceValue.size(); ++i) {
688 if (replaceValue.at(i) == QLatin1Char(
'$') && i < replaceValue.size() - 1) {
689 ushort ch = replaceValue.at(i + 1).unicode();
690 uint substStart = JSC::Yarr::offsetNoMatch;
691 uint substEnd = JSC::Yarr::offsetNoMatch;
694 *result += QChar(ch);
697 }
else if (ch ==
'&') {
698 substStart = matchOffsets[0];
699 substEnd = matchOffsets[1];
701 }
else if (ch ==
'`') {
703 substEnd = matchOffsets[0];
705 }
else if (ch ==
'\'') {
706 substStart = matchOffsets[1];
707 substEnd = input.size();
709 }
else if (ch >=
'0' && ch <=
'9') {
710 uint capture = ch -
'0';
712 if (i < replaceValue.size() - 2) {
713 ch = replaceValue.at(i + 2).unicode();
714 if (ch >=
'0' && ch <=
'9') {
715 uint c = capture*10 + ch -
'0';
716 if (c <
static_cast<uint>(captureCount)) {
722 if (capture > 0 && capture <
static_cast<uint>(captureCount)) {
723 substStart = matchOffsets[capture * 2];
724 substEnd = matchOffsets[capture * 2 + 1];
730 if (substStart != JSC::Yarr::offsetNoMatch && substEnd != JSC::Yarr::offsetNoMatch)
731 *result += QStringView{input}.mid(substStart, substEnd - substStart);
733 *result += replaceValue.at(i);
735 *result += replaceValue.at(i);
740ReturnedValue
StringPrototype::method_replace(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
744 string = thisString->d()->string->toQString();
746 string = thisObject->toQString();
749 int numStringMatches = 0;
751 uint allocatedMatchOffsets = 64;
752 uint _matchOffsets[64];
753 uint *matchOffsets = _matchOffsets;
756 ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue());
757 Scoped<RegExpObject> regExp(scope, searchValue);
760 uint nMatchOffsets = 0;
765 int oldSize = nMatchOffsets;
766 if (allocatedMatchOffsets < nMatchOffsets + re->captureCount() * 2) {
767 allocatedMatchOffsets = qMax(allocatedMatchOffsets * 2, nMatchOffsets + re->captureCount() * 2);
768 uint *newOffsets = (uint *)malloc(allocatedMatchOffsets*
sizeof(uint));
769 memcpy(newOffsets, matchOffsets, nMatchOffsets*
sizeof(uint));
770 if (matchOffsets != _matchOffsets)
772 matchOffsets = newOffsets;
774 if (re->match(string, offset, matchOffsets + oldSize) == JSC::Yarr::offsetNoMatch) {
775 nMatchOffsets = oldSize;
778 nMatchOffsets += re->captureCount() * 2;
779 if (!regExp->global())
782 const uint matchBegin = matchOffsets[oldSize];
783 const uint matchEnd = matchOffsets[oldSize + 1];
786 const uint matchOffset = (matchBegin == matchEnd) ? matchEnd + 1 : matchEnd;
788 offset =
std::max(offset + 1, matchOffset);
790 if (regExp->global()) {
791 regExp->setLastIndex(0);
792 if (scope.hasException())
793 return Encode::undefined();
795 numStringMatches = nMatchOffsets / (regExp->value()->captureCount() * 2);
796 numCaptures = regExp->value()->captureCount();
799 QString searchString = searchValue->toQString();
800 int idx = string.indexOf(searchString);
802 numStringMatches = 1;
803 matchOffsets[0] = idx;
804 matchOffsets[1] = idx + searchString.size();
810 ScopedValue replaceValue(scope, argc > 1 ? argv[1] : Value::undefinedValue());
812 if (!!searchCallback) {
813 result.reserve(string.size() + 10*numStringMatches);
815 Value *arguments = scope.alloc(numCaptures + 2);
817 for (
int i = 0; i < numStringMatches; ++i) {
818 for (
int k = 0; k < numCaptures; ++k) {
819 int idx = (i * numCaptures + k) * 2;
820 uint start = matchOffsets[idx];
821 uint end = matchOffsets[idx + 1];
822 entry = Value::undefinedValue();
823 if (start != JSC::Yarr::offsetNoMatch && end != JSC::Yarr::offsetNoMatch)
824 entry = scope.engine->newString(string.mid(start, end - start));
825 arguments[k] = entry;
827 uint matchStart = matchOffsets[i * numCaptures * 2];
828 Q_ASSERT(matchStart >=
static_cast<uint>(lastEnd));
829 uint matchEnd = matchOffsets[i * numCaptures * 2 + 1];
830 arguments[numCaptures] = Value::fromUInt32(matchStart);
831 arguments[numCaptures + 1] = scope.engine->newString(string);
833 Value that = Value::undefinedValue();
834 replacement = searchCallback->call(&that, arguments, numCaptures + 2);
836 result += QStringView{string}.mid(lastEnd, matchStart - lastEnd);
837 result += replacement
->toQString();
840 result += QStringView{string}.mid(lastEnd);
842 QString newString = replaceValue->toQString();
843 result.reserve(string.size() + numStringMatches*newString.size());
846 for (
int i = 0; i < numStringMatches; ++i) {
847 int baseIndex = i * numCaptures * 2;
848 uint matchStart = matchOffsets[baseIndex];
849 uint matchEnd = matchOffsets[baseIndex + 1];
850 if (matchStart == JSC::Yarr::offsetNoMatch)
853 result += QStringView{string}.mid(lastEnd, matchStart - lastEnd);
854 appendReplacementString(&result, string, newString, matchOffsets + baseIndex, numCaptures);
857 result += QStringView{string}.mid(lastEnd);
860 if (matchOffsets != _matchOffsets)
863 return Encode(scope.engine->newString(result));
866ReturnedValue
StringPrototype::method_search(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
869 QString string = getThisString(scope.engine, thisObject);
870 if (scope.hasException())
871 return QV4::Encode::undefined();
873 Scoped<RegExpObject> regExp(scope, argc ? argv[0] : Value::undefinedValue());
875 regExp = scope.engine->regExpCtor()->callAsConstructor(argv, 1);
876 if (scope.hasException())
877 return QV4::Encode::undefined();
882 Q_ALLOCA_VAR(uint, matchOffsets, regExp->value()->captureCount() * 2 *
sizeof(uint));
883 uint result = re->match(string, 0, matchOffsets);
884 if (result == JSC::Yarr::offsetNoMatch)
887 return Encode(result);
890ReturnedValue
StringPrototype::method_slice(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
892 ExecutionEngine *v4 = b->engine();
895 if (v4->hasException)
896 return QV4::Encode::undefined();
899 const double length = s->d()->length();
901 double start = argc ? argv[0].toInteger() : 0;
902 double end = (argc < 2 || argv[1].isUndefined())
903 ? length : argv[1].toInteger();
906 start = qMax(length + start, 0.);
908 start = qMin(start, length);
911 end = qMax(length + end, 0.);
913 end = qMin(end, length);
915 const int intStart =
int(start);
916 const int intEnd =
int(end);
918 int count = qMax(0, intEnd - intStart);
919 return Encode(v4->memoryManager->alloc<
ComplexString>(s->d(), intStart, count));
922ReturnedValue
StringPrototype::method_split(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
924 ExecutionEngine *v4 = b->engine();
925 QString text = getThisString(v4, thisObject);
926 if (v4->hasException)
927 return QV4::Encode::undefined();
930 ScopedValue separatorValue(scope, argc ? argv[0] : Value::undefinedValue());
931 ScopedValue limitValue(scope, argc > 1 ? argv[1] : Value::undefinedValue());
935 if (separatorValue
->isUndefined()) {
936 if (limitValue
->isUndefined()) {
939 return array.asReturnedValue();
941 RETURN_RESULT(scope.engine->newString(text.left(limitValue->toInteger())));
944 uint limit = limitValue->isUndefined() ? UINT_MAX : limitValue->toUInt32();
947 return array.asReturnedValue();
949 Scoped<RegExpObject> re(scope, separatorValue);
951 if (re->value()->pattern->isEmpty()) {
952 re = (RegExpObject *)
nullptr;
953 separatorValue = scope.engine->newString();
960 Q_ALLOCA_VAR(uint, matchOffsets, re->value()->captureCount() * 2 *
sizeof(uint));
963 uint result = regexp->match(text, offset, matchOffsets);
964 if (result == JSC::Yarr::offsetNoMatch)
967 array->push_back((s = scope.engine->newString(text.mid(offset, matchOffsets[0] - offset))));
968 offset = qMax(offset + 1, matchOffsets[1]);
970 if (array->getLength() >= limit)
973 for (
int i = 1; i < re->value()->captureCount(); ++i) {
974 uint start = matchOffsets[i * 2];
975 uint end = matchOffsets[i * 2 + 1];
976 array->push_back((s = scope.engine->newString(text.mid(start, end - start))));
977 if (array->getLength() >= limit)
981 if (array->getLength() < limit)
982 array->push_back((s = scope.engine->newString(text.mid(offset))));
984 QString separator = separatorValue->toQString();
985 if (separator.isEmpty()) {
986 for (uint i = 0; i < qMin(limit, uint(text.size())); ++i)
987 array->push_back((s = scope.engine->newString(text.mid(i, 1))));
988 return array.asReturnedValue();
993 while ((end = text.indexOf(separator, start)) != -1) {
994 array->push_back((s = scope.engine->newString(text.mid(start, end - start))));
995 start = end + separator.size();
996 if (array->getLength() >= limit)
999 if (array->getLength() < limit && start != -1)
1000 array->push_back((s = scope.engine->newString(text.mid(start))));
1002 return array.asReturnedValue();
1034ReturnedValue
StringPrototype::method_substr(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
1036 ExecutionEngine *v4 = b->engine();
1037 const QString value = getThisString(v4, thisObject);
1038 if (v4->hasException)
1039 return QV4::Encode::undefined();
1043 start = argv[0].toInteger();
1045 double length = +qInf();
1047 length = argv[1].toInteger();
1049 double count = value.size();
1051 start = qMax(count + start, 0.0);
1053 length = qMin(qMax(length, 0.0), count - start);
1055 qint32 x = Value::toInt32(start);
1056 qint32 y = Value::toInt32(length);
1057 return Encode(v4->newString(value.mid(x, y)));
1124 ExecutionEngine *v4 = b->engine();
1126 QString stringifiedLocale;
1128 if (
const QV4::String *that = argv[0].as<QV4::String>()) {
1129 stringifiedLocale = that->toQString();
1130 }
else if (
const QLocale *locale = getLocaleDataResource(argv[0])) {
1132 }
else if (argv[0].isObject()) {
1135 ScopedObject arrayLike(scope, argv[0].toObject(scope.engine));
1137 if (arrayLike->getLength() > 0) {
1138 kValue = arrayLike->get(uint(0));
1139 if (kValue
->isString())
1140 stringifiedLocale = kValue
->toQString();
1141 else if (
const QLocale *locale = getLocaleDataResource(kValue))
1145 v4->throwTypeError();
1148 return QLocale(stringifiedLocale);