[JSC] re-implement String#padStart and String#padEnd in JavaScript
authorcaitp@igalia.com <caitp@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 28 Apr 2016 21:39:34 +0000 (21:39 +0000)
committercaitp@igalia.com <caitp@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 28 Apr 2016 21:39:34 +0000 (21:39 +0000)
https://bugs.webkit.org/show_bug.cgi?id=157146

Reviewed by Saam Barati.

* builtins/StringPrototype.js:
(repeatCharactersSlowPath):
(padStart):
(padEnd):
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init):
* runtime/StringPrototype.cpp:
(JSC::StringPrototype::finishCreation): Deleted.
(JSC::repeatStringPattern): Deleted.
(JSC::padString): Deleted.
(JSC::stringProtoFuncPadEnd): Deleted.
(JSC::stringProtoFuncPadStart): Deleted.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@200210 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/builtins/StringPrototype.js
Source/JavaScriptCore/runtime/JSGlobalObject.cpp
Source/JavaScriptCore/runtime/StringPrototype.cpp

index d5157fd..ee9dfe6 100644 (file)
@@ -1,3 +1,23 @@
+2016-04-28  Caitlin Potter  <caitp@igalia.com>
+
+        [JSC] re-implement String#padStart and String#padEnd in JavaScript
+        https://bugs.webkit.org/show_bug.cgi?id=157146
+
+        Reviewed by Saam Barati.
+
+        * builtins/StringPrototype.js:
+        (repeatCharactersSlowPath):
+        (padStart):
+        (padEnd):
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::init):
+        * runtime/StringPrototype.cpp:
+        (JSC::StringPrototype::finishCreation): Deleted.
+        (JSC::repeatStringPattern): Deleted.
+        (JSC::padString): Deleted.
+        (JSC::stringProtoFuncPadEnd): Deleted.
+        (JSC::stringProtoFuncPadStart): Deleted.
+
 2016-04-28  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: Tweak auto attach initialization on some platforms
index 2a8f572..0cc25b1 100644 (file)
@@ -84,6 +84,30 @@ function repeatSlowPath(string, count)
     }
 }
 
+
+function repeatCharactersSlowPath(string, count)
+{
+    "use strict";
+    var repeatCount = (count / string.length) | 0;
+    var remainingCharacters = count - repeatCount * string.length;
+    var result = "";
+    var operand = string;
+    // Bit operation onto |repeatCount| is safe because |repeatCount| should be within Int32 range,
+    // Repeat log N times to generate the repeated string rope.
+    while (true) {
+        if (repeatCount & 1)
+            result += operand;
+        repeatCount >>= 1;
+        if (!repeatCount)
+            break;
+        operand += operand;
+    }
+    if (remainingCharacters)
+        result += @stringSubstrInternal.@call(string, 0, remainingCharacters);
+    return result;
+}
+
+
 function repeat(count)
 {
     "use strict";
@@ -105,6 +129,79 @@ function repeat(count)
     return @repeatSlowPath(string, count);
 }
 
+function padStart(maxLength/*, fillString*/)
+{
+    "use strict";
+
+    if (this === null)
+        throw new @TypeError("String.prototype.padStart requires that |this| not be null");
+    
+    if (this === @undefined)
+        throw new @TypeError("String.prototype.padStart requires that |this| not be undefined");
+
+    var string = @toString(this);
+    maxLength = @toLength(maxLength);
+    var fillString = arguments[1];
+
+    var stringLength = string.length;
+    if (maxLength <= stringLength)
+        return string;
+
+    var filler;
+    if (arguments[1] === @undefined)
+        filler = " ";
+    else {
+        filler = @toString(arguments[1]);
+        if (filler === "")
+            return string;
+    }
+
+    var fillLength = maxLength - stringLength;
+    var truncatedStringFiller;
+
+    if (filler.length === 1)
+        truncatedStringFiller = @repeatCharacter(filler, fillLength);
+    else
+        truncatedStringFiller = @repeatCharactersSlowPath(filler, fillLength);
+    return truncatedStringFiller + string;
+}
+
+function padEnd(maxLength/*, fillString*/)
+{
+    "use strict";
+
+    if (this === null)
+        throw new @TypeError("String.prototype.padEnd requires that |this| not be null");
+    
+    if (this === @undefined)
+        throw new @TypeError("String.prototype.padEnd requires that |this| not be undefined");
+
+    var string = @toString(this);
+    maxLength = @toLength(maxLength);
+
+    var stringLength = string.length;
+    if (maxLength <= stringLength)
+        return string;
+
+    var filler;
+    if (arguments[1] === @undefined)
+        filler = " ";
+    else {
+        filler = @toString(arguments[1]);
+        if (filler === "")
+            return string;
+    }
+
+    var fillLength = maxLength - stringLength;
+    var truncatedStringFiller;
+
+    if (filler.length === 1)
+        truncatedStringFiller = @repeatCharacter(filler, fillLength);
+    else
+        truncatedStringFiller = @repeatCharactersSlowPath(filler, fillLength);
+    return string + truncatedStringFiller;
+}
+
 function hasObservableSideEffectsForStringReplace(regexp, replacer) {
     if (replacer !== @regExpPrototypeSymbolReplace)
         return true;
index 36bbed4..c11e50c 100644 (file)
@@ -599,6 +599,7 @@ putDirectWithoutTransition(vm, vm.propertyNames-> jsName, lowerName ## Construct
 
         GlobalPropertyInfo(vm.propertyNames->repeatCharacterPrivateName, JSFunction::create(vm, this, 2, String(), stringProtoFuncRepeatCharacter), DontEnum | DontDelete | ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->builtinNames().repeatSlowPathPrivateName(), JSFunction::createBuiltinFunction(vm, stringPrototypeRepeatSlowPathCodeGenerator(vm), this), DontEnum | DontDelete | ReadOnly),
+        GlobalPropertyInfo(vm.propertyNames->builtinNames().repeatCharactersSlowPathPrivateName(), JSFunction::createBuiltinFunction(vm, stringPrototypeRepeatCharactersSlowPathCodeGenerator(vm), this), DontEnum | DontDelete | ReadOnly),
 
         GlobalPropertyInfo(vm.propertyNames->isSetPrivateName, JSFunction::create(vm, this, 1, String(), privateFuncIsSet), DontEnum | DontDelete | ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->SetIteratorPrivateName, JSFunction::create(vm, this, 1, String(), privateFuncSetIterator), DontEnum | DontDelete | ReadOnly),
index 89c1e8a..d378e2f 100644 (file)
@@ -66,8 +66,6 @@ EncodedJSValue JSC_HOST_CALL stringProtoFuncCodePointAt(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncConcat(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncIndexOf(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncLastIndexOf(ExecState*);
-EncodedJSValue JSC_HOST_CALL stringProtoFuncPadEnd(ExecState*);
-EncodedJSValue JSC_HOST_CALL stringProtoFuncPadStart(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingRegExp(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncReplaceUsingStringSearch(ExecState*);
 EncodedJSValue JSC_HOST_CALL stringProtoFuncSlice(ExecState*);
@@ -111,6 +109,8 @@ const ClassInfo StringPrototype::s_info = { "String", &StringObject::s_info, &st
 /* Source for StringConstructor.lut.h
 @begin stringPrototypeTable
     match     JSBuiltin    DontEnum|Function 1
+    padStart  JSBuiltin    DontEnum|Function 1
+    padEnd    JSBuiltin    DontEnum|Function 1
     repeat    JSBuiltin    DontEnum|Function 1
     replace   JSBuiltin    DontEnum|Function 2
     search    JSBuiltin    DontEnum|Function 1
@@ -137,8 +137,6 @@ void StringPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject, JSStr
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("concat", stringProtoFuncConcat, DontEnum, 1);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("indexOf", stringProtoFuncIndexOf, DontEnum, 1);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("lastIndexOf", stringProtoFuncLastIndexOf, DontEnum, 1);
-    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("padEnd", stringProtoFuncPadEnd, DontEnum, 1);
-    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("padStart", stringProtoFuncPadStart, DontEnum, 1);
     JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->replaceUsingRegExpPrivateName, stringProtoFuncReplaceUsingRegExp, DontEnum, 2, StringPrototypeReplaceRegExpIntrinsic);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->replaceUsingStringSearchPrivateName, stringProtoFuncReplaceUsingStringSearch, DontEnum, 2);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("slice", stringProtoFuncSlice, DontEnum, 2);
@@ -800,101 +798,6 @@ EncodedJSValue JSC_HOST_CALL stringProtoFuncRepeatCharacter(ExecState* exec)
     return JSValue::encode(repeatCharacter(*exec, character, repeatCount));
 }
 
-static inline bool repeatStringPattern(ExecState& exec, unsigned maxLength, JSString* string, JSRopeString::RopeBuilder& ropeBuilder)
-{
-    unsigned repeatCount = maxLength / string->length();
-    unsigned remainingCharacters = maxLength - repeatCount * string->length();
-    for (unsigned i = 0; i < repeatCount; ++i) {
-        if (!ropeBuilder.append(string)) {
-            throwOutOfMemoryError(&exec);
-            return false;
-        }
-    }
-    if (remainingCharacters) {
-        JSString* substr = jsSubstring(&exec, string, 0, remainingCharacters);
-        if (!substr || !ropeBuilder.append(substr)) {
-            throwOutOfMemoryError(&exec);
-            return false;
-        }
-    }
-    return true;
-}
-
-enum class StringPaddingLocation { Start, End };
-
-static EncodedJSValue padString(ExecState& exec, StringPaddingLocation paddingLocation)
-{
-    JSValue thisValue = exec.thisValue();
-    if (!thisValue.requireObjectCoercible(&exec))
-        return JSValue::encode(jsUndefined());
-    JSString* thisString = thisValue.toString(&exec);
-    if (exec.hadException())
-        return JSValue::encode(jsUndefined());
-
-    double maxLengthAsDouble = exec.argument(0).toLength(&exec);
-    if (exec.hadException())
-        return JSValue::encode(jsUndefined());
-    ASSERT(maxLengthAsDouble >= 0.0);
-    ASSERT(maxLengthAsDouble == std::trunc(maxLengthAsDouble));
-
-    if (maxLengthAsDouble <= thisString->length())
-        return JSValue::encode(thisString);
-
-    if (maxLengthAsDouble > JSString::MaxLength)
-        return JSValue::encode(throwOutOfMemoryError(&exec));
-
-    unsigned maxLength = static_cast<unsigned>(maxLengthAsDouble);
-
-    JSValue fillString = exec.argument(1);
-    JSString* filler = nullptr;
-    if (!fillString.isUndefined()) {
-        filler = fillString.toString(&exec);
-        if (!filler)
-            return JSValue::encode(jsUndefined());
-        if (!filler->length())
-            return JSValue::encode(thisString);
-    }
-
-    unsigned fillLength = static_cast<unsigned>(maxLength) - thisString->length();
-
-    JSRopeString::RopeBuilder ropeBuilder(exec.vm());
-    if (paddingLocation == StringPaddingLocation::End) {
-        if (!ropeBuilder.append(thisString))
-            return JSValue::encode(throwOutOfMemoryError(&exec));
-    }
-
-    if (!filler || filler->length() == 1) {
-        UChar character = filler && filler->length() ? filler->view(&exec)[0] : ' ';
-        if (!(character & ~0xff))
-            filler = repeatCharacter(exec, static_cast<LChar>(character), fillLength);
-        else
-            filler = repeatCharacter(exec, character, fillLength);
-        if (!filler || !ropeBuilder.append(filler))
-            return JSValue::encode(throwOutOfMemoryError(&exec));
-        ASSERT(filler->length() == fillLength);
-    } else {
-        if (!repeatStringPattern(exec, fillLength, filler, ropeBuilder))
-            return JSValue::encode(throwOutOfMemoryError(&exec));
-    }
-
-    if (paddingLocation == StringPaddingLocation::Start) {
-        if (!ropeBuilder.append(thisString))
-            return JSValue::encode(throwOutOfMemoryError(&exec));
-    }
-    ASSERT(!exec.hadException());
-    return JSValue::encode(ropeBuilder.release());
-}
-
-EncodedJSValue JSC_HOST_CALL stringProtoFuncPadEnd(ExecState* exec)
-{
-    return padString(*exec, StringPaddingLocation::End);
-}
-
-EncodedJSValue JSC_HOST_CALL stringProtoFuncPadStart(ExecState* exec)
-{
-    return padString(*exec, StringPaddingLocation::Start);
-}
-
 ALWAYS_INLINE EncodedJSValue replace(
     VM& vm, ExecState* exec, JSString* string, JSValue searchValue, JSValue replaceValue)
 {