aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@qt.io>2018-08-08 15:04:42 +0200
committerSimon Hausmann <simon.hausmann@qt.io>2018-08-15 14:24:38 +0000
commit4bfd94c35e5099557cafcc9ae9b7d7a970089c9f (patch)
treeb221ef374ace6fca0eaa1f041d338145dc83b083 /src
parentcecd8be881034153fc6b92e96f152a4a0c189ae4 (diff)
Implement RegExp.prototype[Symbol.replace]
Change-Id: I5a2c9cb1e9dcca664526b3949671d72d2ffee427 Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/qml/jsruntime/qv4regexp.cpp54
-rw-r--r--src/qml/jsruntime/qv4regexp_p.h2
-rw-r--r--src/qml/jsruntime/qv4regexpobject.cpp139
-rw-r--r--src/qml/jsruntime/qv4regexpobject_p.h1
4 files changed, 181 insertions, 15 deletions
diff --git a/src/qml/jsruntime/qv4regexp.cpp b/src/qml/jsruntime/qv4regexp.cpp
index fe44720b8a..05c3b4c4ca 100644
--- a/src/qml/jsruntime/qv4regexp.cpp
+++ b/src/qml/jsruntime/qv4regexp.cpp
@@ -76,6 +76,60 @@ uint RegExp::match(const QString &string, int start, uint *matchOffsets)
return JSC::Yarr::interpret(byteCode(), s.characters16(), string.length(), start, matchOffsets);
}
+QString RegExp::getSubstitution(const QString &matched, const QString &str, int position, const Value *captures, int nCaptures, const QString &replacement)
+{
+ QString result;
+
+ int matchedLength = matched.length();
+ Q_ASSERT(position >= 0 && position <= str.length());
+ int tailPos = position + matchedLength;
+ int seenDollar = -1;
+ for (int i = 0; i < replacement.length(); ++i) {
+ QChar ch = replacement.at(i);
+ if (seenDollar >= 0) {
+ if (ch.unicode() == '$') {
+ result += QLatin1Char('$');
+ } else if (ch.unicode() == '&') {
+ result += matched;
+ } else if (ch.unicode() == '`') {
+ result += str.left(position);
+ } else if (ch.unicode() == '\'') {
+ result += str.mid(tailPos);
+ } else if (ch.unicode() >= '0' && ch.unicode() <= '9') {
+ int n = ch.unicode() - '0';
+ if (i + 1 < replacement.length()) {
+ ch = replacement.at(i + 1);
+ if (ch.unicode() >= '0' && ch.unicode() <= '9') {
+ n = n*10 + (ch.unicode() - '0');
+ ++i;
+ }
+ }
+ if (n > 0 && n <= nCaptures) {
+ String *s = captures[n].stringValue();
+ if (s)
+ result += s->toQString();
+ } else {
+ for (int j = seenDollar; j <= i; ++j)
+ result += replacement.at(j);
+ }
+ } else {
+ result += QLatin1Char('$');
+ result += ch;
+ }
+ seenDollar = -1;
+ } else {
+ if (ch == QLatin1Char('$')) {
+ seenDollar = i;
+ continue;
+ }
+ result += ch;
+ }
+ }
+ if (seenDollar >= 0)
+ result += QLatin1Char('$');
+ return result;
+}
+
QString Heap::RegExp::flagsAsString() const
{
QString result;
diff --git a/src/qml/jsruntime/qv4regexp_p.h b/src/qml/jsruntime/qv4regexp_p.h
index 1e35d141ca..b027ba7659 100644
--- a/src/qml/jsruntime/qv4regexp_p.h
+++ b/src/qml/jsruntime/qv4regexp_p.h
@@ -131,6 +131,8 @@ struct RegExp : public Managed
int captureCount() const { return subPatternCount() + 1; }
+ static QString getSubstitution(const QString &matched, const QString &str, int position, const Value *captures, int nCaptures, const QString &replacement);
+
friend class RegExpCache;
};
diff --git a/src/qml/jsruntime/qv4regexpobject.cpp b/src/qml/jsruntime/qv4regexpobject.cpp
index 63b0d1524f..d157350e00 100644
--- a/src/qml/jsruntime/qv4regexpobject.cpp
+++ b/src/qml/jsruntime/qv4regexpobject.cpp
@@ -342,6 +342,7 @@ void RegExpPrototype::init(ExecutionEngine *engine, Object *constructor)
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(QStringLiteral("source"), method_get_source, nullptr);
defineAccessorProperty(scope.engine->id_sticky(), method_get_sticky, nullptr);
@@ -490,6 +491,24 @@ ReturnedValue RegExpPrototype::method_get_ignoreCase(const FunctionObject *f, co
return Encode(b);
}
+static void advanceLastIndexOnEmptyMatch(ExecutionEngine *e, bool unicode, Object *rx, const String *matchString, const QString &str)
+{
+ Scope scope(e);
+ if (matchString->d()->length() == 0) {
+ ScopedValue v(scope, rx->get(scope.engine->id_lastIndex()));
+ int lastIndex = v->toLength();
+ if (unicode) {
+ if (lastIndex < str.length() - 1 &&
+ str.at(lastIndex).isHighSurrogate() &&
+ str.at(lastIndex + 1).isLowSurrogate())
+ ++lastIndex;
+ }
+ ++lastIndex;
+ if (!rx->put(scope.engine->id_lastIndex(), Primitive::fromInt32(lastIndex)))
+ scope.engine->throwTypeError();
+ }
+}
+
ReturnedValue RegExpPrototype::method_match(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
Scope scope(f);
@@ -524,25 +543,12 @@ ReturnedValue RegExpPrototype::method_match(const FunctionObject *f, const Value
return a->asReturnedValue();
}
Q_ASSERT(result->isObject());
- match = static_cast<Object &>(*result).getIndexed(0);
+ match = static_cast<Object &>(*result).get(PropertyKey::fromArrayIndex(0));
matchString = match->toString(scope.engine);
if (scope.hasException())
return Encode::undefined();
a->push_back(matchString);
- if (matchString->d()->length() == 0) {
- v = rx->get(scope.engine->id_lastIndex());
- int lastIndex = v->toLength();
- if (unicode) {
- QString str = s->toQString();
- if (lastIndex < str.length() - 1 &&
- str.at(lastIndex).isHighSurrogate() &&
- str.at(lastIndex + 1).isLowSurrogate())
- ++lastIndex;
- }
- ++lastIndex;
- if (!rx->put(scope.engine->id_lastIndex(), Primitive::fromInt32(lastIndex)))
- return scope.engine->throwTypeError();
- }
+ advanceLastIndexOnEmptyMatch(scope.engine, unicode, rx, matchString, s->toQString());
++n;
}
}
@@ -558,6 +564,108 @@ ReturnedValue RegExpPrototype::method_get_multiline(const FunctionObject *f, con
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] : Primitive::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] : Primitive::undefinedValue()));
+ bool functionalReplace = !!replaceFunction;
+ if (!functionalReplace)
+ replaceValue = (argc > 1 ? argv[1] : Primitive::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(), Primitive::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));
+ 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);
@@ -592,6 +700,7 @@ ReturnedValue RegExpPrototype::method_search(const FunctionObject *f, const Valu
return o->get(scope.engine->id_index());
}
+
ReturnedValue RegExpPrototype::method_get_source(const FunctionObject *f, const Value *thisObject, const Value *, int)
{
Scope scope(f);
diff --git a/src/qml/jsruntime/qv4regexpobject_p.h b/src/qml/jsruntime/qv4regexpobject_p.h
index 1aebbf3e58..2173981b66 100644
--- a/src/qml/jsruntime/qv4regexpobject_p.h
+++ b/src/qml/jsruntime/qv4regexpobject_p.h
@@ -166,6 +166,7 @@ struct RegExpPrototype: RegExpObject
static ReturnedValue method_get_ignoreCase(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_match(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_get_multiline(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
+ static ReturnedValue method_replace(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_search(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_get_source(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_get_sticky(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);