1cef4f9662483fbf9dcffb77f6e45e4f827793e3
[WebKit.git] / Source / JavaScriptCore / builtins / StringPrototype.js
1 /*
2  * Copyright (C) 2015 Andy VanWagoner <thetalecrafter@gmail.com>.
3  * Copyright (C) 2016 Yusuke Suzuki <utatane.tea@gmail.com>
4  * Copyright (C) 2016 Apple Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 function match(regexp)
29 {
30     "use strict";
31
32     if (this == null)
33         @throwTypeError("String.prototype.match requires that |this| not be null or undefined");
34
35     if (regexp != null) {
36         var matcher = regexp.@matchSymbol;
37         if (matcher != @undefined)
38             return matcher.@call(regexp, this);
39     }
40
41     let thisString = @toString(this);
42     let createdRegExp = @regExpCreate(regexp, @undefined);
43     return createdRegExp.@matchSymbol(thisString);
44 }
45
46 @globalPrivate
47 function repeatSlowPath(string, count)
48 {
49     "use strict";
50
51     // Return an empty string.
52     if (count === 0 || string.length === 0)
53         return "";
54
55     // Return the original string.
56     if (count === 1)
57         return string;
58
59     if (string.length * count > @MAX_STRING_LENGTH)
60         @throwOutOfMemoryError();
61
62     // Bit operation onto |count| is safe because |count| should be within Int32 range,
63     // Repeat log N times to generate the repeated string rope.
64     var result = "";
65     var operand = string;
66     while (true) {
67         if (count & 1)
68             result += operand;
69         count >>= 1;
70         if (!count)
71             return result;
72         operand += operand;
73     }
74 }
75
76 @globalPrivate
77 function repeatCharactersSlowPath(string, count)
78 {
79     "use strict";
80     var repeatCount = (count / string.length) | 0;
81     var remainingCharacters = count - repeatCount * string.length;
82     var result = "";
83     var operand = string;
84     // Bit operation onto |repeatCount| is safe because |repeatCount| should be within Int32 range,
85     // Repeat log N times to generate the repeated string rope.
86     while (true) {
87         if (repeatCount & 1)
88             result += operand;
89         repeatCount >>= 1;
90         if (!repeatCount)
91             break;
92         operand += operand;
93     }
94     if (remainingCharacters)
95         result += @stringSubstrInternal.@call(string, 0, remainingCharacters);
96     return result;
97 }
98
99
100 function repeat(count)
101 {
102     "use strict";
103
104     if (this == null)
105         @throwTypeError("String.prototype.repeat requires that |this| not be null or undefined");
106
107     var string = @toString(this);
108     count = @toInteger(count);
109
110     if (count < 0 || count === @Infinity)
111         @throwRangeError("String.prototype.repeat argument must be greater than or equal to 0 and not be Infinity");
112
113     if (string.length === 1)
114         return @repeatCharacter(string, count);
115
116     return @repeatSlowPath(string, count);
117 }
118
119 function padStart(maxLength/*, fillString*/)
120 {
121     "use strict";
122
123     if (this == null)
124         @throwTypeError("String.prototype.padStart requires that |this| not be null or undefined");
125
126     var string = @toString(this);
127     maxLength = @toLength(maxLength);
128
129     var stringLength = string.length;
130     if (maxLength <= stringLength)
131         return string;
132
133     var filler;
134     var fillString = arguments[1];
135     if (fillString === @undefined)
136         filler = " ";
137     else {
138         filler = @toString(fillString);
139         if (filler === "")
140             return string;
141     }
142
143     if (maxLength > @MAX_STRING_LENGTH)
144         @throwOutOfMemoryError();
145
146     var fillLength = maxLength - stringLength;
147     var truncatedStringFiller;
148
149     if (filler.length === 1)
150         truncatedStringFiller = @repeatCharacter(filler, fillLength);
151     else
152         truncatedStringFiller = @repeatCharactersSlowPath(filler, fillLength);
153     return truncatedStringFiller + string;
154 }
155
156 function padEnd(maxLength/*, fillString*/)
157 {
158     "use strict";
159
160     if (this == null)
161         @throwTypeError("String.prototype.padEnd requires that |this| not be null or undefined");
162
163     var string = @toString(this);
164     maxLength = @toLength(maxLength);
165
166     var stringLength = string.length;
167     if (maxLength <= stringLength)
168         return string;
169
170     var filler;
171     var fillString = arguments[1];
172     if (fillString === @undefined)
173         filler = " ";
174     else {
175         filler = @toString(fillString);
176         if (filler === "")
177             return string;
178     }
179
180     if (maxLength > @MAX_STRING_LENGTH)
181         @throwOutOfMemoryError();
182
183     var fillLength = maxLength - stringLength;
184     var truncatedStringFiller;
185
186     if (filler.length === 1)
187         truncatedStringFiller = @repeatCharacter(filler, fillLength);
188     else
189         truncatedStringFiller = @repeatCharactersSlowPath(filler, fillLength);
190     return string + truncatedStringFiller;
191 }
192
193 @globalPrivate
194 function hasObservableSideEffectsForStringReplace(regexp, replacer) {
195     if (replacer !== @regExpPrototypeSymbolReplace)
196         return true;
197     
198     let regexpExec = @tryGetById(regexp, "exec");
199     if (regexpExec !== @regExpBuiltinExec)
200         return true;
201
202     let regexpGlobal = @tryGetById(regexp, "global");
203     if (regexpGlobal !== @regExpProtoGlobalGetter)
204         return true;
205
206     let regexpUnicode = @tryGetById(regexp, "unicode");
207     if (regexpUnicode !== @regExpProtoUnicodeGetter)
208         return true;
209
210     return !@isRegExpObject(regexp);
211 }
212
213 @intrinsic=StringPrototypeReplaceIntrinsic
214 function replace(search, replace)
215 {
216     "use strict";
217
218     if (this == null)
219         @throwTypeError("String.prototype.replace requires that |this| not be null or undefined");
220
221     if (search != null) {
222         let replacer = search.@replaceSymbol;
223         if (replacer !== @undefined) {
224             if (!@hasObservableSideEffectsForStringReplace(search, replacer))
225                 return @toString(this).@replaceUsingRegExp(search, replace);
226             return replacer.@call(search, this, replace);
227         }
228     }
229
230     let thisString = @toString(this);
231     let searchString = @toString(search);
232     return thisString.@replaceUsingStringSearch(searchString, replace);
233 }
234     
235 function localeCompare(that/*, locales, options */)
236 {
237     "use strict";
238
239     // 13.1.1 String.prototype.localeCompare (that [, locales [, options ]]) (ECMA-402 2.0)
240     // http://ecma-international.org/publications/standards/Ecma-402.htm
241
242     // 1. Let O be RequireObjectCoercible(this value).
243     if (this == null)
244         @throwTypeError("String.prototype.localeCompare requires that |this| not be null or undefined");
245
246     // 2. Let S be ToString(O).
247     // 3. ReturnIfAbrupt(S).
248     var thisString = @toString(this);
249
250     // 4. Let That be ToString(that).
251     // 5. ReturnIfAbrupt(That).
252     var thatString = @toString(that);
253
254     // Avoid creating a collator for defaults.
255     if (arguments[1] === @undefined && arguments[2] === @undefined)
256         return @Collator.prototype.compare(thisString, thatString);
257
258     // 6. Let collator be Construct(%Collator%, Ā«locales, optionsĀ»).
259     // 7. ReturnIfAbrupt(collator).
260     var collator = new @Collator(arguments[1], arguments[2]);
261
262     // 8. Return CompareStrings(collator, S, That).
263     return collator.compare(thisString, thatString);
264 }
265
266 function search(regexp)
267 {
268     "use strict";
269
270     if (this == null)
271         @throwTypeError("String.prototype.search requires that |this| not be null or undefined");
272
273     if (regexp != null) {
274         var searcher = regexp.@searchSymbol;
275         if (searcher != @undefined)
276             return searcher.@call(regexp, this);
277     }
278
279     var thisString = @toString(this);
280     var createdRegExp = @regExpCreate(regexp, @undefined);
281     return createdRegExp.@searchSymbol(thisString);
282 }
283
284 function split(separator, limit)
285 {
286     "use strict";
287     
288     if (this == null)
289         @throwTypeError("String.prototype.split requires that |this| not be null or undefined");
290     
291     if (separator != null) {
292         var splitter = separator.@splitSymbol;
293         if (splitter != @undefined)
294             return splitter.@call(separator, this, limit);
295     }
296     
297     return @stringSplitFast.@call(this, separator, limit);
298 }