aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@qt.io>2020-04-02 09:28:23 +0200
committerLars Knoll <lars.knoll@qt.io>2020-04-03 21:01:45 +0200
commite1bc9db85149b89feb73f1690fd218de498b8b27 (patch)
tree1aa369e8fa0461e1629a177923b0433cb681e90b
parent3156fd64cccbc49588fbd71621a8e83b95b25eba (diff)
Improve conversion between JS RegExp and QRegularExpression
Add support for InvertedGreedinessOption and MultilineOption. Change-Id: I19dce6e356a7ec406640bb8858885cd576b4aa2f Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
-rw-r--r--src/qml/jsruntime/qv4regexpobject.cpp97
-rw-r--r--tests/auto/qml/qjsengine/tst_qjsengine.cpp57
2 files changed, 110 insertions, 44 deletions
diff --git a/src/qml/jsruntime/qv4regexpobject.cpp b/src/qml/jsruntime/qv4regexpobject.cpp
index a8a37cda37..f763237ea3 100644
--- a/src/qml/jsruntime/qv4regexpobject.cpp
+++ b/src/qml/jsruntime/qv4regexpobject.cpp
@@ -84,6 +84,42 @@ void Heap::RegExpObject::init(QV4::RegExp *value)
o->initProperties();
}
+static QString minimalPattern(const QString &pattern)
+{
+ 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;
+ }
+ }
+ return ecmaPattern;
+}
+
// 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.
@@ -93,40 +129,8 @@ void Heap::RegExpObject::init(const QRegExp &re)
// 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;
- }
+ if (re.isMinimal())
+ pattern = minimalPattern(pattern);
Scope scope(internalClass->engine);
Scoped<QV4::RegExpObject> o(scope, this);
@@ -148,10 +152,16 @@ void Heap::RegExpObject::init(const QRegularExpression &re)
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));
+ QRegularExpression::PatternOptions options = re.patternOptions();
+ uint flags = (options & QRegularExpression::CaseInsensitiveOption)
+ ? CompiledData::RegExp::RegExp_IgnoreCase
+ : CompiledData::RegExp::RegExp_NoFlags;
+ if (options & QRegularExpression::MultilineOption)
+ flags |= CompiledData::RegExp::RegExp_Multiline;
+ QString pattern = re.pattern();
+ if (options & QRegularExpression::InvertedGreedinessOption)
+ pattern = minimalPattern(pattern);
+ o->d()->value.set(scope.engine, QV4::RegExp::create(scope.engine, pattern, flags));
o->initProperties();
}
#endif
@@ -178,11 +188,12 @@ QRegExp RegExpObject::toQRegExp() const
// 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);
+ QRegularExpression::PatternOptions options = QRegularExpression::NoPatternOption;
+ if (value()->flags & CompiledData::RegExp::RegExp_IgnoreCase)
+ options |= QRegularExpression::CaseInsensitiveOption;
+ if (value()->flags & CompiledData::RegExp::RegExp_Multiline)
+ options |= QRegularExpression::MultilineOption;
+ return QRegularExpression(*value()->pattern, options);
}
#endif
diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp
index 8dd0f7b188..1d859021ad 100644
--- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp
+++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp
@@ -142,6 +142,8 @@ private slots:
void qRegExpInport();
void qRegularExpressionImport_data();
void qRegularExpressionImport();
+ void qRegularExpressionExport_data();
+ void qRegularExpressionExport();
void dateRoundtripJSQtJS();
void dateRoundtripQtJSQt();
void dateConversionJSQt();
@@ -3287,7 +3289,6 @@ void tst_QJSEngine::qRegularExpressionImport_data()
{
QTest::addColumn<QRegularExpression>("rx");
QTest::addColumn<QString>("string");
- QTest::addColumn<QString>("matched");
QTest::newRow("normal") << QRegularExpression("(test|foo)") << "test _ foo _ test _ Foo";
QTest::newRow("normal2") << QRegularExpression("(Test|Foo)") << "test _ foo _ test _ Foo";
@@ -3311,6 +3312,14 @@ void tst_QJSEngine::qRegularExpressionImport_data()
QTest::newRow(".+ minimal") << QRegularExpression("^.+$") << ".+";
QTest::newRow("[.?] minimal") << QRegularExpression("^[.?]$") << ".?";
QTest::newRow("[.+] minimal") << QRegularExpression("^[.+]$") << ".+";
+ QTest::newRow("aaa inverted greedyness") << QRegularExpression("a{2,5}", QRegularExpression::InvertedGreedinessOption) << "aAaAaaaaaAa";
+ QTest::newRow("inverted greedyness") << QRegularExpression(".*\\} [*8]", QRegularExpression::InvertedGreedinessOption) << "}?} ?} *";
+ QTest::newRow(".? inverted greedyness") << QRegularExpression(".?", QRegularExpression::InvertedGreedinessOption) << ".?";
+ QTest::newRow(".+ inverted greedyness") << QRegularExpression(".+", QRegularExpression::InvertedGreedinessOption) << ".+";
+ QTest::newRow("[.?] inverted greedyness") << QRegularExpression("[.?]", QRegularExpression::InvertedGreedinessOption) << ".?";
+ QTest::newRow("[.+] inverted greedyness") << QRegularExpression("[.+]", QRegularExpression::InvertedGreedinessOption) << ".+";
+ QTest::newRow("two lines") << QRegularExpression("^.*$") << "abc\ndef";
+ QTest::newRow("multiline") << QRegularExpression("^.*$", QRegularExpression::MultilineOption) << "abc\ndef";
}
void tst_QJSEngine::qRegularExpressionImport()
@@ -3333,6 +3342,52 @@ void tst_QJSEngine::qRegularExpressionImport()
QCOMPARE(result.property(i).toString(), match.captured(i));
}
+void tst_QJSEngine::qRegularExpressionExport_data()
+{
+ QTest::addColumn<QString>("js");
+ QTest::addColumn<QRegularExpression>("regularexpression");
+
+ QTest::newRow("normal") << "/(test|foo)/" << QRegularExpression("(test|foo)");
+ QTest::newRow("normal2") << "/(Test|Foo)/" << QRegularExpression("(Test|Foo)");
+ QTest::newRow("case insensitive") << "/(test|foo)/i" << QRegularExpression("(test|foo)", QRegularExpression::CaseInsensitiveOption);
+ QTest::newRow("case insensitive2") << "/(Test|Foo)/i" << QRegularExpression("(Test|Foo)", QRegularExpression::CaseInsensitiveOption);
+ QTest::newRow("b(a*)(b*)") << "/b(a*)(b*)/i" << QRegularExpression("b(a*)(b*)", QRegularExpression::CaseInsensitiveOption);
+ QTest::newRow("greedy") << "/a*(a*)/i" << QRegularExpression("a*(a*)", QRegularExpression::CaseInsensitiveOption);
+ QTest::newRow("wildcard") << "/.*\\.txt/" << QRegularExpression(".*\\.txt");
+ QTest::newRow("wildcard 2") << "/a.b\\.txt/" << QRegularExpression("a.b\\.txt");
+ QTest::newRow("slash") << "/g\\/.*\\/s/i" << QRegularExpression("g\\/.*\\/s", QRegularExpression::CaseInsensitiveOption);
+ QTest::newRow("slash2") << "/g \\/ .* \\/ s/i" << QRegularExpression("g \\/ .* \\/ s", QRegularExpression::CaseInsensitiveOption);
+ QTest::newRow("fixed") << "/a\\*aa\\.a\\(ba\\)\\*a\\\\ba/i" << QRegularExpression("a\\*aa\\.a\\(ba\\)\\*a\\\\ba", QRegularExpression::CaseInsensitiveOption);
+ QTest::newRow("fixed insensitive") << "/A\\*A/i" << QRegularExpression("A\\*A", QRegularExpression::CaseInsensitiveOption);
+ QTest::newRow("fixed sensitive") << "/A\\*A/" << QRegularExpression("A\\*A");
+ QTest::newRow("html") << "/<b>(.*)<\\/b>/" << QRegularExpression("<b>(.*)<\\/b>");
+ QTest::newRow("html minimal") << "/^<b>(.*)<\\/b>$/" << QRegularExpression("^<b>(.*)<\\/b>$");
+ QTest::newRow("aaa") << "/a{2,5}/" << QRegularExpression("a{2,5}");
+ QTest::newRow("aaa minimal") << "/^a{2,5}$/" << QRegularExpression("^a{2,5}$");
+ QTest::newRow("minimal") << "/^.*\\} [*8]$/" << QRegularExpression("^.*\\} [*8]$");
+ QTest::newRow(".? minimal") << "/^.?$/" << QRegularExpression("^.?$");
+ QTest::newRow(".+ minimal") << "/^.+$/" << QRegularExpression("^.+$");
+ QTest::newRow("[.?] minimal") << "/^[.?]$/" << QRegularExpression("^[.?]$");
+ QTest::newRow("[.+] minimal") << "/^[.+]$/" << QRegularExpression("^[.+]$");
+ QTest::newRow("multiline") << "/^.*$/m" << QRegularExpression("^.*$", QRegularExpression::MultilineOption);
+}
+
+void tst_QJSEngine::qRegularExpressionExport()
+{
+ QFETCH(QString, js);
+ QFETCH(QRegularExpression, regularexpression);
+
+ QJSEngine eng;
+ QJSValue rexp;
+ rexp = eng.evaluate(js);
+
+ QCOMPARE(rexp.isRegExp(), true);
+ QCOMPARE(rexp.isCallable(), false);
+
+ QRegularExpression rx = qjsvalue_cast<QRegularExpression>(rexp);
+ QCOMPARE(rx, regularexpression);
+}
+
// QScriptValue::toDateTime() returns a local time, whereas JS dates
// are always stored as UTC. Qt Script must respect the current time
// zone, and correctly adjust for daylight saving time that may be in