blob: da8a37e2bc8a8ad9b3a097eb49281528ed246d18 [file] [log] [blame]
/*
* Copyright (C) 2015 Andy VanWagoner <andy@vanwagoner.family>.
* Copyright (C) 2016 Yusuke Suzuki <utatane.tea@gmail.com>
* Copyright (C) 2016-2018 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
function match(regexp)
{
"use strict";
if (@isUndefinedOrNull(this))
@throwTypeError("String.prototype.match requires that |this| not be null or undefined");
if (regexp != null) {
var matcher = regexp.@matchSymbol;
if (matcher != @undefined)
return matcher.@call(regexp, this);
}
let thisString = @toString(this);
let createdRegExp = @regExpCreate(regexp, @undefined);
return createdRegExp.@matchSymbol(thisString);
}
function matchAll(arg)
{
"use strict";
if (@isUndefinedOrNull(this))
@throwTypeError("String.prototype.matchAll requires |this| not to be null nor undefined");
if (!@isUndefinedOrNull(arg)) {
if (@isRegExp(arg) && !@stringIncludesInternal.@call(@toString(arg.flags), "g"))
@throwTypeError("String.prototype.matchAll argument must not be a non-global regular expression");
let matcher = arg.@matchAllSymbol;
if (!@isUndefinedOrNull(matcher))
return matcher.@call(arg, this);
}
let string = @toString(this);
let regExp = @regExpCreate(arg, "g");
return regExp.@matchAllSymbol(string);
}
@globalPrivate
function repeatSlowPath(string, count)
{
"use strict";
// Return an empty string.
if (count === 0 || string.length === 0)
return "";
// Return the original string.
if (count === 1)
return string;
if (string.length * count > @MAX_STRING_LENGTH)
@throwOutOfMemoryError();
// Bit operation onto |count| is safe because |count| should be within Int32 range,
// Repeat log N times to generate the repeated string rope.
var result = "";
var operand = string;
while (true) {
if (count & 1)
result += operand;
count >>= 1;
if (!count)
return result;
operand += operand;
}
}
@globalPrivate
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";
if (@isUndefinedOrNull(this))
@throwTypeError("String.prototype.repeat requires that |this| not be null or undefined");
var string = @toString(this);
count = @toInteger(count);
if (count < 0 || count === @Infinity)
@throwRangeError("String.prototype.repeat argument must be greater than or equal to 0 and not be Infinity");
if (string.length === 1)
return @repeatCharacter(string, count);
return @repeatSlowPath(string, count);
}
function padStart(maxLength/*, fillString*/)
{
"use strict";
if (@isUndefinedOrNull(this))
@throwTypeError("String.prototype.padStart requires that |this| not be null or undefined");
var string = @toString(this);
maxLength = @toLength(maxLength);
var stringLength = string.length;
if (maxLength <= stringLength)
return string;
var filler;
var fillString = @argument(1);
if (fillString === @undefined)
filler = " ";
else {
filler = @toString(fillString);
if (filler === "")
return string;
}
if (maxLength > @MAX_STRING_LENGTH)
@throwOutOfMemoryError();
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 (@isUndefinedOrNull(this))
@throwTypeError("String.prototype.padEnd requires that |this| not be null or undefined");
var string = @toString(this);
maxLength = @toLength(maxLength);
var stringLength = string.length;
if (maxLength <= stringLength)
return string;
var filler;
var fillString = @argument(1);
if (fillString === @undefined)
filler = " ";
else {
filler = @toString(fillString);
if (filler === "")
return string;
}
if (maxLength > @MAX_STRING_LENGTH)
@throwOutOfMemoryError();
var fillLength = maxLength - stringLength;
var truncatedStringFiller;
if (filler.length === 1)
truncatedStringFiller = @repeatCharacter(filler, fillLength);
else
truncatedStringFiller = @repeatCharactersSlowPath(filler, fillLength);
return string + truncatedStringFiller;
}
@globalPrivate
function hasObservableSideEffectsForStringReplace(regexp, replacer)
{
"use strict";
if (!@isRegExpObject(regexp))
return true;
if (replacer !== @regExpPrototypeSymbolReplace)
return true;
let regexpExec = @tryGetById(regexp, "exec");
if (regexpExec !== @regExpBuiltinExec)
return true;
let regexpGlobal = @tryGetById(regexp, "global");
if (regexpGlobal !== @regExpProtoGlobalGetter)
return true;
let regexpUnicode = @tryGetById(regexp, "unicode");
if (regexpUnicode !== @regExpProtoUnicodeGetter)
return true;
return typeof regexp.lastIndex !== "number";
}
@intrinsic=StringPrototypeReplaceIntrinsic
function replace(search, replace)
{
"use strict";
if (@isUndefinedOrNull(this))
@throwTypeError("String.prototype.replace requires that |this| not be null or undefined");
if (search != null) {
let replacer = search.@replaceSymbol;
if (replacer !== @undefined) {
if (!@hasObservableSideEffectsForStringReplace(search, replacer))
return @toString(this).@replaceUsingRegExp(search, replace);
return replacer.@call(search, this, replace);
}
}
let thisString = @toString(this);
let searchString = @toString(search);
return thisString.@replaceUsingStringSearch(searchString, replace);
}
function search(regexp)
{
"use strict";
if (@isUndefinedOrNull(this))
@throwTypeError("String.prototype.search requires that |this| not be null or undefined");
if (regexp != null) {
var searcher = regexp.@searchSymbol;
if (searcher != @undefined)
return searcher.@call(regexp, this);
}
var thisString = @toString(this);
var createdRegExp = @regExpCreate(regexp, @undefined);
return createdRegExp.@searchSymbol(thisString);
}
function split(separator, limit)
{
"use strict";
if (@isUndefinedOrNull(this))
@throwTypeError("String.prototype.split requires that |this| not be null or undefined");
if (separator != null) {
var splitter = separator.@splitSymbol;
if (splitter != @undefined)
return splitter.@call(separator, this, limit);
}
return @stringSplitFast.@call(this, separator, limit);
}
@globalPrivate
function stringConcatSlowPath()
{
"use strict";
var result = @toString(this);
for (var i = 0, length = arguments.length; i < length; ++i)
result += @toString(arguments[i]);
return result;
}
function concat(arg /* ... */)
{
"use strict";
if (@isUndefinedOrNull(this))
@throwTypeError("String.prototype.concat requires that |this| not be null or undefined");
if (@argumentCount() === 1)
return @toString(this) + @toString(arg);
return @tailCallForwardArguments(@stringConcatSlowPath, this);
}
@globalPrivate
function createHTML(func, string, tag, attribute, value)
{
"use strict";
if (@isUndefinedOrNull(string))
@throwTypeError(`${func} requires that |this| not be null or undefined`);
let S = @toString(string);
let p1 = "<" + tag;
if (attribute) {
let V = @toString(value);
let escapedV = V.@replaceUsingRegExp(/"/g, '&quot;');
p1 = p1 + " " + @toString(attribute) + '="' + escapedV + '"'
}
let p2 = p1 + ">"
let p3 = p2 + S;
let p4 = p3 + "</" + tag + ">";
return p4;
}
function anchor(url)
{
"use strict";
return @createHTML("String.prototype.link", this, "a", "name", url)
}
function big()
{
"use strict";
return @createHTML("String.prototype.big", this, "big", "", "");
}
function blink()
{
"use strict";
return @createHTML("String.prototype.blink", this, "blink", "", "");
}
function bold()
{
"use strict";
return @createHTML("String.prototype.bold", this, "b", "", "");
}
function fixed()
{
"use strict";
return @createHTML("String.prototype.fixed", this, "tt", "", "");
}
function fontcolor(color)
{
"use strict";
return @createHTML("String.prototype.fontcolor", this, "font", "color", color);
}
function fontsize(size)
{
"use strict";
return @createHTML("String.prototype.fontsize", this, "font", "size", size);
}
function italics()
{
"use strict";
return @createHTML("String.prototype.italics", this, "i", "", "");
}
function link(url)
{
"use strict";
return @createHTML("String.prototype.link", this, "a", "href", url)
}
function small()
{
"use strict";
return @createHTML("String.prototype.small", this, "small", "", "");
}
function strike()
{
"use strict";
return @createHTML("String.prototype.strike", this, "strike", "", "");
}
function sub()
{
"use strict";
return @createHTML("String.prototype.sub", this, "sub", "", "");
}
function sup()
{
"use strict";
return @createHTML("String.prototype.sup", this, "sup", "", "");
}