From 19025ab3422658ab27415cee99336d88a4ae19fa Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Mon, 20 Jan 2014 14:32:26 +0100 Subject: Add support for deprecated RegExp (constructor) properties These were apparently part of ancient EcmaScript specs, aren't even listed anymore in any recent spec (not even as deprecated), but apparently they are part of what the web supports as well as previous versions of Qml. So this patch implements them. Task-number: QTBUG-36244 Change-Id: I1b9ea7ea09fceb6a486f615837a71e41aae12de4 Reviewed-by: Lars Knoll --- src/qml/jsruntime/qv4regexpobject.cpp | 93 +++++++++++++++++++++++++++++- src/qml/jsruntime/qv4regexpobject_p.h | 14 +++++ tests/auto/qml/qjsengine/tst_qjsengine.cpp | 64 ++++++++++++++++++++ 3 files changed, 169 insertions(+), 2 deletions(-) diff --git a/src/qml/jsruntime/qv4regexpobject.cpp b/src/qml/jsruntime/qv4regexpobject.cpp index 468fb34d76..0ebad2f781 100644 --- a/src/qml/jsruntime/qv4regexpobject.cpp +++ b/src/qml/jsruntime/qv4regexpobject.cpp @@ -238,6 +238,15 @@ RegExpCtor::RegExpCtor(ExecutionContext *scope) : FunctionObject(scope, QStringLiteral("RegExp")) { setVTable(&static_vtbl); + clearLastMatch(); +} + +void RegExpCtor::clearLastMatch() +{ + lastMatch = Primitive::nullValue(); + lastInput = engine()->newIdentifier(QString()); + lastMatchStart = 0; + lastMatchEnd = 0; } ReturnedValue RegExpCtor::construct(Managed *m, CallData *callData) @@ -300,6 +309,14 @@ ReturnedValue RegExpCtor::call(Managed *that, CallData *callData) return construct(that, callData); } +void RegExpCtor::markObjects(Managed *that, ExecutionEngine *e) +{ + RegExpCtor *This = static_cast(that); + This->lastMatch.mark(e); + This->lastInput.mark(e); + FunctionObject::markObjects(that, e); +} + void RegExpPrototype::init(ExecutionEngine *engine, ObjectRef ctor) { Scope scope(engine); @@ -307,6 +324,28 @@ void RegExpPrototype::init(ExecutionEngine *engine, ObjectRef ctor) ctor->defineReadonlyProperty(engine->id_prototype, (o = this)); ctor->defineReadonlyProperty(engine->id_length, Primitive::fromInt32(2)); + + // Properties deprecated in the spec but required by "the web" :( + ctor->defineAccessorProperty(QStringLiteral("lastMatch"), method_get_lastMatch_n<0>, 0); + ctor->defineAccessorProperty(QStringLiteral("$&"), method_get_lastMatch_n<0>, 0); + ctor->defineAccessorProperty(QStringLiteral("$1"), method_get_lastMatch_n<1>, 0); + ctor->defineAccessorProperty(QStringLiteral("$2"), method_get_lastMatch_n<2>, 0); + ctor->defineAccessorProperty(QStringLiteral("$3"), method_get_lastMatch_n<3>, 0); + ctor->defineAccessorProperty(QStringLiteral("$4"), method_get_lastMatch_n<4>, 0); + ctor->defineAccessorProperty(QStringLiteral("$5"), method_get_lastMatch_n<5>, 0); + ctor->defineAccessorProperty(QStringLiteral("$6"), method_get_lastMatch_n<6>, 0); + ctor->defineAccessorProperty(QStringLiteral("$7"), method_get_lastMatch_n<7>, 0); + ctor->defineAccessorProperty(QStringLiteral("$8"), method_get_lastMatch_n<8>, 0); + ctor->defineAccessorProperty(QStringLiteral("$9"), method_get_lastMatch_n<9>, 0); + ctor->defineAccessorProperty(QStringLiteral("lastParen"), method_get_lastParen, 0); + ctor->defineAccessorProperty(QStringLiteral("$+"), method_get_lastParen, 0); + ctor->defineAccessorProperty(QStringLiteral("input"), method_get_input, 0); + ctor->defineAccessorProperty(QStringLiteral("$_"), method_get_input, 0); + ctor->defineAccessorProperty(QStringLiteral("leftContext"), method_get_leftContext, 0); + ctor->defineAccessorProperty(QStringLiteral("$`"), method_get_leftContext, 0); + ctor->defineAccessorProperty(QStringLiteral("rightContext"), method_get_rightContext, 0); + ctor->defineAccessorProperty(QStringLiteral("$'"), method_get_rightContext, 0); + defineDefaultProperty(QStringLiteral("constructor"), (o = ctor)); defineDefaultProperty(QStringLiteral("exec"), method_exec, 1); defineDefaultProperty(QStringLiteral("test"), method_test, 1); @@ -334,7 +373,11 @@ ReturnedValue RegExpPrototype::method_exec(CallContext *ctx) } uint* matchOffsets = (uint*)alloca(r->value->captureCount() * 2 * sizeof(uint)); - int result = r->value->match(s, offset, matchOffsets); + const int result = r->value->match(s, offset, matchOffsets); + + Scoped regExpCtor(scope, ctx->engine->regExpCtor); + regExpCtor->clearLastMatch(); + if (result == -1) { r->lastIndexProperty(ctx)->value = Primitive::fromInt32(0); return Encode::null(); @@ -351,10 +394,14 @@ ReturnedValue RegExpPrototype::method_exec(CallContext *ctx) array->arrayDataLen = i + 1; } array->setArrayLengthUnchecked(len); - array->memberData[Index_ArrayIndex].value = Primitive::fromInt32(result); array->memberData[Index_ArrayInput].value = arg.asReturnedValue(); + regExpCtor->lastMatch = array; + regExpCtor->lastInput = arg->stringValue(); + regExpCtor->lastMatchStart = matchOffsets[0]; + regExpCtor->lastMatchEnd = matchOffsets[1]; + if (r->global) r->lastIndexProperty(ctx)->value = Primitive::fromInt32(matchOffsets[1]); @@ -395,4 +442,46 @@ ReturnedValue RegExpPrototype::method_compile(CallContext *ctx) return Encode::undefined(); } +template +ReturnedValue RegExpPrototype::method_get_lastMatch_n(CallContext *ctx) +{ + Scope scope(ctx); + ScopedArrayObject lastMatch(scope, static_cast(ctx->engine->regExpCtor.objectValue())->lastMatch); + ScopedValue result(scope, lastMatch ? lastMatch->getIndexed(index) : Encode::undefined()); + if (result->isUndefined()) + return ctx->engine->newString(QString())->asReturnedValue(); + return result.asReturnedValue(); +} + +ReturnedValue RegExpPrototype::method_get_lastParen(CallContext *ctx) +{ + Scope scope(ctx); + ScopedArrayObject lastMatch(scope, static_cast(ctx->engine->regExpCtor.objectValue())->lastMatch); + ScopedValue result(scope, lastMatch ? lastMatch->getIndexed(lastMatch->arrayLength() - 1) : Encode::undefined()); + if (result->isUndefined()) + return ctx->engine->newString(QString())->asReturnedValue(); + return result.asReturnedValue(); +} + +ReturnedValue RegExpPrototype::method_get_input(CallContext *ctx) +{ + return static_cast(ctx->engine->regExpCtor.objectValue())->lastInput.asReturnedValue(); +} + +ReturnedValue RegExpPrototype::method_get_leftContext(CallContext *ctx) +{ + Scope scope(ctx); + Scoped regExpCtor(scope, ctx->engine->regExpCtor); + QString lastInput = regExpCtor->lastInput->toQString(); + return ctx->engine->newString(lastInput.left(regExpCtor->lastMatchStart))->asReturnedValue(); +} + +ReturnedValue RegExpPrototype::method_get_rightContext(CallContext *ctx) +{ + Scope scope(ctx); + Scoped regExpCtor(scope, ctx->engine->regExpCtor); + QString lastInput = regExpCtor->lastInput->toQString(); + return ctx->engine->newString(lastInput.mid(regExpCtor->lastMatchEnd))->asReturnedValue(); +} + QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4regexpobject_p.h b/src/qml/jsruntime/qv4regexpobject_p.h index 0129f8d396..f112ad804d 100644 --- a/src/qml/jsruntime/qv4regexpobject_p.h +++ b/src/qml/jsruntime/qv4regexpobject_p.h @@ -106,8 +106,15 @@ struct RegExpCtor: FunctionObject Q_MANAGED RegExpCtor(ExecutionContext *scope); + SafeValue lastMatch; + SafeString lastInput; + int lastMatchStart; + int lastMatchEnd; + void clearLastMatch(); + static ReturnedValue construct(Managed *m, CallData *callData); static ReturnedValue call(Managed *that, CallData *callData); + static void markObjects(Managed *that, ExecutionEngine *e); }; struct RegExpPrototype: RegExpObject @@ -119,6 +126,13 @@ struct RegExpPrototype: RegExpObject static ReturnedValue method_test(CallContext *ctx); static ReturnedValue method_toString(CallContext *ctx); static ReturnedValue method_compile(CallContext *ctx); + + template + static ReturnedValue method_get_lastMatch_n(CallContext *ctx); + static ReturnedValue method_get_lastParen(CallContext *ctx); + static ReturnedValue method_get_input(CallContext *ctx); + static ReturnedValue method_get_leftContext(CallContext *ctx); + static ReturnedValue method_get_rightContext(CallContext *ctx); }; } diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index a1662b495c..ba99b34935 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -148,6 +148,8 @@ private slots: void functionDeclarationsInConditionals(); void arrayPop_QTBUG_35979(); + + void regexpLastMatch(); }; tst_QJSEngine::tst_QJSEngine() @@ -2705,6 +2707,68 @@ void tst_QJSEngine::arrayPop_QTBUG_35979() QCOMPARE(result.toString(), QString("1,3")); } +void tst_QJSEngine::regexpLastMatch() +{ + QJSEngine eng; + + QCOMPARE(eng.evaluate("RegExp.input").toString(), QString()); + + QJSValue hasProperty; + + for (int i = 1; i < 9; ++i) { + hasProperty = eng.evaluate("RegExp.hasOwnProperty(\"$" + QString::number(i) + "\")"); + QVERIFY(hasProperty.isBool()); + QVERIFY(hasProperty.toBool()); + } + + hasProperty = eng.evaluate("RegExp.hasOwnProperty(\"$0\")"); + QVERIFY(hasProperty.isBool()); + QVERIFY(!hasProperty.toBool()); + + hasProperty = eng.evaluate("RegExp.hasOwnProperty(\"$10\")"); + QVERIFY(!hasProperty.toBool()); + + hasProperty = eng.evaluate("RegExp.hasOwnProperty(\"lastMatch\")"); + QVERIFY(hasProperty.toBool()); + hasProperty = eng.evaluate("RegExp.hasOwnProperty(\"$&\")"); + QVERIFY(hasProperty.toBool()); + + QJSValue result = eng.evaluate("" + "var re = /h(el)l(o)/\n" + "var text = \"blah hello world\"\n" + "text.match(re)\n"); + QVERIFY(!result.isError()); + QJSValue match = eng.evaluate("RegExp.$1"); + QCOMPARE(match.toString(), QString("el")); + match = eng.evaluate("RegExp.$2"); + QCOMPARE(match.toString(), QString("o")); + for (int i = 3; i <= 9; ++i) { + match = eng.evaluate("RegExp.$" + QString::number(i)); + QVERIFY(match.isString()); + QCOMPARE(match.toString(), QString()); + } + QCOMPARE(eng.evaluate("RegExp.input").toString(), QString("blah hello world")); + QCOMPARE(eng.evaluate("RegExp.lastParen").toString(), QString("o")); + QCOMPARE(eng.evaluate("RegExp.leftContext").toString(), QString("blah ")); + QCOMPARE(eng.evaluate("RegExp.rightContext").toString(), QString(" world")); + + QCOMPARE(eng.evaluate("RegExp.lastMatch").toString(), QString("hello")); + + result = eng.evaluate("" + "var re = /h(ello)/\n" + "var text = \"hello\"\n" + "text.match(re)\n"); + QVERIFY(!result.isError()); + match = eng.evaluate("RegExp.$1"); + QCOMPARE(match.toString(), QString("ello")); + for (int i = 2; i <= 9; ++i) { + match = eng.evaluate("RegExp.$" + QString::number(i)); + QVERIFY(match.isString()); + QCOMPARE(match.toString(), QString()); + } + +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" -- cgit v1.2.3