blob: a8a37cda371f306d57115ce75ac2164ff9cf8893 [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 "qv4regexpobject_p.h"
#include "qv4objectproto_p.h"
#include "qv4regexp_p.h"
#include "qv4stringobject_p.h"
#include <private/qv4mm_p.h>
#include "qv4scopedvalue_p.h"
#include "qv4jscall_p.h"
#include "qv4symbol_p.h"
#include "private/qlocale_tools_p.h"
#include <QtCore/QDebug>
#include <QtCore/qregexp.h>
#if QT_CONFIG(regularexpression)
#include <QtCore/qregularexpression.h>
#endif
#include <cassert>
#include <typeinfo>
#include <iostream>
#include <private/qv4alloca_p.h>
QT_BEGIN_NAMESPACE
Q_CORE_EXPORT QString qt_regexp_toCanonical(const QString &, QRegExp::PatternSyntax);
using namespace QV4;
DEFINE_OBJECT_VTABLE(RegExpObject);
void Heap::RegExpObject::init()
{
Object::init();
Scope scope(internalClass->engine);
Scoped<QV4::RegExpObject> o(scope, this);
value.set(scope.engine, QV4::RegExp::create(scope.engine, QString(), CompiledData::RegExp::RegExp_NoFlags));
o->initProperties();
}
void Heap::RegExpObject::init(QV4::RegExp *value)
{
Object::init();
Scope scope(internalClass->engine);
this->value.set(scope.engine, value->d());
Scoped<QV4::RegExpObject> o(scope, this);
o->initProperties();
}
// Converts a QRegExp to a JS RegExp.
// The conversion is not 100% exact since ECMA regexp and QRegExp
// have different semantics/flags, but we try to do our best.
void Heap::RegExpObject::init(const QRegExp &re)
{
Object::init();
// Convert the pattern to a ECMAScript pattern.
QString pattern = QT_PREPEND_NAMESPACE(qt_regexp_toCanonical)(re.pattern(), re.patternSyntax());
if (re.isMinimal()) {
QString ecmaPattern;
int len = pattern.length();
ecmaPattern.reserve(len);
int i = 0;
const QChar *wc = pattern.unicode();
bool inBracket = false;
while (i < len) {
QChar c = wc[i++];
ecmaPattern += c;
switch (c.unicode()) {
case '?':
case '+':
case '*':
case '}':
if (!inBracket)
ecmaPattern += QLatin1Char('?');
break;
case '\\':
if (i < len)
ecmaPattern += wc[i++];
break;
case '[':
inBracket = true;
break;
case ']':
inBracket = false;
break;
default:
break;
}
}
pattern = ecmaPattern;
}
Scope scope(internalClass->engine);
Scoped<QV4::RegExpObject> o(scope, this);
uint flags = (re.caseSensitivity() == Qt::CaseInsensitive ? CompiledData::RegExp::RegExp_IgnoreCase : CompiledData::RegExp::RegExp_NoFlags);
o->d()->value.set(scope.engine, QV4::RegExp::create(scope.engine, pattern, flags));
o->initProperties();
}
#if QT_CONFIG(regularexpression)
// Converts a QRegularExpression to a JS RegExp.
// The conversion is not 100% exact since ECMA regexp and QRegularExpression
// have different semantics/flags, but we try to do our best.
void Heap::RegExpObject::init(const QRegularExpression &re)
{
Object::init();
Scope scope(internalClass->engine);
Scoped<QV4::RegExpObject> o(scope, this);
const uint flags = (re.patternOptions() & QRegularExpression::CaseInsensitiveOption)
? CompiledData::RegExp::RegExp_IgnoreCase
: CompiledData::RegExp::RegExp_NoFlags;
o->d()->value.set(scope.engine, QV4::RegExp::create(scope.engine, re.pattern(), flags));
o->initProperties();
}
#endif
void RegExpObject::initProperties()
{
setProperty(Index_LastIndex, Value::fromInt32(0));
Q_ASSERT(value());
}
// Converts a JS RegExp to a QRegExp.
// The conversion is not 100% exact since ECMA regexp and QRegExp
// have different semantics/flags, but we try to do our best.
QRegExp RegExpObject::toQRegExp() const
{
Qt::CaseSensitivity caseSensitivity = (value()->flags & CompiledData::RegExp::RegExp_IgnoreCase) ? Qt::CaseInsensitive : Qt::CaseSensitive;
return QRegExp(*value()->pattern, caseSensitivity, QRegExp::RegExp2);
}
#if QT_CONFIG(regularexpression)
// Converts a JS RegExp to a QRegularExpression.
// The conversion is not 100% exact since ECMA regexp and QRegularExpression
// have different semantics/flags, but we try to do our best.
QRegularExpression RegExpObject::toQRegularExpression() const
{
QRegularExpression::PatternOptions caseSensitivity
= (value()->flags & CompiledData::RegExp::RegExp_IgnoreCase)
? QRegularExpression::CaseInsensitiveOption
: QRegularExpression::NoPatternOption;
return QRegularExpression(*value()->pattern, caseSensitivity);
}
#endif
QString RegExpObject::toString() const
{
QString p = *value()->pattern;
if (p.isEmpty()) {
p = QStringLiteral("(?:)");
} else {
// escape certain parts, see ch. 15.10.4
p.replace('/', QLatin1String("\\/"));
}
return p;
}
ReturnedValue RegExpObject::builtinExec(ExecutionEngine *engine, const String *str)
{
QString s = str->toQString();
Scope scope(engine);
int offset = (global() || sticky()) ? lastIndex() : 0;
if (offset < 0 || offset > s.length()) {
setLastIndex(0);
RETURN_RESULT(Encode::null());
}
Q_ALLOCA_VAR(uint, matchOffsets, value()->captureCount() * 2 * sizeof(uint));
const uint result = Scoped<RegExp>(scope, value())->match(s, offset, matchOffsets);
RegExpCtor *regExpCtor = static_cast<RegExpCtor *>(scope.engine->regExpCtor());
regExpCtor->d()->clearLastMatch();
if (result == JSC::Yarr::offsetNoMatch) {
if (global() || sticky())
setLastIndex(0);
RETURN_RESULT(Encode::null());
}
Q_ASSERT(result <= uint(std::numeric_limits<int>::max()));
// fill in result data
ScopedArrayObject array(scope, scope.engine->newArrayObject(scope.engine->internalClasses(EngineBase::Class_RegExpExecArray)));
int len = value()->captureCount();
array->arrayReserve(len);
ScopedValue v(scope);
int strlen = s.length();
for (int i = 0; i < len; ++i) {
int start = matchOffsets[i * 2];
int end = matchOffsets[i * 2 + 1];
if (end > strlen)
end = strlen;
v = (start != -1) ? scope.engine->memoryManager->alloc<ComplexString>(str->d(), start, end - start)->asReturnedValue() : Encode::undefined();
array->arrayPut(i, v);
}
array->setArrayLengthUnchecked(len);
array->setProperty(Index_ArrayIndex, Value::fromInt32(int(result)));
array->setProperty(Index_ArrayInput, *str);
RegExpCtor::Data *dd = regExpCtor->d();
dd->lastMatch.set(scope.engine, array);
dd->lastInput.set(scope.engine, str->d());
dd->lastMatchStart = matchOffsets[0];
dd->lastMatchEnd = matchOffsets[1];
if (global() || sticky())
setLastIndex(matchOffsets[1]);
return array.asReturnedValue();
}
DEFINE_OBJECT_VTABLE(RegExpCtor);
void Heap::RegExpCtor::init(QV4::ExecutionContext *scope)
{
Heap::FunctionObject::init(scope, QStringLiteral("RegExp"));
clearLastMatch();
}
void Heap::RegExpCtor::clearLastMatch()
{
lastMatch.set(internalClass->engine, Value::nullValue());
lastInput.set(internalClass->engine, internalClass->engine->id_empty()->d());
lastMatchStart = 0;
lastMatchEnd = 0;
}
static bool isRegExp(ExecutionEngine *e, const QV4::Value *arg)
{
const QV4::Object *o = arg->objectValue();
if (!o)
return false;
QV4::Value isRegExp = QV4::Value::fromReturnedValue(o->get(e->symbol_match()));
if (!isRegExp.isUndefined())
return isRegExp.toBoolean();
const RegExpObject *re = o->as<RegExpObject>();
return re ? true : false;
}
uint parseFlags(Scope &scope, const QV4::Value *f)
{
uint flags = CompiledData::RegExp::RegExp_NoFlags;
if (!f->isUndefined()) {
ScopedString s(scope, f->toString(scope.engine));
if (scope.hasException())
return flags;
QString str = s->toQString();
for (int i = 0; i < str.length(); ++i) {
if (str.at(i) == QLatin1Char('g') && !(flags & CompiledData::RegExp::RegExp_Global)) {
flags |= CompiledData::RegExp::RegExp_Global;
} else if (str.at(i) == QLatin1Char('i') && !(flags & CompiledData::RegExp::RegExp_IgnoreCase)) {
flags |= CompiledData::RegExp::RegExp_IgnoreCase;
} else if (str.at(i) == QLatin1Char('m') && !(flags & CompiledData::RegExp::RegExp_Multiline)) {
flags |= CompiledData::RegExp::RegExp_Multiline;
} else if (str.at(i) == QLatin1Char('u') && !(flags & CompiledData::RegExp::RegExp_Unicode)) {
flags |= CompiledData::RegExp::RegExp_Unicode;
} else if (str.at(i) == QLatin1Char('y') && !(flags & CompiledData::RegExp::RegExp_Sticky)) {
flags |= CompiledData::RegExp::RegExp_Sticky;
} else {
scope.engine->throwSyntaxError(QStringLiteral("Invalid flags supplied to RegExp constructor"));
return flags;
}
}
}
return flags;
}
ReturnedValue RegExpCtor::virtualCallAsConstructor(const FunctionObject *fo, const Value *argv, int argc, const Value *newTarget)
{
Scope scope(fo);
bool patternIsRegExp = argc ? ::isRegExp(scope.engine, argv) : false;
if (newTarget == fo) {
if (patternIsRegExp && (argc < 2 || argv[1].isUndefined())) {
const Object *pattern = static_cast<const Object *>(argv);
ScopedValue patternConstructor(scope, pattern->get(scope.engine->id_constructor()));
if (patternConstructor->sameValue(*newTarget))
return pattern->asReturnedValue();
}
}
ScopedValue p(scope, argc ? argv[0] : Value::undefinedValue());
ScopedValue f(scope, argc > 1 ? argv[1] : Value::undefinedValue());
Scoped<RegExpObject> re(scope, p);
QString pattern;
uint flags = CompiledData::RegExp::RegExp_NoFlags;
if (re) {
if (f->isUndefined()) {
Scoped<RegExp> regexp(scope, re->value());
return Encode(scope.engine->newRegExpObject(regexp));
}
pattern = *re->value()->pattern;
flags = parseFlags(scope, f);
} else if (patternIsRegExp) {
const Object *po = static_cast<const Object *>(argv);
p = po->get(scope.engine->id_source());
if (!p->isUndefined())
pattern = p->toQString();
if (scope.hasException())
return Encode::undefined();
if (f->isUndefined())
f = po->get(scope.engine->id_flags());
flags = parseFlags(scope, f);
} else {
if (!p->isUndefined())
pattern = p->toQString();
if (scope.hasException())
return Encode::undefined();
flags = parseFlags(scope, f);
}
if (scope.hasException())
return Encode::undefined();
Scoped<RegExp> regexp(scope, RegExp::create(scope.engine, pattern, flags));
if (!regexp->isValid()) {
return scope.engine->throwSyntaxError(QStringLiteral("Invalid regular expression"));
}
ReturnedValue o = Encode(scope.engine->newRegExpObject(regexp));
if (!newTarget)
return o;
ScopedObject obj(scope, o);
obj->setProtoFromNewTarget(newTarget);
return obj->asReturnedValue();
}
ReturnedValue RegExpCtor::virtualCall(const FunctionObject *f, const Value *, const Value *argv, int argc)
{
return virtualCallAsConstructor(f, argv, argc, f);
}
void RegExpPrototype::init(ExecutionEngine *engine, Object *constructor)
{
Scope scope(engine);
ScopedObject o(scope);
ScopedObject ctor(scope, constructor);
ctor->defineReadonlyProperty(engine->id_prototype(), (o = this));
ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(2));
ctor->addSymbolSpecies();
// Properties deprecated in the spec but required by "the web" :(
ctor->defineAccessorProperty(QStringLiteral("lastMatch"), method_get_lastMatch_n<0>, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$&"), method_get_lastMatch_n<0>, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$1"), method_get_lastMatch_n<1>, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$2"), method_get_lastMatch_n<2>, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$3"), method_get_lastMatch_n<3>, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$4"), method_get_lastMatch_n<4>, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$5"), method_get_lastMatch_n<5>, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$6"), method_get_lastMatch_n<6>, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$7"), method_get_lastMatch_n<7>, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$8"), method_get_lastMatch_n<8>, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$9"), method_get_lastMatch_n<9>, nullptr);
ctor->defineAccessorProperty(QStringLiteral("lastParen"), method_get_lastParen, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$+"), method_get_lastParen, nullptr);
ctor->defineAccessorProperty(QStringLiteral("input"), method_get_input, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$_"), method_get_input, nullptr);
ctor->defineAccessorProperty(QStringLiteral("leftContext"), method_get_leftContext, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$`"), method_get_leftContext, nullptr);
ctor->defineAccessorProperty(QStringLiteral("rightContext"), method_get_rightContext, nullptr);
ctor->defineAccessorProperty(QStringLiteral("$'"), method_get_rightContext, nullptr);
defineDefaultProperty(QStringLiteral("constructor"), (o = ctor));
defineAccessorProperty(scope.engine->id_flags(), method_get_flags, nullptr);
defineAccessorProperty(scope.engine->id_global(), method_get_global, nullptr);
defineAccessorProperty(scope.engine->id_ignoreCase(), method_get_ignoreCase, nullptr);
defineDefaultProperty(QStringLiteral("exec"), method_exec, 1);
defineDefaultProperty(engine->symbol_match(), method_match, 1);
defineAccessorProperty(scope.engine->id_multiline(), method_get_multiline, nullptr);
defineDefaultProperty(engine->symbol_replace(), method_replace, 2);
defineDefaultProperty(engine->symbol_search(), method_search, 1);
defineAccessorProperty(scope.engine->id_source(), method_get_source, nullptr);
defineDefaultProperty(engine->symbol_split(), method_split, 2);
defineAccessorProperty(scope.engine->id_sticky(), method_get_sticky, nullptr);
defineDefaultProperty(QStringLiteral("test"), method_test, 1);
defineDefaultProperty(engine->id_toString(), method_toString, 0);
defineAccessorProperty(scope.engine->id_unicode(), method_get_unicode, nullptr);
// another web extension
defineDefaultProperty(QStringLiteral("compile"), method_compile, 2);
}
/* used by String.match */
ReturnedValue RegExpPrototype::execFirstMatch(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
Scope scope(b);
Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>());
Q_ASSERT(r && r->global());
ScopedString str(scope, argc ? argv[0] : Value::undefinedValue());
Q_ASSERT(str);
QString s = str->toQString();
int offset = r->lastIndex();
if (offset < 0 || offset > s.length()) {
r->setLastIndex(0);
RETURN_RESULT(Encode::null());
}
Q_ALLOCA_VAR(uint, matchOffsets, r->value()->captureCount() * 2 * sizeof(uint));
const int result = Scoped<RegExp>(scope, r->value())->match(s, offset, matchOffsets);
RegExpCtor *regExpCtor = static_cast<RegExpCtor *>(scope.engine->regExpCtor());
regExpCtor->d()->clearLastMatch();
if (result == -1) {
r->setLastIndex(0);
RETURN_RESULT(Encode::null());
}
ReturnedValue retVal = Encode::undefined();
// return first match
if (r->value()->captureCount()) {
int start = matchOffsets[0];
int end = matchOffsets[1];
retVal = (start != -1) ? scope.engine->memoryManager->alloc<ComplexString>(str->d(), start, end - start)->asReturnedValue() : Encode::undefined();
}
RegExpCtor::Data *dd = regExpCtor->d();
dd->lastInput.set(scope.engine, str->d());
dd->lastMatchStart = matchOffsets[0];
dd->lastMatchEnd = matchOffsets[1];
r->setLastIndex(matchOffsets[1]);
return retVal;
}
ReturnedValue RegExpPrototype::exec(ExecutionEngine *engine, const Object *o, const String *s)
{
Scope scope(engine);
ScopedString key(scope, scope.engine->newString(QStringLiteral("exec")));
ScopedFunctionObject exec(scope, o->get(key));
if (exec) {
ScopedValue result(scope, exec->call(o, s, 1));
if (scope.hasException())
RETURN_UNDEFINED();
if (!result->isNull() && !result->isObject())
return scope.engine->throwTypeError();
return result->asReturnedValue();
}
Scoped<RegExpObject> re(scope, o);
if (!re)
return scope.engine->throwTypeError();
return re->builtinExec(engine, s);
}
ReturnedValue RegExpPrototype::method_exec(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
Scope scope(b);
Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>());
if (!r)
return scope.engine->throwTypeError();
ScopedValue arg(scope, argc ? argv[0]: Value::undefinedValue());
ScopedString str(scope, arg->toString(scope.engine));
if (scope.hasException())
RETURN_UNDEFINED();
return r->builtinExec(scope.engine, str);
}
ReturnedValue RegExpPrototype::method_get_flags(const FunctionObject *f, const Value *thisObject, const Value *, int)
{
Scope scope(f);
ScopedObject o(scope, thisObject);
if (!o)
return scope.engine->throwTypeError();
QString result;
ScopedValue v(scope);
v = o->get(scope.engine->id_global());
if (scope.hasException())
return Encode::undefined();
if (v->toBoolean())
result += QLatin1Char('g');
v = o->get(scope.engine->id_ignoreCase());
if (scope.hasException())
return Encode::undefined();
if (v->toBoolean())
result += QLatin1Char('i');
v = o->get(scope.engine->id_multiline());
if (scope.hasException())
return Encode::undefined();
if (v->toBoolean())
result += QLatin1Char('m');
v = o->get(scope.engine->id_unicode());
if (scope.hasException())
return Encode::undefined();
if (v->toBoolean())
result += QLatin1Char('u');
v = o->get(scope.engine->id_sticky());
if (scope.hasException())
return Encode::undefined();
if (v->toBoolean())
result += QLatin1Char('y');
return scope.engine->newString(result)->asReturnedValue();
}
ReturnedValue RegExpPrototype::method_get_global(const FunctionObject *f, const Value *thisObject, const Value *, int)
{
Scope scope(f);
Scoped<RegExpObject> re(scope, thisObject);
if (!re) {
if (thisObject->sameValue(*scope.engine->regExpPrototype()))
return Encode::undefined();
return scope.engine->throwTypeError();
}
bool b = re->value()->flags & CompiledData::RegExp::RegExp_Global;
return Encode(b);
}
ReturnedValue RegExpPrototype::method_get_ignoreCase(const FunctionObject *f, const Value *thisObject, const Value *, int)
{
Scope scope(f);
Scoped<RegExpObject> re(scope, thisObject);
if (!re) {
if (thisObject->sameValue(*scope.engine->regExpPrototype()))
return Encode::undefined();
return scope.engine->throwTypeError();
}
bool b = re->value()->flags & CompiledData::RegExp::RegExp_IgnoreCase;
return Encode(b);
}
static int advanceStringIndex(int index, const QString &str, bool unicode)
{
if (unicode) {
if (index < str.length() - 1 &&
str.at(index).isHighSurrogate() &&
str.at(index + 1).isLowSurrogate())
++index;
}
++index;
return index;
}
static void advanceLastIndexOnEmptyMatch(ExecutionEngine *e, bool unicode, QV4::Object *rx, const String *matchString, const QString &str)
{
Scope scope(e);
if (matchString->d()->length() == 0) {
QV4::ScopedValue v(scope, rx->get(scope.engine->id_lastIndex()));
int lastIndex = advanceStringIndex(v->toLength(), str, unicode);
if (!rx->put(scope.engine->id_lastIndex(), QV4::Value::fromInt32(lastIndex)))
scope.engine->throwTypeError();
}
}
ReturnedValue RegExpPrototype::method_match(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
Scope scope(f);
ScopedObject rx(scope, thisObject);
if (!rx)
return scope.engine->throwTypeError();
ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine));
if (scope.hasException())
return Encode::undefined();
bool global = ScopedValue(scope, rx->get(scope.engine->id_global()))->toBoolean();
if (!global)
return exec(scope.engine, rx, s);
bool unicode = ScopedValue(scope, rx->get(scope.engine->id_unicode()))->toBoolean();
rx->put(scope.engine->id_lastIndex(), Value::fromInt32(0));
ScopedArrayObject a(scope, scope.engine->newArrayObject());
uint n = 0;
ScopedValue result(scope);
ScopedValue match(scope);
ScopedString matchString(scope);
ScopedValue v(scope);
while (1) {
result = exec(scope.engine, rx, s);
if (scope.hasException())
return Encode::undefined();
if (result->isNull()) {
if (!n)
return Encode::null();
return a->asReturnedValue();
}
Q_ASSERT(result->isObject());
match = static_cast<Object &>(*result).get(PropertyKey::fromArrayIndex(0));
matchString = match->toString(scope.engine);
if (scope.hasException())
return Encode::undefined();
a->push_back(matchString);
advanceLastIndexOnEmptyMatch(scope.engine, unicode, rx, matchString, s->toQString());
++n;
}
}
ReturnedValue RegExpPrototype::method_get_multiline(const FunctionObject *f, const Value *thisObject, const Value *, int)
{
Scope scope(f);
Scoped<RegExpObject> re(scope, thisObject);
if (!re) {
if (thisObject->sameValue(*scope.engine->regExpPrototype()))
return Encode::undefined();
return scope.engine->throwTypeError();
}
bool b = re->value()->flags & CompiledData::RegExp::RegExp_Multiline;
return Encode(b);
}
ReturnedValue RegExpPrototype::method_replace(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
Scope scope(f);
ScopedObject rx(scope, thisObject);
if (!rx)
return scope.engine->throwTypeError();
ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine));
if (scope.hasException())
return Encode::undefined();
int lengthS = s->toQString().length();
ScopedString replaceValue(scope);
ScopedFunctionObject replaceFunction(scope, (argc > 1 ? argv[1] : Value::undefinedValue()));
bool functionalReplace = !!replaceFunction;
if (!functionalReplace)
replaceValue = (argc > 1 ? argv[1] : Value::undefinedValue()).toString(scope.engine);
ScopedValue v(scope);
bool global = (v = rx->get(scope.engine->id_global()))->toBoolean();
bool unicode = false;
if (global) {
unicode = (v = rx->get(scope.engine->id_unicode()))->toBoolean();
if (!rx->put(scope.engine->id_lastIndex(), Value::fromInt32(0)))
return scope.engine->throwTypeError();
}
ScopedArrayObject results(scope, scope.engine->newArrayObject());
ScopedValue result(scope);
ScopedValue match(scope);
ScopedString matchString(scope);
while (1) {
result = exec(scope.engine, rx, s);
if (scope.hasException())
return Encode::undefined();
if (result->isNull())
break;
results->push_back(result);
if (!global)
break;
match = static_cast<Object &>(*result).get(PropertyKey::fromArrayIndex(0));
matchString = match->toString(scope.engine);
if (scope.hasException())
return Encode::undefined();
advanceLastIndexOnEmptyMatch(scope.engine, unicode, rx, matchString, s->toQString());
}
QString accumulatedResult;
int nextSourcePosition = 0;
int resultsLength = results->getLength();
ScopedObject resultObject(scope);
for (int i = 0; i < resultsLength; ++i) {
resultObject = results->get(PropertyKey::fromArrayIndex(i));
if (scope.hasException())
return Encode::undefined();
int nCaptures = resultObject->getLength();
nCaptures = qMax(nCaptures - 1, 0);
match = resultObject->get(PropertyKey::fromArrayIndex(0));
matchString = match->toString(scope.engine);
if (scope.hasException())
return Encode::undefined();
QString m = matchString->toQString();
int matchLength = m.length();
v = resultObject->get(scope.engine->id_index());
int position = v->toInt32();
position = qMax(qMin(position, lengthS), 0);
if (scope.hasException())
return Encode::undefined();
int n = 1;
Scope innerScope(scope.engine);
JSCallData cData(scope, nCaptures + 3);
while (n <= nCaptures) {
v = resultObject->get(PropertyKey::fromArrayIndex(n));
if (!v->isUndefined())
cData->args[n] = v->toString(scope.engine);
++n;
}
QString replacement;
if (functionalReplace) {
cData->args[0] = matchString;
cData->args[nCaptures + 1] = Encode(position);
cData->args[nCaptures + 2] = s;
ScopedValue replValue(scope, replaceFunction->call(cData));
if (scope.hasException())
return Encode::undefined();
replacement = replValue->toQString();
} else {
replacement = RegExp::getSubstitution(matchString->toQString(), s->toQString(), position, cData.args, nCaptures, replaceValue->toQString());
}
if (scope.hasException())
return Encode::undefined();
if (position >= nextSourcePosition) {
accumulatedResult += s->toQString().midRef(nextSourcePosition, position - nextSourcePosition) + replacement;
nextSourcePosition = position + matchLength;
}
}
if (nextSourcePosition < lengthS) {
accumulatedResult += s->toQString().midRef(nextSourcePosition);
}
return scope.engine->newString(accumulatedResult)->asReturnedValue();
}
ReturnedValue RegExpPrototype::method_search(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
Scope scope(f);
ScopedObject rx(scope, thisObject);
if (!rx)
return scope.engine->throwTypeError();
ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine));
if (scope.hasException())
return Encode::undefined();
ScopedValue previousLastIndex(scope, rx->get(scope.engine->id_lastIndex()));
if (previousLastIndex->toNumber() != 0) {
if (!rx->put(scope.engine->id_lastIndex(), Value::fromInt32(0)))
return scope.engine->throwTypeError();
}
ScopedValue result(scope, exec(scope.engine, rx, s));
if (scope.hasException())
return Encode::undefined();
ScopedValue currentLastIndex(scope, rx->get(scope.engine->id_lastIndex()));
if (!currentLastIndex->sameValue(previousLastIndex)) {
if (!rx->put(scope.engine->id_lastIndex(), previousLastIndex))
return scope.engine->throwTypeError();
}
if (result->isNull())
return Encode(-1);
ScopedObject o(scope, result);
Q_ASSERT(o);
return o->get(scope.engine->id_index());
}
ReturnedValue RegExpPrototype::method_get_source(const FunctionObject *f, const Value *thisObject, const Value *, int)
{
Scope scope(f);
Scoped<RegExpObject> re(scope, thisObject);
if (!re) {
if (thisObject->sameValue(*scope.engine->regExpPrototype()))
return scope.engine->newString(QStringLiteral("(?:)"))->asReturnedValue();
return scope.engine->throwTypeError();
}
return scope.engine->newString(re->toString())->asReturnedValue();
}
ReturnedValue RegExpPrototype::method_split(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
Scope scope(f);
ScopedObject rx(scope, thisObject);
if (!rx)
return scope.engine->throwTypeError();
ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine));
if (scope.hasException())
return Encode::undefined();
ScopedValue flagsValue(scope, rx->get(scope.engine->id_flags()));
ScopedString flags(scope, flagsValue->toString(scope.engine));
if (scope.hasException())
return Encode::undefined();
QString flagsString = flags->toQString();
if (!flagsString.contains(QLatin1Char('y')))
flags = scope.engine->newString(flagsString + QLatin1Char('y'));
bool unicodeMatching = flagsString.contains(QLatin1Char('u'));
const FunctionObject *C = rx->speciesConstructor(scope, scope.engine->regExpCtor());
if (!C)
return Encode::undefined();
Value *args = scope.alloc(2);
args[0] = rx;
args[1] = flags;
ScopedObject splitter(scope, C->callAsConstructor(args, 2, f));
if (scope.hasException())
return Encode::undefined();
ScopedArrayObject A(scope, scope.engine->newArrayObject());
uint lengthA = 0;
uint limit = argc < 2 ? UINT_MAX : argv[1].toUInt32();
if (limit == 0)
return A->asReturnedValue();
QString S = s->toQString();
int size = S.length();
if (size == 0) {
ScopedValue z(scope, exec(scope.engine, splitter, s));
if (z->isNull())
A->push_back(s);
return A->asReturnedValue();
}
int p = 0;
int q = 0;
ScopedValue v(scope);
ScopedValue z(scope);
ScopedObject zz(scope);
ScopedString t(scope);
while (q < size) {
Value qq = Value::fromInt32(q);
if (!splitter->put(scope.engine->id_lastIndex(), qq))
return scope.engine->throwTypeError();
z = exec(scope.engine, splitter, s);
if (scope.hasException())
return Encode::undefined();
if (z->isNull()) {
q = advanceStringIndex(q, S, unicodeMatching);
continue;
}
v = splitter->get(scope.engine->id_lastIndex());
int e = qMin(v->toInt32(), size);
if (e == p) {
q = advanceStringIndex(q, S, unicodeMatching);
continue;
}
QString T = S.mid(p, q - p);
t = scope.engine->newString(T);
A->push_back(t);
++lengthA;
if (lengthA == limit)
return A->asReturnedValue();
p = e;
zz = *z;
uint numberOfCaptures = qMax(zz->getLength() - 1, 0ll);
for (uint i = 1; i <= numberOfCaptures; ++i) {
v = zz->get(PropertyKey::fromArrayIndex(i));
A->push_back(v);
++lengthA;
if (lengthA == limit)
return A->asReturnedValue();
}
q = p;
}
QString T = S.mid(p);
t = scope.engine->newString(T);
A->push_back(t);
return A->asReturnedValue();
}
ReturnedValue RegExpPrototype::method_get_sticky(const FunctionObject *f, const Value *thisObject, const Value *, int)
{
Scope scope(f);
Scoped<RegExpObject> re(scope, thisObject);
if (!re) {
if (thisObject->sameValue(*scope.engine->regExpPrototype()))
return Encode::undefined();
return scope.engine->throwTypeError();
}
bool b = re->value()->flags & CompiledData::RegExp::RegExp_Sticky;
return Encode(b);
}
ReturnedValue RegExpPrototype::method_test(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
Value res = Value::fromReturnedValue(method_exec(b, thisObject, argv, argc));
return Encode(!res.isNull());
}
ReturnedValue RegExpPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
Scope scope(b);
const Object *r = thisObject->as<Object>();
if (!r)
return scope.engine->throwTypeError();
ScopedValue v(scope);
v = r->get(scope.engine->id_source());
ScopedString source(scope, v->toString(scope.engine));
if (scope.hasException())
return Encode::undefined();
v = r->get(scope.engine->id_flags());
ScopedString flags(scope, v->toString(scope.engine));
if (scope.hasException())
return Encode::undefined();
QString result = QLatin1Char('/') + source->toQString() + QLatin1Char('/') + flags->toQString();
return Encode(scope.engine->newString(result));
}
ReturnedValue RegExpPrototype::method_get_unicode(const FunctionObject *f, const Value *thisObject, const Value *, int)
{
Scope scope(f);
Scoped<RegExpObject> re(scope, thisObject);
if (!re) {
if (thisObject->sameValue(*scope.engine->regExpPrototype()))
return Encode::undefined();
return scope.engine->throwTypeError();
}
bool b = re->value()->flags & CompiledData::RegExp::RegExp_Unicode;
return Encode(b);
}
ReturnedValue RegExpPrototype::method_compile(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
Scope scope(b);
Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>());
if (!r)
return scope.engine->throwTypeError();
Scoped<RegExpObject> re(scope, scope.engine->regExpCtor()->callAsConstructor(argv, argc));
if (re) // Otherwise the regexp constructor should have thrown an exception
r->d()->value.set(scope.engine, re->value());
return Encode::undefined();
}
template <uint index>
ReturnedValue RegExpPrototype::method_get_lastMatch_n(const FunctionObject *b, const Value *, const Value *, int)
{
Scope scope(b);
ScopedArrayObject lastMatch(scope, static_cast<RegExpCtor*>(scope.engine->regExpCtor())->lastMatch());
ScopedValue res(scope, lastMatch ? lastMatch->get(index) : Encode::undefined());
if (res->isUndefined())
res = scope.engine->newString();
return res->asReturnedValue();
}
ReturnedValue RegExpPrototype::method_get_lastParen(const FunctionObject *b, const Value *, const Value *, int)
{
Scope scope(b);
ScopedArrayObject lastMatch(scope, static_cast<RegExpCtor*>(scope.engine->regExpCtor())->lastMatch());
ScopedValue res(scope, lastMatch ? lastMatch->get(lastMatch->getLength() - 1) : Encode::undefined());
if (res->isUndefined())
res = scope.engine->newString();
return res->asReturnedValue();
}
ReturnedValue RegExpPrototype::method_get_input(const FunctionObject *b, const Value *, const Value *, int)
{
return static_cast<RegExpCtor*>(b->engine()->regExpCtor())->lastInput()->asReturnedValue();
}
ReturnedValue RegExpPrototype::method_get_leftContext(const FunctionObject *b, const Value *, const Value *, int)
{
Scope scope(b);
Scoped<RegExpCtor> regExpCtor(scope, scope.engine->regExpCtor());
QString lastInput = regExpCtor->lastInput()->toQString();
return Encode(scope.engine->newString(lastInput.left(regExpCtor->lastMatchStart())));
}
ReturnedValue RegExpPrototype::method_get_rightContext(const FunctionObject *b, const Value *, const Value *, int)
{
Scope scope(b);
Scoped<RegExpCtor> regExpCtor(scope, scope.engine->regExpCtor());
QString lastInput = regExpCtor->lastInput()->toQString();
return Encode(scope.engine->newString(lastInput.mid(regExpCtor->lastMatchEnd())));
}
QT_END_NAMESPACE