[JSC] Drop direct references to Intl constructors by rewriting Intl JS builtins in C++
[WebKit-https.git] / Source / JavaScriptCore / builtins / StringPrototype.js
1 /*
2  * Copyright (C) 2015 Andy VanWagoner <andy@vanwagoner.family>.
3  * Copyright (C) 2016 Yusuke Suzuki <utatane.tea@gmail.com>
4  * Copyright (C) 2016-2018 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 (@isUndefinedOrNull(this))
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 (@isUndefinedOrNull(this))
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 (@isUndefinedOrNull(this))
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 = @argument(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 (@isUndefinedOrNull(this))
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 = @argument(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 {
196     "use strict";
197
198     if (!@isRegExpObject(regexp))
199         return true;
200
201     if (replacer !== @regExpPrototypeSymbolReplace)
202         return true;
203     
204     let regexpExec = @tryGetById(regexp, "exec");
205     if (regexpExec !== @regExpBuiltinExec)
206         return true;
207
208     let regexpGlobal = @tryGetById(regexp, "global");
209     if (regexpGlobal !== @regExpProtoGlobalGetter)
210         return true;
211
212     let regexpUnicode = @tryGetById(regexp, "unicode");
213     if (regexpUnicode !== @regExpProtoUnicodeGetter)
214         return true;
215
216     return typeof regexp.lastIndex !== "number";
217 }
218
219 @intrinsic=StringPrototypeReplaceIntrinsic
220 function replace(search, replace)
221 {
222     "use strict";
223
224     if (@isUndefinedOrNull(this))
225         @throwTypeError("String.prototype.replace requires that |this| not be null or undefined");
226
227     if (search != null) {
228         let replacer = search.@replaceSymbol;
229         if (replacer !== @undefined) {
230             if (!@hasObservableSideEffectsForStringReplace(search, replacer))
231                 return @toString(this).@replaceUsingRegExp(search, replace);
232             return replacer.@call(search, this, replace);
233         }
234     }
235
236     let thisString = @toString(this);
237     let searchString = @toString(search);
238     return thisString.@replaceUsingStringSearch(searchString, replace);
239 }
240
241 function search(regexp)
242 {
243     "use strict";
244
245     if (@isUndefinedOrNull(this))
246         @throwTypeError("String.prototype.search requires that |this| not be null or undefined");
247
248     if (regexp != null) {
249         var searcher = regexp.@searchSymbol;
250         if (searcher != @undefined)
251             return searcher.@call(regexp, this);
252     }
253
254     var thisString = @toString(this);
255     var createdRegExp = @regExpCreate(regexp, @undefined);
256     return createdRegExp.@searchSymbol(thisString);
257 }
258
259 function split(separator, limit)
260 {
261     "use strict";
262     
263     if (@isUndefinedOrNull(this))
264         @throwTypeError("String.prototype.split requires that |this| not be null or undefined");
265     
266     if (separator != null) {
267         var splitter = separator.@splitSymbol;
268         if (splitter != @undefined)
269             return splitter.@call(separator, this, limit);
270     }
271     
272     return @stringSplitFast.@call(this, separator, limit);
273 }
274
275 @globalPrivate
276 function stringConcatSlowPath()
277 {
278     "use strict";
279
280     var result = @toString(this);
281     for (var i = 0, length = arguments.length; i < length; ++i)
282         result += @toString(arguments[i]);
283     return result;
284 }
285
286 function concat(arg /* ... */)
287 {
288     "use strict";
289
290     if (@isUndefinedOrNull(this))
291         @throwTypeError("String.prototype.concat requires that |this| not be null or undefined");
292
293     if (@argumentCount() === 1)
294         return @toString(this) + @toString(arg);
295     return @tailCallForwardArguments(@stringConcatSlowPath, this);
296 }
297
298 @globalPrivate
299 function createHTML(func, string, tag, attribute, value)
300 {
301     "use strict";
302     if (@isUndefinedOrNull(string))
303         @throwTypeError(`${func} requires that |this| not be null or undefined`);
304     let S = @toString(string);
305     let p1 = "<" + tag;
306     if (attribute) {
307         let V = @toString(value);
308         let escapedV = V.@replaceUsingRegExp(/"/g, '&quot;');
309         p1 = p1 + " " + @toString(attribute) + '="' + escapedV + '"'
310     }
311     let p2 = p1 + ">"
312     let p3 = p2 + S;
313     let p4 = p3 + "</" + tag + ">";
314     return p4;
315 }
316
317 function anchor(url)
318 {
319     "use strict";
320     return @createHTML("String.prototype.link", this, "a", "name", url)
321 }
322
323 function big()
324 {
325     "use strict";
326     return @createHTML("String.prototype.big", this, "big", "", "");
327 }
328
329 function blink()
330 {
331     "use strict";
332     return @createHTML("String.prototype.blink", this, "blink", "", "");
333 }
334
335 function bold()
336 {
337     "use strict";
338     return @createHTML("String.prototype.bold", this, "b", "", "");
339 }
340
341 function fixed()
342 {
343     "use strict";
344     return @createHTML("String.prototype.fixed", this, "tt", "", "");
345 }
346
347 function fontcolor(color)
348 {
349     "use strict";
350     return @createHTML("String.prototype.fontcolor", this, "font", "color", color);
351 }
352
353 function fontsize(size)
354 {
355     "use strict";
356     return @createHTML("String.prototype.fontsize", this, "font", "size", size);
357 }
358
359 function italics()
360 {
361     "use strict";
362     return @createHTML("String.prototype.italics", this, "i", "", "");
363 }
364
365 function link(url)
366 {
367     "use strict";
368     return @createHTML("String.prototype.link", this, "a", "href", url)
369 }
370
371 function small()
372 {
373     "use strict";
374     return @createHTML("String.prototype.small", this, "small", "", "");
375 }
376
377 function strike()
378 {
379     "use strict";
380     return @createHTML("String.prototype.strike", this, "strike", "", "");
381 }
382
383 function sub()
384 {
385     "use strict";
386     return @createHTML("String.prototype.sub", this, "sub", "", "");
387 }
388
389 function sup()
390 {
391     "use strict";
392     return @createHTML("String.prototype.sup", this, "sup", "", "");
393 }