blob: 9b4a2d575e4ba925a09edc9fb5da378e8366cfcb [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qv4stringobject_p.h"
#include "qv4regexp_p.h"
#include "qv4regexpobject_p.h"
#include "qv4objectproto_p.h"
#include <private/qv4mm_p.h>
#include "qv4scopedvalue_p.h"
#include "qv4symbol_p.h"
#include <private/qv4alloca_p.h>
#include "qv4jscall_p.h"
#include "qv4stringiterator_p.h"
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <QtCore/QStringList>
#include <cassert>
#ifndef Q_OS_WIN
# include <time.h>
# ifndef Q_OS_VXWORKS
# include <sys/time.h>
# else
# include "qplatformdefs.h"
# endif
#else
# include <windows.h>
#endif
using namespace QV4;
DEFINE_OBJECT_VTABLE(StringObject);
void Heap::StringObject::init()
{
Object::init();
Q_ASSERT(vtable() == QV4::StringObject::staticVTable());
string.set(internalClass->engine, internalClass->engine->id_empty()->d());
setProperty(internalClass->engine, LengthPropertyIndex, Value::fromInt32(0));
}
void Heap::StringObject::init(const QV4::String *str)
{
Object::init();
string.set(internalClass->engine, str->d());
setProperty(internalClass->engine, LengthPropertyIndex, Value::fromInt32(length()));
}
Heap::String *Heap::StringObject::getIndex(uint index) const
{
QString str = string->toQString();
if (index >= (uint)str.length())
return nullptr;
return internalClass->engine->newString(str.mid(index, 1));
}
uint Heap::StringObject::length() const
{
return string->length();
}
bool StringObject::virtualDeleteProperty(Managed *m, PropertyKey id)
{
Q_ASSERT(m->as<StringObject>());
if (id.isArrayIndex()) {
StringObject *o = static_cast<StringObject *>(m);
uint index = id.asArrayIndex();
if (index < static_cast<uint>(o->d()->string->toQString().length()))
return false;
}
return Object::virtualDeleteProperty(m, id);
}
struct StringObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator
{
~StringObjectOwnPropertyKeyIterator() override = default;
PropertyKey next(const QV4::Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override;
};
PropertyKey StringObjectOwnPropertyKeyIterator::next(const QV4::Object *o, Property *pd, PropertyAttributes *attrs)
{
const StringObject *s = static_cast<const StringObject *>(o);
uint slen = s->d()->string->toQString().length();
if (arrayIndex < slen) {
uint index = arrayIndex;
++arrayIndex;
if (attrs)
*attrs = Attr_NotConfigurable|Attr_NotWritable;
if (pd)
pd->value = s->getIndex(index);
return PropertyKey::fromArrayIndex(index);
} else if (arrayIndex == slen) {
if (s->arrayData()) {
SparseArrayNode *arrayNode = s->sparseBegin();
// iterate until we're past the end of the string
while (arrayNode && arrayNode->key() < slen)
arrayNode = arrayNode->nextNode();
}
}
return ObjectOwnPropertyKeyIterator::next(o, pd, attrs);
}
OwnPropertyKeyIterator *StringObject::virtualOwnPropertyKeys(const Object *m, Value *target)
{
*target = *m;
return new StringObjectOwnPropertyKeyIterator;
}
PropertyAttributes StringObject::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p)
{
PropertyAttributes attributes = Object::virtualGetOwnProperty(m, id, p);
if (attributes != Attr_Invalid)
return attributes;
if (id.isArrayIndex()) {
const uint index = id.asArrayIndex();
const auto s = static_cast<const StringObject *>(m);
if (index < uint(s->d()->string->toQString().length())) {
if (p)
p->value = s->getIndex(index);
return Attr_NotConfigurable|Attr_NotWritable;
}
}
return Object::virtualGetOwnProperty(m, id, p);
}
DEFINE_OBJECT_VTABLE(StringCtor);
void Heap::StringCtor::init(QV4::ExecutionContext *scope)
{
Heap::FunctionObject::init(scope, QStringLiteral("String"));
}
ReturnedValue StringCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget)
{
ExecutionEngine *v4 = static_cast<const Object *>(f)->engine();
Scope scope(v4);
ScopedString value(scope);
if (argc)
value = argv[0].toString(v4);
else
value = v4->newString();
CHECK_EXCEPTION();
ReturnedValue o = Encode(v4->newStringObject(value));
if (!newTarget)
return o;
ScopedObject obj(scope, o);
obj->setProtoFromNewTarget(newTarget);
return obj->asReturnedValue();
}
ReturnedValue StringCtor::virtualCall(const FunctionObject *m, const Value *, const Value *argv, int argc)
{
ExecutionEngine *v4 = m->engine();
if (!argc)
return v4->newString()->asReturnedValue();
if (argv[0].isSymbol())
return v4->newString(argv[0].symbolValue()->descriptiveString())->asReturnedValue();
return argv[0].toString(v4)->asReturnedValue();
}
ReturnedValue StringCtor::method_fromCharCode(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
QString str(argc, Qt::Uninitialized);
QChar *ch = str.data();
for (int i = 0, ei = argc; i < ei; ++i) {
*ch = QChar(argv[i].toUInt16());
++ch;
}
*ch = 0;
return Encode(b->engine()->newString(str));
}
ReturnedValue StringCtor::method_fromCodePoint(const FunctionObject *f, const Value *, const Value *argv, int argc)
{
ExecutionEngine *e = f->engine();
QString result(argc*2, Qt::Uninitialized); // assume worst case
QChar *ch = result.data();
for (int i = 0; i < argc; ++i) {
double num = argv[i].toNumber();
if (e->hasException)
return Encode::undefined();
int cp = static_cast<int>(num);
if (cp != num || cp < 0 || cp > 0x10ffff)
return e->throwRangeError(QStringLiteral("String.fromCodePoint: argument out of range."));
if (cp > 0xffff) {
*ch = QChar::highSurrogate(cp);
++ch;
*ch = QChar::lowSurrogate(cp);
} else {
*ch = cp;
}
++ch;
}
*ch = 0;
result.truncate(ch - result.constData());
return e->newString(result)->asReturnedValue();
}
ReturnedValue StringCtor::method_raw(const FunctionObject *f, const Value *, const Value *argv, int argc)
{
Scope scope(f);
if (!argc)
return scope.engine->throwTypeError();
ScopedObject cooked(scope, argv[0].toObject(scope.engine));
if (!cooked)
return scope.engine->throwTypeError();
ScopedString rawString(scope, scope.engine->newIdentifier(QStringLiteral("raw")));
ScopedValue rawValue(scope, cooked->get(rawString));
ScopedObject raw(scope, rawValue->toObject(scope.engine));
if (scope.hasException())
return Encode::undefined();
++argv;
--argc;
QString result;
uint literalSegments = raw->getLength();
if (!literalSegments)
return scope.engine->id_empty()->asReturnedValue();
uint nextIndex = 0;
ScopedValue val(scope);
while (1) {
val = raw->get(nextIndex);
result += val->toQString();
if (scope.engine->hasException)
return Encode::undefined();
if (nextIndex + 1 == literalSegments)
return scope.engine->newString(result)->asReturnedValue();
if (nextIndex < static_cast<uint>(argc))
result += argv[nextIndex].toQString();
if (scope.engine->hasException)
return Encode::undefined();
++nextIndex;
}
}
void StringPrototype::init(ExecutionEngine *engine, Object *ctor)
{
Scope scope(engine);
ScopedObject o(scope);
// need to set this once again, as these were not fully defined when creating the string proto
Heap::InternalClass *ic = scope.engine->classes[ExecutionEngine::Class_StringObject]->changePrototype(scope.engine->objectPrototype()->d());
d()->internalClass.set(scope.engine, ic);
d()->string.set(scope.engine, scope.engine->id_empty()->d());
setProperty(scope.engine, Heap::StringObject::LengthPropertyIndex, Value::fromInt32(0));
ctor->defineReadonlyProperty(engine->id_prototype(), (o = this));
ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1));
ctor->defineDefaultProperty(QStringLiteral("fromCharCode"), StringCtor::method_fromCharCode, 1);
ctor->defineDefaultProperty(QStringLiteral("fromCodePoint"), StringCtor::method_fromCodePoint, 1);
ctor->defineDefaultProperty(QStringLiteral("raw"), StringCtor::method_raw, 1);
defineDefaultProperty(QStringLiteral("constructor"), (o = ctor));
defineDefaultProperty(engine->id_toString(), method_toString);
defineDefaultProperty(engine->id_valueOf(), method_toString); // valueOf and toString are identical
defineDefaultProperty(QStringLiteral("charAt"), method_charAt, 1);
defineDefaultProperty(QStringLiteral("charCodeAt"), method_charCodeAt, 1);
defineDefaultProperty(QStringLiteral("codePointAt"), method_codePointAt, 1);
defineDefaultProperty(QStringLiteral("concat"), method_concat, 1);
defineDefaultProperty(QStringLiteral("endsWith"), method_endsWith, 1);
defineDefaultProperty(QStringLiteral("indexOf"), method_indexOf, 1);
defineDefaultProperty(QStringLiteral("includes"), method_includes, 1);
defineDefaultProperty(QStringLiteral("lastIndexOf"), method_lastIndexOf, 1);
defineDefaultProperty(QStringLiteral("localeCompare"), method_localeCompare, 1);
defineDefaultProperty(QStringLiteral("match"), method_match, 1);
defineDefaultProperty(QStringLiteral("normalize"), method_normalize, 0);
defineDefaultProperty(QStringLiteral("padEnd"), method_padEnd, 1);
defineDefaultProperty(QStringLiteral("padStart"), method_padStart, 1);
defineDefaultProperty(QStringLiteral("repeat"), method_repeat, 1);
defineDefaultProperty(QStringLiteral("replace"), method_replace, 2);
defineDefaultProperty(QStringLiteral("search"), method_search, 1);
defineDefaultProperty(QStringLiteral("slice"), method_slice, 2);
defineDefaultProperty(QStringLiteral("split"), method_split, 2);
defineDefaultProperty(QStringLiteral("startsWith"), method_startsWith, 1);
defineDefaultProperty(QStringLiteral("substr"), method_substr, 2);
defineDefaultProperty(QStringLiteral("substring"), method_substring, 2);
defineDefaultProperty(QStringLiteral("toLowerCase"), method_toLowerCase);
defineDefaultProperty(QStringLiteral("toLocaleLowerCase"), method_toLocaleLowerCase);
defineDefaultProperty(QStringLiteral("toUpperCase"), method_toUpperCase);
defineDefaultProperty(QStringLiteral("toLocaleUpperCase"), method_toLocaleUpperCase);
defineDefaultProperty(QStringLiteral("trim"), method_trim);
defineDefaultProperty(engine->symbol_iterator(), method_iterator);
}
static Heap::String *thisAsString(ExecutionEngine *v4, const QV4::Value *thisObject)
{
if (String *s = thisObject->stringValue())
return s->d();
if (const StringObject *thisString = thisObject->as<StringObject>())
return thisString->d()->string;
return thisObject->toString(v4);
}
static QString getThisString(ExecutionEngine *v4, const QV4::Value *thisObject)
{
if (String *s = thisObject->stringValue())
return s->toQString();
if (const StringObject *thisString = thisObject->as<StringObject>())
return thisString->d()->string->toQString();
if (thisObject->isUndefined() || thisObject->isNull()) {
v4->throwTypeError();
return QString();
}
return thisObject->toQString();
}
ReturnedValue StringPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
if (thisObject->isString())
return thisObject->asReturnedValue();
ExecutionEngine *v4 = b->engine();
const StringObject *o = thisObject->as<StringObject>();
if (!o)
return v4->throwTypeError();
return o->d()->string->asReturnedValue();
}
ReturnedValue StringPrototype::method_charAt(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
const QString str = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
int pos = 0;
if (argc > 0)
pos = (int) argv[0].toInteger();
QString result;
if (pos >= 0 && pos < str.length())
result += str.at(pos);
return Encode(v4->newString(result));
}
ReturnedValue StringPrototype::method_charCodeAt(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
const QString str = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
int pos = 0;
if (argc > 0)
pos = (int) argv[0].toInteger();
if (pos >= 0 && pos < str.length())
RETURN_RESULT(Encode(str.at(pos).unicode()));
return Encode(qt_qnan());
}
ReturnedValue StringPrototype::method_codePointAt(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = f->engine();
QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
int index = argc ? argv[0].toInteger() : 0;
if (v4->hasException)
return QV4::Encode::undefined();
if (index < 0 || index >= value.size())
return Encode::undefined();
uint first = value.at(index).unicode();
if (QChar::isHighSurrogate(first) && index + 1 < value.size()) {
uint second = value.at(index + 1).unicode();
if (QChar::isLowSurrogate(second))
return Encode(QChar::surrogateToUcs4(first, second));
}
return Encode(first);
}
ReturnedValue StringPrototype::method_concat(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
Scope scope(v4);
ScopedString s(scope);
for (int i = 0; i < argc; ++i) {
s = argv[i].toString(scope.engine);
if (v4->hasException)
return QV4::Encode::undefined();
Q_ASSERT(s->isString());
value += s->toQString();
}
return Encode(v4->newString(value));
}
ReturnedValue StringPrototype::method_endsWith(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
const QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
if (argc && argv[0].as<RegExpObject>())
return v4->throwTypeError();
QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
if (v4->hasException)
return Encode::undefined();
int pos = value.length();
if (argc > 1)
pos = (int) argv[1].toInteger();
if (pos == value.length())
RETURN_RESULT(Encode(value.endsWith(searchString)));
QStringRef stringToSearch = value.leftRef(pos);
return Encode(stringToSearch.endsWith(searchString));
}
ReturnedValue StringPrototype::method_indexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
const QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
if (v4->hasException)
return Encode::undefined();
int pos = 0;
if (argc > 1)
pos = (int) argv[1].toInteger();
int index = -1;
if (! value.isEmpty())
index = value.indexOf(searchString, qMin(qMax(pos, 0), value.length()));
return Encode(index);
}
ReturnedValue StringPrototype::method_includes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
const QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
if (argc && argv[0].as<RegExpObject>())
return v4->throwTypeError();
QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
if (v4->hasException)
return Encode::undefined();
int pos = 0;
if (argc > 1) {
const Value &posArg = argv[1];
pos = (int) posArg.toInteger();
if (!posArg.isInteger() && posArg.isNumber() && qIsInf(posArg.toNumber()))
pos = value.length();
}
if (pos == 0)
RETURN_RESULT(Encode(value.contains(searchString)));
QStringRef stringToSearch = value.midRef(pos);
return Encode(stringToSearch.contains(searchString));
}
ReturnedValue StringPrototype::method_lastIndexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
const QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
if (v4->hasException)
return Encode::undefined();
double position = argc > 1 ? RuntimeHelpers::toNumber(argv[1]) : +qInf();
if (std::isnan(position))
position = +qInf();
else
position = trunc(position);
int pos = trunc(qMin(qMax(position, 0.0), double(value.length())));
if (!searchString.isEmpty() && pos == value.length())
--pos;
if (searchString.isNull() && pos == 0)
RETURN_RESULT(Encode(-1));
int index = value.lastIndexOf(searchString, pos);
return Encode(index);
}
ReturnedValue StringPrototype::method_localeCompare(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
const QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
const QString that = (argc ? argv[0] : Value::undefinedValue()).toQString();
return Encode(QString::localeAwareCompare(value, that));
}
ReturnedValue StringPrototype::method_match(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
if (thisObject->isNullOrUndefined())
return v4->throwTypeError();
Scope scope(v4);
if (argc && !argv[0].isNullOrUndefined()) {
ScopedObject r(scope, argv[0].toObject(scope.engine));
if (scope.hasException())
return Encode::undefined();
ScopedValue f(scope, r->get(scope.engine->symbol_match()));
if (!f->isNullOrUndefined()) {
ScopedFunctionObject fo(scope, f);
if (!fo)
return scope.engine->throwTypeError();
return fo->call(r, thisObject, 1);
}
}
ScopedString s(scope, thisObject->toString(v4));
if (v4->hasException)
return Encode::undefined();
Scoped<RegExpObject> that(scope, argc ? argv[0] : Value::undefinedValue());
if (!that) {
// convert args[0] to a regexp
that = RegExpCtor::virtualCallAsConstructor(b, argv, argc, b);
if (v4->hasException)
return Encode::undefined();
}
Q_ASSERT(!!that);
ScopedFunctionObject match(scope, that->get(scope.engine->symbol_match()));
if (!match)
return scope.engine->throwTypeError();
return match->call(that, s, 1);
}
ReturnedValue StringPrototype::method_normalize(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = f->engine();
const QString value = getThisString(v4, thisObject);
if (v4->hasException)
return Encode::undefined();
QString::NormalizationForm form = QString::NormalizationForm_C;
if (argc >= 1 && !argv[0].isUndefined()) {
QString f = argv[0].toQString();
if (v4->hasException)
return Encode::undefined();
if (f == QLatin1String("NFC"))
form = QString::NormalizationForm_C;
else if (f == QLatin1String("NFD"))
form = QString::NormalizationForm_D;
else if (f == QLatin1String("NFKC"))
form = QString::NormalizationForm_KC;
else if (f == QLatin1String("NFKD"))
form = QString::NormalizationForm_KD;
else
return v4->throwRangeError(QLatin1String("String.prototype.normalize: Invalid normalization form."));
}
QString normalized = value.normalized(form);
return v4->newString(normalized)->asReturnedValue();
}
ReturnedValue StringPrototype::method_padEnd(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = f->engine();
if (thisObject->isNullOrUndefined())
return v4->throwTypeError();
Scope scope(v4);
ScopedString s(scope, thisAsString(v4, thisObject));
if (v4->hasException)
return Encode::undefined();
if (!argc)
return s->asReturnedValue();
int maxLen = argv[0].toInteger();
if (maxLen <= s->d()->length())
return s->asReturnedValue();
QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : QString::fromLatin1(" ");
if (v4->hasException)
return Encode::undefined();
if (fillString.isEmpty())
return s->asReturnedValue();
QString padded = s->toQString();
int oldLength = padded.length();
int toFill = maxLen - oldLength;
padded.resize(maxLen);
QChar *ch = padded.data() + oldLength;
while (toFill) {
int copy = qMin(fillString.length(), toFill);
memcpy(ch, fillString.constData(), copy*sizeof(QChar));
toFill -= copy;
ch += copy;
}
*ch = 0;
return v4->newString(padded)->asReturnedValue();
}
ReturnedValue StringPrototype::method_padStart(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = f->engine();
if (thisObject->isNullOrUndefined())
return v4->throwTypeError();
Scope scope(v4);
ScopedString s(scope, thisAsString(v4, thisObject));
if (v4->hasException)
return Encode::undefined();
if (!argc)
return s->asReturnedValue();
int maxLen = argv[0].toInteger();
if (maxLen <= s->d()->length())
return s->asReturnedValue();
QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : QString::fromLatin1(" ");
if (v4->hasException)
return Encode::undefined();
if (fillString.isEmpty())
return s->asReturnedValue();
QString original = s->toQString();
int oldLength = original.length();
int toFill = maxLen - oldLength;
QString padded;
padded.resize(maxLen);
QChar *ch = padded.data();
while (toFill) {
int copy = qMin(fillString.length(), toFill);
memcpy(ch, fillString.constData(), copy*sizeof(QChar));
toFill -= copy;
ch += copy;
}
memcpy(ch, original.constData(), oldLength*sizeof(QChar));
ch += oldLength;
*ch = 0;
return v4->newString(padded)->asReturnedValue();
}
ReturnedValue StringPrototype::method_repeat(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
const QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
double repeats = (argc ? argv[0] : Value::undefinedValue()).toInteger();
if (repeats < 0 || qIsInf(repeats))
return v4->throwRangeError(QLatin1String("Invalid count value"));
return Encode(v4->newString(value.repeated(int(repeats))));
}
static void appendReplacementString(QString *result, const QString &input, const QString& replaceValue, uint* matchOffsets, int captureCount)
{
result->reserve(result->length() + replaceValue.length());
for (int i = 0; i < replaceValue.length(); ++i) {
if (replaceValue.at(i) == QLatin1Char('$') && i < replaceValue.length() - 1) {
ushort ch = replaceValue.at(i + 1).unicode();
uint substStart = JSC::Yarr::offsetNoMatch;
uint substEnd = JSC::Yarr::offsetNoMatch;
int skip = 0;
if (ch == '$') {
*result += QChar(ch);
++i;
continue;
} else if (ch == '&') {
substStart = matchOffsets[0];
substEnd = matchOffsets[1];
skip = 1;
} else if (ch == '`') {
substStart = 0;
substEnd = matchOffsets[0];
skip = 1;
} else if (ch == '\'') {
substStart = matchOffsets[1];
substEnd = input.length();
skip = 1;
} else if (ch >= '0' && ch <= '9') {
uint capture = ch - '0';
skip = 1;
if (i < replaceValue.length() - 2) {
ch = replaceValue.at(i + 2).unicode();
if (ch >= '0' && ch <= '9') {
uint c = capture*10 + ch - '0';
if (c < static_cast<uint>(captureCount)) {
capture = c;
skip = 2;
}
}
}
if (capture > 0 && capture < static_cast<uint>(captureCount)) {
substStart = matchOffsets[capture * 2];
substEnd = matchOffsets[capture * 2 + 1];
} else {
skip = 0;
}
}
i += skip;
if (substStart != JSC::Yarr::offsetNoMatch && substEnd != JSC::Yarr::offsetNoMatch)
*result += input.midRef(substStart, substEnd - substStart);
else if (skip == 0) // invalid capture reference. Taken as literal value
*result += replaceValue.at(i);
} else {
*result += replaceValue.at(i);
}
}
}
ReturnedValue StringPrototype::method_replace(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
QString string;
if (const StringObject *thisString = thisObject->as<StringObject>())
string = thisString->d()->string->toQString();
else
string = thisObject->toQString();
int numCaptures = 0;
int numStringMatches = 0;
uint allocatedMatchOffsets = 64;
uint _matchOffsets[64];
uint *matchOffsets = _matchOffsets;
Scope scope(b);
ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue());
Scoped<RegExpObject> regExp(scope, searchValue);
if (regExp) {
uint offset = 0;
uint nMatchOffsets = 0;
// We extract the pointer here to work around a compiler bug on Android.
Scoped<RegExp> re(scope, regExp->value());
while (true) {
int oldSize = nMatchOffsets;
if (allocatedMatchOffsets < nMatchOffsets + re->captureCount() * 2) {
allocatedMatchOffsets = qMax(allocatedMatchOffsets * 2, nMatchOffsets + re->captureCount() * 2);
uint *newOffsets = (uint *)malloc(allocatedMatchOffsets*sizeof(uint));
memcpy(newOffsets, matchOffsets, nMatchOffsets*sizeof(uint));
if (matchOffsets != _matchOffsets)
free(matchOffsets);
matchOffsets = newOffsets;
}
if (re->match(string, offset, matchOffsets + oldSize) == JSC::Yarr::offsetNoMatch) {
nMatchOffsets = oldSize;
break;
}
nMatchOffsets += re->captureCount() * 2;
if (!regExp->global())
break;
offset = qMax(offset + 1, matchOffsets[oldSize + 1]);
}
if (regExp->global()) {
regExp->setLastIndex(0);
if (scope.hasException())
return Encode::undefined();
}
numStringMatches = nMatchOffsets / (regExp->value()->captureCount() * 2);
numCaptures = regExp->value()->captureCount();
} else {
numCaptures = 1;
QString searchString = searchValue->toQString();
int idx = string.indexOf(searchString);
if (idx != -1) {
numStringMatches = 1;
matchOffsets[0] = idx;
matchOffsets[1] = idx + searchString.length();
}
}
QString result;
ScopedValue replacement(scope);
ScopedValue replaceValue(scope, argc > 1 ? argv[1] : Value::undefinedValue());
ScopedFunctionObject searchCallback(scope, replaceValue);
if (!!searchCallback) {
result.reserve(string.length() + 10*numStringMatches);
ScopedValue entry(scope);
Value *arguments = scope.alloc(numCaptures + 2);
int lastEnd = 0;
for (int i = 0; i < numStringMatches; ++i) {
for (int k = 0; k < numCaptures; ++k) {
int idx = (i * numCaptures + k) * 2;
uint start = matchOffsets[idx];
uint end = matchOffsets[idx + 1];
entry = Value::undefinedValue();
if (start != JSC::Yarr::offsetNoMatch && end != JSC::Yarr::offsetNoMatch)
entry = scope.engine->newString(string.mid(start, end - start));
arguments[k] = entry;
}
uint matchStart = matchOffsets[i * numCaptures * 2];
Q_ASSERT(matchStart >= static_cast<uint>(lastEnd));
uint matchEnd = matchOffsets[i * numCaptures * 2 + 1];
arguments[numCaptures] = Value::fromUInt32(matchStart);
arguments[numCaptures + 1] = scope.engine->newString(string);
Value that = Value::undefinedValue();
replacement = searchCallback->call(&that, arguments, numCaptures + 2);
result += string.midRef(lastEnd, matchStart - lastEnd);
result += replacement->toQString();
lastEnd = matchEnd;
}
result += string.midRef(lastEnd);
} else {
QString newString = replaceValue->toQString();
result.reserve(string.length() + numStringMatches*newString.size());
int lastEnd = 0;
for (int i = 0; i < numStringMatches; ++i) {
int baseIndex = i * numCaptures * 2;
uint matchStart = matchOffsets[baseIndex];
uint matchEnd = matchOffsets[baseIndex + 1];
if (matchStart == JSC::Yarr::offsetNoMatch)
continue;
result += string.midRef(lastEnd, matchStart - lastEnd);
appendReplacementString(&result, string, newString, matchOffsets + baseIndex, numCaptures);
lastEnd = matchEnd;
}
result += string.midRef(lastEnd);
}
if (matchOffsets != _matchOffsets)
free(matchOffsets);
return Encode(scope.engine->newString(result));
}
ReturnedValue StringPrototype::method_search(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
Scope scope(b);
QString string = getThisString(scope.engine, thisObject);
if (scope.engine->hasException)
return QV4::Encode::undefined();
Scoped<RegExpObject> regExp(scope, argc ? argv[0] : Value::undefinedValue());
if (!regExp) {
regExp = scope.engine->regExpCtor()->callAsConstructor(argv, 1);
if (scope.engine->hasException)
return QV4::Encode::undefined();
Q_ASSERT(regExp);
}
Scoped<RegExp> re(scope, regExp->value());
Q_ALLOCA_VAR(uint, matchOffsets, regExp->value()->captureCount() * 2 * sizeof(uint));
uint result = re->match(string, /*offset*/0, matchOffsets);
if (result == JSC::Yarr::offsetNoMatch)
return Encode(-1);
else
return Encode(result);
}
ReturnedValue StringPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
Scope scope(v4);
ScopedString s(scope, thisAsString(v4, thisObject));
if (v4->hasException)
return QV4::Encode::undefined();
Q_ASSERT(s);
const double length = s->d()->length();
double start = argc ? argv[0].toInteger() : 0;
double end = (argc < 2 || argv[1].isUndefined())
? length : argv[1].toInteger();
if (start < 0)
start = qMax(length + start, 0.);
else
start = qMin(start, length);
if (end < 0)
end = qMax(length + end, 0.);
else
end = qMin(end, length);
const int intStart = int(start);
const int intEnd = int(end);
int count = qMax(0, intEnd - intStart);
return Encode(v4->memoryManager->alloc<ComplexString>(s->d(), intStart, count));
}
ReturnedValue StringPrototype::method_split(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
QString text = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
Scope scope(v4);
ScopedValue separatorValue(scope, argc ? argv[0] : Value::undefinedValue());
ScopedValue limitValue(scope, argc > 1 ? argv[1] : Value::undefinedValue());
ScopedArrayObject array(scope, scope.engine->newArrayObject());
if (separatorValue->isUndefined()) {
if (limitValue->isUndefined()) {
ScopedString s(scope, scope.engine->newString(text));
array->push_back(s);
return array.asReturnedValue();
}
RETURN_RESULT(scope.engine->newString(text.left(limitValue->toInteger())));
}
uint limit = limitValue->isUndefined() ? UINT_MAX : limitValue->toUInt32();
if (limit == 0)
return array.asReturnedValue();
Scoped<RegExpObject> re(scope, separatorValue);
if (re) {
if (re->value()->pattern->isEmpty()) {
re = (RegExpObject *)nullptr;
separatorValue = scope.engine->newString();
}
}
ScopedString s(scope);
if (re) {
uint offset = 0;
Q_ALLOCA_VAR(uint, matchOffsets, re->value()->captureCount() * 2 * sizeof(uint));
while (true) {
Scoped<RegExp> regexp(scope, re->value());
uint result = regexp->match(text, offset, matchOffsets);
if (result == JSC::Yarr::offsetNoMatch)
break;
array->push_back((s = scope.engine->newString(text.mid(offset, matchOffsets[0] - offset))));
offset = qMax(offset + 1, matchOffsets[1]);
if (array->getLength() >= limit)
break;
for (int i = 1; i < re->value()->captureCount(); ++i) {
uint start = matchOffsets[i * 2];
uint end = matchOffsets[i * 2 + 1];
array->push_back((s = scope.engine->newString(text.mid(start, end - start))));
if (array->getLength() >= limit)
break;
}
}
if (array->getLength() < limit)
array->push_back((s = scope.engine->newString(text.mid(offset))));
} else {
QString separator = separatorValue->toQString();
if (separator.isEmpty()) {
for (uint i = 0; i < qMin(limit, uint(text.length())); ++i)
array->push_back((s = scope.engine->newString(text.mid(i, 1))));
return array.asReturnedValue();
}
int start = 0;
int end;
while ((end = text.indexOf(separator, start)) != -1) {
array->push_back((s = scope.engine->newString(text.mid(start, end - start))));
start = end + separator.size();
if (array->getLength() >= limit)
break;
}
if (array->getLength() < limit && start != -1)
array->push_back((s = scope.engine->newString(text.mid(start))));
}
return array.asReturnedValue();
}
ReturnedValue StringPrototype::method_startsWith(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
const QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
if (argc && argv[0].as<RegExpObject>())
return v4->throwTypeError();
QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
if (v4->hasException)
return Encode::undefined();
int pos = 0;
if (argc > 1)
pos = (int) argv[1].toInteger();
if (pos == 0)
return Encode(value.startsWith(searchString));
QStringRef stringToSearch = value.midRef(pos);
RETURN_RESULT(Encode(stringToSearch.startsWith(searchString)));
}
ReturnedValue StringPrototype::method_substr(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
const QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
double start = 0;
if (argc > 0)
start = argv[0].toInteger();
double length = +qInf();
if (argc > 1)
length = argv[1].toInteger();
double count = value.length();
if (start < 0)
start = qMax(count + start, 0.0);
length = qMin(qMax(length, 0.0), count - start);
qint32 x = Value::toInt32(start);
qint32 y = Value::toInt32(length);
return Encode(v4->newString(value.mid(x, y)));
}
ReturnedValue StringPrototype::method_substring(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
const QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
int length = value.length();
double start = 0;
double end = length;
if (argc > 0)
start = argv[0].toInteger();
if (argc > 1 && !argv[1].isUndefined())
end = argv[1].toInteger();
if (std::isnan(start) || start < 0)
start = 0;
if (std::isnan(end) || end < 0)
end = 0;
if (start > length)
start = length;
if (end > length)
end = length;
if (start > end) {
double was = start;
start = end;
end = was;
}
qint32 x = (int)start;
qint32 y = (int)(end - start);
return Encode(v4->newString(value.mid(x, y)));
}
ReturnedValue StringPrototype::method_toLowerCase(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
ExecutionEngine *v4 = b->engine();
const QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
return Encode(v4->newString(value.toLower()));
}
ReturnedValue StringPrototype::method_toLocaleLowerCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
return method_toLowerCase(b, thisObject, argv, argc);
}
ReturnedValue StringPrototype::method_toUpperCase(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
ExecutionEngine *v4 = b->engine();
const QString value = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
return Encode(v4->newString(value.toUpper()));
}
ReturnedValue StringPrototype::method_toLocaleUpperCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
return method_toUpperCase(b, thisObject, argv, argc);
}
ReturnedValue StringPrototype::method_trim(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
ExecutionEngine *v4 = b->engine();
QString s = getThisString(v4, thisObject);
if (v4->hasException)
return QV4::Encode::undefined();
const QChar *chars = s.constData();
int start, end;
for (start = 0; start < s.length(); ++start) {
if (!chars[start].isSpace() && chars[start].unicode() != 0xfeff)
break;
}
for (end = s.length() - 1; end >= start; --end) {
if (!chars[end].isSpace() && chars[end].unicode() != 0xfeff)
break;
}
return Encode(v4->newString(QString(chars + start, end - start + 1)));
}
ReturnedValue StringPrototype::method_iterator(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
Scope scope(b);
ScopedString s(scope, thisObject->toString(scope.engine));
if (!s || thisObject->isNullOrUndefined())
return scope.engine->throwTypeError();
Scoped<StringIteratorObject> si(scope, scope.engine->memoryManager->allocate<StringIteratorObject>(s->d(), scope.engine));
return si->asReturnedValue();
}