183ReturnedValue
StringCtor::method_fromCodePoint(
const FunctionObject *f,
const Value *,
const Value *argv,
int argc)
185 ExecutionEngine *e = f->engine();
186 QString result(argc*2, Qt::Uninitialized);
187 QChar *ch = result.data();
188 for (
int i = 0; i < argc; ++i) {
189 double num = argv[i].toNumber();
191 return Encode::undefined();
192 int cp =
static_cast<
int>(num);
193 if (cp != num || cp < 0 || cp > 0x10ffff)
194 return e->throwRangeError(QStringLiteral(
"String.fromCodePoint: argument out of range."));
196 *ch = QChar::highSurrogate(cp);
198 *ch = QChar::lowSurrogate(cp);
204 result.truncate(ch - result.constData());
205 return e->newString(result)->asReturnedValue();
208ReturnedValue
StringCtor::method_raw(
const FunctionObject *f,
const Value *,
const Value *argv,
int argc)
212 return scope.engine->throwTypeError();
214 ScopedObject cooked(scope, argv[0].toObject(scope.engine));
216 return scope.engine->throwTypeError();
217 ScopedString rawString(scope, scope.engine->newIdentifier(QStringLiteral(
"raw")));
218 ScopedValue rawValue(scope, cooked->get(rawString));
219 ScopedObject raw(scope, rawValue->toObject(scope.engine));
220 if (scope.hasException())
221 return Encode::undefined();
227 uint literalSegments = raw->getLength();
228 if (!literalSegments)
229 return scope.engine->id_empty()->asReturnedValue();
234 val = raw->get(nextIndex);
235 result += val
->toQString();
236 if (scope.hasException())
237 return Encode::undefined();
238 if (nextIndex + 1 == literalSegments)
239 return scope.engine->newString(result)->asReturnedValue();
241 if (nextIndex <
static_cast<uint>(argc))
242 result += argv[nextIndex].toQString();
243 if (scope.hasException())
244 return Encode::undefined();
255 Heap::
InternalClass *ic = scope.engine->classes[ExecutionEngine::Class_StringObject]->changePrototype(scope.engine->objectPrototype()->d());
256 d()->internalClass.set(scope.engine, ic);
257 d()->string.set(scope.engine, scope.engine->id_empty()->d());
258 setProperty(scope.engine, Heap::StringObject::LengthPropertyIndex, Value::fromInt32(0));
260 ctor->defineReadonlyProperty(engine->id_prototype(), (o =
this));
261 ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1));
262 ctor->defineDefaultProperty(QStringLiteral(
"fromCharCode"), StringCtor::method_fromCharCode, 1);
263 ctor->defineDefaultProperty(QStringLiteral(
"fromCodePoint"), StringCtor::method_fromCodePoint, 1);
264 ctor->defineDefaultProperty(QStringLiteral(
"raw"), StringCtor::method_raw, 1);
266 defineDefaultProperty(QStringLiteral(
"constructor"), (o = ctor));
267 defineDefaultProperty(engine->id_toString(), method_toString);
268 defineDefaultProperty(engine->id_valueOf(), method_toString);
269 defineDefaultProperty(QStringLiteral(
"charAt"), method_charAt, 1);
270 defineDefaultProperty(QStringLiteral(
"charCodeAt"), method_charCodeAt, 1);
271 defineDefaultProperty(QStringLiteral(
"codePointAt"), method_codePointAt, 1);
272 defineDefaultProperty(QStringLiteral(
"concat"), method_concat, 1);
273 defineDefaultProperty(QStringLiteral(
"endsWith"), method_endsWith, 1);
274 defineDefaultProperty(QStringLiteral(
"indexOf"), method_indexOf, 1);
275 defineDefaultProperty(QStringLiteral(
"includes"), method_includes, 1);
276 defineDefaultProperty(QStringLiteral(
"lastIndexOf"), method_lastIndexOf, 1);
277 defineDefaultProperty(QStringLiteral(
"localeCompare"), method_localeCompare, 1);
278 defineDefaultProperty(QStringLiteral(
"match"), method_match, 1);
279 defineDefaultProperty(QStringLiteral(
"normalize"), method_normalize, 0);
280 defineDefaultProperty(QStringLiteral(
"padEnd"), method_padEnd, 1);
281 defineDefaultProperty(QStringLiteral(
"padStart"), method_padStart, 1);
282 defineDefaultProperty(QStringLiteral(
"repeat"), method_repeat, 1);
283 defineDefaultProperty(QStringLiteral(
"replace"), method_replace, 2);
284 defineDefaultProperty(QStringLiteral(
"search"), method_search, 1);
285 defineDefaultProperty(QStringLiteral(
"slice"), method_slice, 2);
286 defineDefaultProperty(QStringLiteral(
"split"), method_split, 2);
287 defineDefaultProperty(QStringLiteral(
"startsWith"), method_startsWith, 1);
288 defineDefaultProperty(QStringLiteral(
"substr"), method_substr, 2);
289 defineDefaultProperty(QStringLiteral(
"substring"), method_substring, 2);
290 defineDefaultProperty(QStringLiteral(
"toLowerCase"), method_toLowerCase);
291 defineDefaultProperty(QStringLiteral(
"toLocaleLowerCase"), method_toLocaleLowerCase);
292 defineDefaultProperty(QStringLiteral(
"toUpperCase"), method_toUpperCase);
293 defineDefaultProperty(QStringLiteral(
"toLocaleUpperCase"), method_toLocaleUpperCase);
294 defineDefaultProperty(QStringLiteral(
"trim"), method_trim);
295 defineDefaultProperty(engine->symbol_iterator(), method_iterator);
368ReturnedValue
StringPrototype::method_codePointAt(
const FunctionObject *f,
const Value *thisObject,
const Value *argv,
int argc)
370 ExecutionEngine *v4 = f->engine();
371 QString value = getThisString(v4, thisObject);
372 if (v4->hasException)
373 return QV4::Encode::undefined();
375 double index = argc ? argv[0].toInteger() : 0.0;
376 if (v4->hasException)
377 return QV4::Encode::undefined();
379 if (index < 0 || index >= value.size())
380 return Encode::undefined();
382 uint first = value.at(index).unicode();
383 if (QChar::isHighSurrogate(first) && index + 1 < value.size()) {
384 uint second = value.at(index + 1).unicode();
385 if (QChar::isLowSurrogate(second))
386 return Encode(QChar::surrogateToUcs4(first, second));
388 return Encode(first);
458ReturnedValue
StringPrototype::method_includes(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
460 ExecutionEngine *v4 = b->engine();
461 const QString value = getThisString(v4, thisObject);
462 if (v4->hasException)
463 return QV4::Encode::undefined();
465 if (argc && argv[0].as<RegExpObject>())
466 return v4->throwTypeError();
467 QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
468 if (v4->hasException)
469 return Encode::undefined();
473 const Value &posArg = argv[1];
474 pos = posArg.toInteger();
475 if (!posArg.isInteger() && posArg.isNumber() && qIsInf(posArg.toNumber()))
482 QStringView stringToSearch = QStringView{value}.mid(pos);
483 return Encode(stringToSearch.contains(searchString));
486ReturnedValue
StringPrototype::method_lastIndexOf(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
488 ExecutionEngine *v4 = b->engine();
489 const QString value = getThisString(v4, thisObject);
490 if (v4->hasException)
491 return QV4::Encode::undefined();
493 QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
494 if (v4->hasException)
495 return Encode::undefined();
497 double position = argc > 1 ? RuntimeHelpers::toNumber(argv[1]) : +qInf();
498 if (
std::isnan(position))
501 position =
std::trunc(position);
503 int pos =
std::trunc(qMin(qMax(position, 0.0),
double(value.size())));
504 if (!searchString.isEmpty() && pos == value.size())
506 if (searchString.isNull() && pos == 0)
508 int index = value.lastIndexOf(searchString, pos);
509 return Encode(index);
523ReturnedValue
StringPrototype::method_match(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
525 ExecutionEngine *v4 = b->engine();
526 if (thisObject->isNullOrUndefined())
527 return v4->throwTypeError();
530 if (argc && !argv[0].isNullOrUndefined()) {
532 if (scope.hasException())
533 return Encode::undefined();
534 ScopedValue f(scope, r->get(scope.engine->symbol_match()));
535 if (!f
->isNullOrUndefined()) {
538 return scope.engine->throwTypeError();
539 return checkedResult(scope.engine, fo->call(r, thisObject, 1));
544 if (v4->hasException)
545 return Encode::undefined();
547 Scoped<RegExpObject> that(scope, argc ? argv[0] : Value::undefinedValue());
550 that = RegExpCtor::virtualCallAsConstructor(b, argv, argc, b);
551 if (v4->hasException)
552 return Encode::undefined();
558 return scope.engine->throwTypeError();
559 return checkedResult(scope.engine, match->call(that, s, 1));
562ReturnedValue
StringPrototype::method_normalize(
const FunctionObject *f,
const Value *thisObject,
const Value *argv,
int argc)
564 ExecutionEngine *v4 = f->engine();
565 const QString value = getThisString(v4, thisObject);
566 if (v4->hasException)
567 return Encode::undefined();
569 QString::NormalizationForm form = QString::NormalizationForm_C;
570 if (argc >= 1 && !argv[0].isUndefined()) {
571 QString f = argv[0].toQString();
572 if (v4->hasException)
573 return Encode::undefined();
574 if (f == QLatin1String(
"NFC"))
575 form = QString::NormalizationForm_C;
576 else if (f == QLatin1String(
"NFD"))
577 form = QString::NormalizationForm_D;
578 else if (f == QLatin1String(
"NFKC"))
579 form = QString::NormalizationForm_KC;
580 else if (f == QLatin1String(
"NFKD"))
581 form = QString::NormalizationForm_KD;
583 return v4->throwRangeError(QLatin1String(
"String.prototype.normalize: Invalid normalization form."));
585 QString normalized = value.normalized(form);
586 return v4->newString(normalized)->asReturnedValue();
589ReturnedValue
StringPrototype::method_padEnd(
const FunctionObject *f,
const Value *thisObject,
const Value *argv,
int argc)
591 ExecutionEngine *v4 = f->engine();
592 if (thisObject->isNullOrUndefined())
593 return v4->throwTypeError();
597 if (v4->hasException)
598 return Encode::undefined();
600 return s->asReturnedValue();
602 double maxLen = argv[0].toInteger();
603 if (maxLen <= s->d()->length())
604 return s->asReturnedValue();
605 QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : u" "_s;
606 if (v4->hasException)
607 return Encode::undefined();
609 if (fillString.isEmpty())
610 return s->asReturnedValue();
612 QString padded = s->toQString();
613 int oldLength = padded.size();
614 int toFill = maxLen - oldLength;
615 padded.resize(maxLen);
616 QChar *ch = padded.data() + oldLength;
618 int copy = qMin(fillString.size(), toFill);
619 memcpy(ch, fillString.constData(), copy*
sizeof(QChar));
625 return v4->newString(padded)->asReturnedValue();
628ReturnedValue
StringPrototype::method_padStart(
const FunctionObject *f,
const Value *thisObject,
const Value *argv,
int argc)
630 ExecutionEngine *v4 = f->engine();
631 if (thisObject->isNullOrUndefined())
632 return v4->throwTypeError();
636 if (v4->hasException)
637 return Encode::undefined();
639 return s->asReturnedValue();
641 double maxLen = argv[0].toInteger();
642 if (maxLen <= s->d()->length())
643 return s->asReturnedValue();
644 QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : u" "_s;
645 if (v4->hasException)
646 return Encode::undefined();
648 if (fillString.isEmpty())
649 return s->asReturnedValue();
651 QString original = s->toQString();
652 int oldLength = original.size();
653 int toFill = maxLen - oldLength;
655 padded.resize(maxLen);
656 QChar *ch = padded.data();
658 int copy = qMin(fillString.size(), toFill);
659 memcpy(ch, fillString.constData(), copy*
sizeof(QChar));
663 memcpy(ch, original.constData(), oldLength*
sizeof(QChar));
667 return v4->newString(padded)->asReturnedValue();
686static void appendReplacementString(QString *result,
const QString &input,
const QString& replaceValue, uint* matchOffsets,
int captureCount)
688 result->reserve(result->size() + replaceValue.size());
689 for (
int i = 0; i < replaceValue.size(); ++i) {
690 if (replaceValue.at(i) == QLatin1Char(
'$') && i < replaceValue.size() - 1) {
691 ushort ch = replaceValue.at(i + 1).unicode();
692 uint substStart = JSC::Yarr::offsetNoMatch;
693 uint substEnd = JSC::Yarr::offsetNoMatch;
696 *result += QChar(ch);
699 }
else if (ch ==
'&') {
700 substStart = matchOffsets[0];
701 substEnd = matchOffsets[1];
703 }
else if (ch ==
'`') {
705 substEnd = matchOffsets[0];
707 }
else if (ch ==
'\'') {
708 substStart = matchOffsets[1];
709 substEnd = input.size();
711 }
else if (ch >=
'0' && ch <=
'9') {
712 uint capture = ch -
'0';
714 if (i < replaceValue.size() - 2) {
715 ch = replaceValue.at(i + 2).unicode();
716 if (ch >=
'0' && ch <=
'9') {
717 uint c = capture*10 + ch -
'0';
718 if (c <
static_cast<uint>(captureCount)) {
724 if (capture > 0 && capture <
static_cast<uint>(captureCount)) {
725 substStart = matchOffsets[capture * 2];
726 substEnd = matchOffsets[capture * 2 + 1];
732 if (substStart != JSC::Yarr::offsetNoMatch && substEnd != JSC::Yarr::offsetNoMatch)
733 *result += QStringView{input}.mid(substStart, substEnd - substStart);
735 *result += replaceValue.at(i);
737 *result += replaceValue.at(i);
742ReturnedValue
StringPrototype::method_replace(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
746 string = thisString->d()->string->toQString();
748 string = thisObject->toQString();
751 int numStringMatches = 0;
753 uint allocatedMatchOffsets = 64;
754 uint _matchOffsets[64];
755 uint *matchOffsets = _matchOffsets;
758 ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue());
759 Scoped<RegExpObject> regExp(scope, searchValue);
762 uint nMatchOffsets = 0;
767 int oldSize = nMatchOffsets;
768 if (allocatedMatchOffsets < nMatchOffsets + re->captureCount() * 2) {
769 allocatedMatchOffsets = qMax(allocatedMatchOffsets * 2, nMatchOffsets + re->captureCount() * 2);
770 uint *newOffsets = (uint *)malloc(allocatedMatchOffsets*
sizeof(uint));
771 memcpy(newOffsets, matchOffsets, nMatchOffsets*
sizeof(uint));
772 if (matchOffsets != _matchOffsets)
774 matchOffsets = newOffsets;
776 if (re->match(string, offset, matchOffsets + oldSize) == JSC::Yarr::offsetNoMatch) {
777 nMatchOffsets = oldSize;
780 nMatchOffsets += re->captureCount() * 2;
781 if (!regExp->global())
784 const uint matchBegin = matchOffsets[oldSize];
785 const uint matchEnd = matchOffsets[oldSize + 1];
788 const uint matchOffset = (matchBegin == matchEnd) ? matchEnd + 1 : matchEnd;
790 offset =
std::max(offset + 1, matchOffset);
792 if (regExp->global()) {
793 regExp->setLastIndex(0);
794 if (scope.hasException())
795 return Encode::undefined();
797 numStringMatches = nMatchOffsets / (regExp->value()->captureCount() * 2);
798 numCaptures = regExp->value()->captureCount();
801 QString searchString = searchValue->toQString();
802 int idx = string.indexOf(searchString);
804 numStringMatches = 1;
805 matchOffsets[0] = idx;
806 matchOffsets[1] = idx + searchString.size();
812 ScopedValue replaceValue(scope, argc > 1 ? argv[1] : Value::undefinedValue());
814 if (!!searchCallback) {
815 result.reserve(string.size() + 10*numStringMatches);
817 Value *arguments = scope.constructUndefined(numCaptures + 2);
819 for (
int i = 0; i < numStringMatches; ++i) {
820 for (
int k = 0; k < numCaptures; ++k) {
821 int idx = (i * numCaptures + k) * 2;
822 uint start = matchOffsets[idx];
823 uint end = matchOffsets[idx + 1];
824 entry = Value::undefinedValue();
825 if (start != JSC::Yarr::offsetNoMatch && end != JSC::Yarr::offsetNoMatch)
826 entry = scope.engine->newString(string.mid(start, end - start));
827 arguments[k] = entry;
829 uint matchStart = matchOffsets[i * numCaptures * 2];
830 Q_ASSERT(matchStart >=
static_cast<uint>(lastEnd));
831 uint matchEnd = matchOffsets[i * numCaptures * 2 + 1];
832 arguments[numCaptures] = Value::fromUInt32(matchStart);
833 arguments[numCaptures + 1] = scope.engine->newString(string);
835 Value that = Value::undefinedValue();
836 replacement = searchCallback->call(&that, arguments, numCaptures + 2);
838 result += QStringView{string}.mid(lastEnd, matchStart - lastEnd);
839 result += replacement
->toQString();
842 result += QStringView{string}.mid(lastEnd);
844 QString newString = replaceValue->toQString();
845 result.reserve(string.size() + numStringMatches*newString.size());
848 for (
int i = 0; i < numStringMatches; ++i) {
849 int baseIndex = i * numCaptures * 2;
850 uint matchStart = matchOffsets[baseIndex];
851 uint matchEnd = matchOffsets[baseIndex + 1];
852 if (matchStart == JSC::Yarr::offsetNoMatch)
855 result += QStringView{string}.mid(lastEnd, matchStart - lastEnd);
856 appendReplacementString(&result, string, newString, matchOffsets + baseIndex, numCaptures);
859 result += QStringView{string}.mid(lastEnd);
862 if (matchOffsets != _matchOffsets)
865 return Encode(scope.engine->newString(result));
868ReturnedValue
StringPrototype::method_search(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
871 QString string = getThisString(scope.engine, thisObject);
872 if (scope.hasException())
873 return QV4::Encode::undefined();
875 Scoped<RegExpObject> regExp(scope, argc ? argv[0] : Value::undefinedValue());
877 regExp = scope.engine->regExpCtor()->callAsConstructor(argv, 1);
878 if (scope.hasException())
879 return QV4::Encode::undefined();
884 Q_ALLOCA_VAR(uint, matchOffsets, regExp->value()->captureCount() * 2 *
sizeof(uint));
885 uint result = re->match(string, 0, matchOffsets);
886 if (result == JSC::Yarr::offsetNoMatch)
889 return Encode(result);
892ReturnedValue
StringPrototype::method_slice(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
894 ExecutionEngine *v4 = b->engine();
897 if (v4->hasException)
898 return QV4::Encode::undefined();
901 const double length = s->d()->length();
903 double start = argc ? argv[0].toInteger() : 0;
904 double end = (argc < 2 || argv[1].isUndefined())
905 ? length : argv[1].toInteger();
908 start = qMax(length + start, 0.);
910 start = qMin(start, length);
913 end = qMax(length + end, 0.);
915 end = qMin(end, length);
917 const int intStart =
int(start);
918 const int intEnd =
int(end);
920 int count = qMax(0, intEnd - intStart);
921 return Encode(v4->memoryManager->alloc<
ComplexString>(s->d(), intStart, count));
924ReturnedValue
StringPrototype::method_split(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
926 ExecutionEngine *v4 = b->engine();
927 QString text = getThisString(v4, thisObject);
928 if (v4->hasException)
929 return QV4::Encode::undefined();
932 ScopedValue separatorValue(scope, argc ? argv[0] : Value::undefinedValue());
933 ScopedValue limitValue(scope, argc > 1 ? argv[1] : Value::undefinedValue());
937 if (separatorValue
->isUndefined()) {
938 if (limitValue
->isUndefined()) {
941 return array.asReturnedValue();
943 RETURN_RESULT(scope.engine->newString(text.left(limitValue->toInteger())));
946 uint limit = limitValue->isUndefined() ? UINT_MAX : limitValue->toUInt32();
949 return array.asReturnedValue();
951 Scoped<RegExpObject> re(scope, separatorValue);
953 if (re->value()->pattern->isEmpty()) {
954 re = (RegExpObject *)
nullptr;
955 separatorValue = scope.engine->newString();
962 Q_ALLOCA_VAR(uint, matchOffsets, re->value()->captureCount() * 2 *
sizeof(uint));
965 uint result = regexp->match(text, offset, matchOffsets);
966 if (result == JSC::Yarr::offsetNoMatch)
969 array->push_back((s = scope.engine->newString(text.mid(offset, matchOffsets[0] - offset))));
970 offset = qMax(offset + 1, matchOffsets[1]);
972 if (array->getLength() >= limit)
975 for (
int i = 1; i < re->value()->captureCount(); ++i) {
976 uint start = matchOffsets[i * 2];
977 uint end = matchOffsets[i * 2 + 1];
978 array->push_back((s = scope.engine->newString(text.mid(start, end - start))));
979 if (array->getLength() >= limit)
983 if (array->getLength() < limit)
984 array->push_back((s = scope.engine->newString(text.mid(offset))));
986 QString separator = separatorValue->toQString();
987 if (separator.isEmpty()) {
988 for (uint i = 0; i < qMin(limit, uint(text.size())); ++i)
989 array->push_back((s = scope.engine->newString(text.mid(i, 1))));
990 return array.asReturnedValue();
995 while ((end = text.indexOf(separator, start)) != -1) {
996 array->push_back((s = scope.engine->newString(text.mid(start, end - start))));
997 start = end + separator.size();
998 if (array->getLength() >= limit)
1001 if (array->getLength() < limit && start != -1)
1002 array->push_back((s = scope.engine->newString(text.mid(start))));
1004 return array.asReturnedValue();
1036ReturnedValue
StringPrototype::method_substr(
const FunctionObject *b,
const Value *thisObject,
const Value *argv,
int argc)
1038 ExecutionEngine *v4 = b->engine();
1039 const QString value = getThisString(v4, thisObject);
1040 if (v4->hasException)
1041 return QV4::Encode::undefined();
1045 start = argv[0].toInteger();
1047 double length = +qInf();
1049 length = argv[1].toInteger();
1051 double count = value.size();
1053 start = qMax(count + start, 0.0);
1055 length = qMin(qMax(length, 0.0), count - start);
1057 qint32 x = Value::toInt32(start);
1058 qint32 y = Value::toInt32(length);
1059 return Encode(v4->newString(value.mid(x, y)));
1126 ExecutionEngine *v4 = b->engine();
1128 QString stringifiedLocale;
1130 if (
const QV4::String *that = argv[0].as<QV4::String>()) {
1131 stringifiedLocale = that->toQString();
1132 }
else if (
const QLocale *locale = getLocaleDataResource(argv[0])) {
1134 }
else if (argv[0].isObject()) {
1137 ScopedObject arrayLike(scope, argv[0].toObject(scope.engine));
1139 if (arrayLike->getLength() > 0) {
1140 kValue = arrayLike->get(uint(0));
1141 if (kValue
->isString())
1142 stringifiedLocale = kValue
->toQString();
1143 else if (
const QLocale *locale = getLocaleDataResource(kValue))
1147 v4->throwTypeError();
1150 return QLocale(stringifiedLocale);