Implement String.prototype.replace
authorSimon Hausmann <simon.hausmann@digia.com>
Mon, 21 Jan 2013 08:20:03 +0000 (09:20 +0100)
committerLars Knoll <lars.knoll@digia.com>
Mon, 21 Jan 2013 12:25:00 +0000 (13:25 +0100)
Change-Id: I60806b6563337c1c18a6b737e860deca4093c8ff
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
qv4ecmaobjects.cpp
qv4regexp.cpp
qv4regexp.h
tests/TestExpectations

index 933cb18..5024f86 100644 (file)
@@ -793,11 +793,137 @@ Value StringPrototype::method_match(ExecutionContext *ctx)
 
 }
 
+static QString makeReplacementString(const QString &input, const QString& replaceValue, uint* matchOffsets, int captureCount)
+{
+    QString result;
+    result.reserve(replaceValue.length());
+    for (int i = 0; i < replaceValue.length(); ++i) {
+        if (replaceValue.at(i) == QLatin1Char('$') && i < replaceValue.length() - 1) {
+            char ch = replaceValue.at(++i).toLatin1();
+            uint substStart = JSC::Yarr::offsetNoMatch;
+            uint substEnd = JSC::Yarr::offsetNoMatch;
+            if (ch == '$') {
+                result += ch;
+                continue;
+            } else if (ch == '&') {
+                substStart = matchOffsets[0];
+                substEnd = matchOffsets[1];
+            } else if (ch == '`') {
+                substStart = 0;
+                substEnd = matchOffsets[0];
+            } else if (ch == '\'') {
+                substStart = matchOffsets[1];
+                substEnd = input.length();
+            } else if (ch >= '1' && ch <= '9') {
+                char capture = ch - '0';
+                if (capture > 0 && capture < captureCount) {
+                    substStart = matchOffsets[capture * 2];
+                    substEnd = matchOffsets[capture * 2 + 1];
+                }
+            } else if (ch == '0' && i < replaceValue.length() - 1) {
+                int capture = (ch - '0') * 10;
+                ch = replaceValue.at(++i).toLatin1();
+                capture += ch - '0';
+                if (capture > 0 && capture < captureCount) {
+                    substStart = matchOffsets[capture * 2];
+                    substEnd = matchOffsets[capture * 2 + 1];
+                }
+            }
+            if (substStart != JSC::Yarr::offsetNoMatch && substEnd != JSC::Yarr::offsetNoMatch)
+                result += input.midRef(substStart, substEnd - substStart);
+        } else {
+            result += replaceValue.at(i);
+        }
+    }
+    return result;
+}
+
 Value StringPrototype::method_replace(ExecutionContext *ctx)
 {
-    // requires Regexp
-    ctx->throwUnimplemented(QStringLiteral("String.prototype.replace"));
-    return Value::undefinedValue();
+    QString string;
+    if (StringObject *thisString = ctx->thisObject.asStringObject())
+        string = thisString->value.stringValue()->toQString();
+    else
+        string = ctx->thisObject.toString(ctx)->toQString();
+
+    int numCaptures = 0;
+    QVarLengthArray<uint, 16> matchOffsets;
+    int numStringMatches = 0;
+
+    Value searchValue = ctx->argument(0);
+    RegExpObject *regExp = searchValue.asRegExpObject();
+    if (regExp) {
+        uint offset = 0;
+        while (true) {
+            int oldSize = matchOffsets.size();
+            matchOffsets.resize(matchOffsets.size() + regExp->value->captureCount() * 2);
+            if (regExp->value->match(string, offset, matchOffsets.data() + oldSize) == JSC::Yarr::offsetNoMatch) {
+                matchOffsets.resize(oldSize);
+                break;
+            }
+            if (!regExp->global)
+                break;
+            offset = qMax(offset + 1, matchOffsets[oldSize + 1]);
+        }
+        if (regExp->global)
+            regExp->lastIndexProperty->value = Value::fromUInt32(0);
+        numStringMatches = matchOffsets.size() / (regExp->value->captureCount() * 2);
+        numCaptures = regExp->value->captureCount();
+    } else {
+        numCaptures = 1;
+        QString searchString = searchValue.toString(ctx)->toQString();
+        int idx = string.indexOf(searchString);
+        if (idx != -1) {
+            numStringMatches = 1;
+            matchOffsets.resize(2);
+            matchOffsets[0] = idx;
+            matchOffsets[1] = idx + searchString.length();
+        }
+    }
+
+    QString result = string;
+    Value replaceValue = ctx->argument(1);
+    if (FunctionObject* searchCallback = replaceValue.asFunctionObject()) {
+        int replacementDelta = 0;
+        int argc = numCaptures + 2;
+        Value *args = (Value*)alloca((numCaptures + 2) * sizeof(Value));
+        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];
+                Value entry = Value::undefinedValue();
+                if (start != JSC::Yarr::offsetNoMatch && end != JSC::Yarr::offsetNoMatch)
+                    entry = Value::fromString(ctx, string.mid(start, end - start));
+                args[k] = entry;
+            }
+            uint matchStart = matchOffsets[i * numCaptures * 2];
+            uint matchEnd = matchOffsets[i * numCaptures * 2 + 1];
+            args[numCaptures] = Value::fromUInt32(matchStart);
+            args[numCaptures + 1] = Value::fromString(ctx, string);
+            Value replacement = searchCallback->call(ctx, Value::undefinedValue(), args, argc);
+            QString replacementString = replacement.toString(ctx)->toQString();
+            result.replace(replacementDelta + matchStart, matchEnd - matchStart, replacementString);
+            replacementDelta += replacementString.length() - matchEnd + matchStart;
+        }
+    } else {
+        QString newString = replaceValue.toString(ctx)->toQString();
+        int replacementDelta = 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;
+
+            QString replacement = makeReplacementString(string, newString, matchOffsets.data() + baseIndex, numCaptures);
+            result.replace(replacementDelta + matchStart, matchEnd - matchStart, replacement);
+            replacementDelta += replacement.length() - matchEnd + matchStart;
+        }
+    }
+
+    return Value::fromString(ctx, result);
 }
 
 Value StringPrototype::method_search(ExecutionContext *ctx)
index 64d9fef..bcef815 100644 (file)
@@ -46,7 +46,7 @@
 namespace QQmlJS {
 namespace VM {
 
-int RegExp::match(const QString &string, int start, uint *matchOffsets)
+uint RegExp::match(const QString &string, int start, uint *matchOffsets)
 {
     if (!isValid())
         return JSC::Yarr::offsetNoMatch;
index 7fd8225..7eae033 100644 (file)
@@ -69,7 +69,7 @@ public:
 
     bool isValid() const { return m_byteCode.get(); }
 
-    int match(const QString& string, int start, uint *matchOffsets);
+    uint match(const QString& string, int start, uint *matchOffsets);
 
     bool ignoreCase() const { return m_ignoreCase; }
     bool multiLine() const { return m_multiLine; }
index c2e77f5..047d587 100644 (file)
@@ -14,12 +14,6 @@ S10.2.3_A1.3_T2 failing
 10.4.2-1-3 failing
 10.4.2-1-4 failing
 10.4.2-1-5 failing
-10.4.3-1-100-s failing
-10.4.3-1-100gs failing
-10.4.3-1-101-s failing
-10.4.3-1-101gs failing
-10.4.3-1-102-s failing
-10.4.3-1-102gs failing
 10.4.3-1-104 failing
 10.4.3-1-106 failing
 10.4.3-1-17-s failing
@@ -292,13 +286,6 @@ S15.1.2.2_A9.2 failing
 S15.1.2.2_A9.3 failing
 S15.1.2.2_A9.4 failing
 S15.1.2.2_A9.7 failing
-S15.10.2.12_A6_T1 failing
-S15.10.2.12_A1_T1 failing
-S15.10.2.12_A2_T1 failing
-S15.10.2.12_A3_T1 failing
-S15.10.2.12_A4_T1 failing
-S15.10.2.12_A5_T1 failing
-S15.10.2.8_A3_T18 failing
 S15.10.6.2_A1_T2 failing
 S15.10.7_A2_T1 failing
 S15.11.4.2_A1 failing
@@ -630,41 +617,7 @@ S15.4.4.7_A3 failing
 S15.4.4.7_A4_T2 failing
 S15.4.4.7_A4_T3 failing
 S15.5.4.10_A1_T10 failing
-15.5.4.11-1 failing
-S15.5.4.11_A12 failing
-S15.5.4.11_A1_T1 failing
 S15.5.4.11_A1_T10 failing
-S15.5.4.11_A1_T11 failing
-S15.5.4.11_A1_T12 failing
-S15.5.4.11_A1_T13 failing
-S15.5.4.11_A1_T14 failing
-S15.5.4.11_A1_T15 failing
-S15.5.4.11_A1_T16 failing
-S15.5.4.11_A1_T17 failing
-S15.5.4.11_A1_T2 failing
-S15.5.4.11_A1_T4 failing
-S15.5.4.11_A1_T5 failing
-S15.5.4.11_A1_T6 failing
-S15.5.4.11_A1_T7 failing
-S15.5.4.11_A1_T8 failing
-S15.5.4.11_A1_T9 failing
-S15.5.4.11_A2_T1 failing
-S15.5.4.11_A2_T10 failing
-S15.5.4.11_A2_T2 failing
-S15.5.4.11_A2_T3 failing
-S15.5.4.11_A2_T4 failing
-S15.5.4.11_A2_T5 failing
-S15.5.4.11_A2_T6 failing
-S15.5.4.11_A2_T7 failing
-S15.5.4.11_A2_T8 failing
-S15.5.4.11_A2_T9 failing
-S15.5.4.11_A3_T1 failing
-S15.5.4.11_A3_T2 failing
-S15.5.4.11_A3_T3 failing
-S15.5.4.11_A4_T1 failing
-S15.5.4.11_A4_T2 failing
-S15.5.4.11_A4_T3 failing
-S15.5.4.11_A4_T4 failing
 S15.5.4.11_A5_T1 failing
 S15.5.4.12_A1.1_T1 failing
 S15.5.4.12_A1_T1 failing
@@ -1148,4 +1101,4 @@ S15.4.4.4_A1_T2 failing
 
 15.4.4.21-8-b-iii-1-6 failing
 15.12.3_4-1-1
-15.12.3_4-1-3
\ No newline at end of file
+15.12.3_4-1-3