RegExpPrototype.js [plain text]
@globalPrivate
function advanceStringIndex(string, index, unicode)
{
"use strict";
if (!unicode)
return index + 1;
if (index + 1 >= string.length)
return index + 1;
let first = string.@charCodeAt(index);
if (first < 0xD800 || first > 0xDBFF)
return index + 1;
let second = string.@charCodeAt(index + 1);
if (second < 0xDC00 || second > 0xDFFF)
return index + 1;
return index + 2;
}
@globalPrivate
function regExpExec(regexp, str)
{
"use strict";
let exec = regexp.exec;
let builtinExec = @regExpBuiltinExec;
if (exec !== builtinExec && typeof exec === "function") {
let result = exec.@call(regexp, str);
if (result !== null && !@isObject(result))
@throwTypeError("The result of a RegExp exec must be null or an object");
return result;
}
return builtinExec.@call(regexp, str);
}
@globalPrivate
function hasObservableSideEffectsForRegExpMatch(regexp)
{
"use strict";
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 !@isRegExpObject(regexp);
}
@globalPrivate
function matchSlow(regexp, str)
{
"use strict";
if (!regexp.global)
return @regExpExec(regexp, str);
let unicode = regexp.unicode;
regexp.lastIndex = 0;
let resultList = [];
const maximumReasonableMatchSize = 100000000;
while (true) {
let result = @regExpExec(regexp, str);
if (result === null) {
if (resultList.length === 0)
return null;
return resultList;
}
if (resultList.length > maximumReasonableMatchSize)
@throwOutOfMemoryError();
if (!@isObject(result))
@throwTypeError("RegExp.prototype.@@match call to RegExp.exec didn't return null or an object");
let resultString = @toString(result[0]);
if (!resultString.length)
regexp.lastIndex = @advanceStringIndex(str, regexp.lastIndex, unicode);
resultList.@push(resultString);
}
}
@overriddenName="[Symbol.match]"
function match(strArg)
{
"use strict";
if (!@isObject(this))
@throwTypeError("RegExp.prototype.@@match requires that |this| be an Object");
let str = @toString(strArg);
if (!@hasObservableSideEffectsForRegExpMatch(this))
return @regExpMatchFast.@call(this, str);
return @matchSlow(this, str);
}
@overriddenName="[Symbol.replace]"
function replace(strArg, replace)
{
"use strict";
function getSubstitution(matched, str, position, captures, replacement)
{
"use strict";
let matchLength = matched.length;
let stringLength = str.length;
let tailPos = position + matchLength;
let m = captures.length;
let replacementLength = replacement.length;
let result = "";
let lastStart = 0;
for (let start = 0; start = replacement.indexOf("$", lastStart), start !== -1; lastStart = start) {
if (start - lastStart > 0)
result = result + replacement.substring(lastStart, start);
start++;
let ch = replacement.charAt(start);
if (ch === "")
result = result + "$";
else {
switch (ch)
{
case "$":
result = result + "$";
start++;
break;
case "&":
result = result + matched;
start++;
break;
case "`":
if (position > 0)
result = result + str.substring(0, position);
start++;
break;
case "'":
if (tailPos < stringLength)
result = result + str.substring(tailPos);
start++;
break;
default:
let chCode = ch.charCodeAt(0);
if (chCode >= 0x30 && chCode <= 0x39) {
start++;
let n = chCode - 0x30;
if (n > m)
break;
if (start < replacementLength) {
let nextChCode = replacement.charCodeAt(start);
if (nextChCode >= 0x30 && nextChCode <= 0x39) {
let nn = 10 * n + nextChCode - 0x30;
if (nn <= m) {
n = nn;
start++;
}
}
}
if (n == 0)
break;
if (captures[n] != @undefined)
result = result + captures[n];
} else
result = result + "$";
break;
}
}
}
return result + replacement.substring(lastStart);
}
if (!@isObject(this))
@throwTypeError("RegExp.prototype.@@replace requires that |this| be an Object");
let regexp = this;
let str = @toString(strArg);
let stringLength = str.length;
let functionalReplace = typeof replace === 'function';
if (!functionalReplace)
replace = @toString(replace);
let global = regexp.global;
let unicode = false;
if (global) {
unicode = regexp.unicode;
regexp.lastIndex = 0;
}
let resultList = [];
let result;
let done = false;
while (!done) {
result = @regExpExec(regexp, str);
if (result === null)
done = true;
else {
resultList.@push(result);
if (!global)
done = true;
else {
let matchStr = @toString(result[0]);
if (!matchStr.length)
regexp.lastIndex = @advanceStringIndex(str, regexp.lastIndex, unicode);
}
}
}
let accumulatedResult = "";
let nextSourcePosition = 0;
let lastPosition = 0;
for (let i = 0, resultListLength = resultList.length; i < resultListLength; ++i) {
let result = resultList[i];
let nCaptures = result.length - 1;
if (nCaptures < 0)
nCaptures = 0;
let matched = @toString(result[0]);
let matchLength = matched.length;
let position = result.index;
position = (position > stringLength) ? stringLength : position;
position = (position < 0) ? 0 : position;
let captures = [];
for (let n = 1; n <= nCaptures; n++) {
let capN = result[n];
if (capN !== @undefined)
capN = @toString(capN);
captures[n] = capN;
}
let replacement;
if (functionalReplace) {
let replacerArgs = [ matched ].concat(captures.slice(1));
replacerArgs.@push(position);
replacerArgs.@push(str);
let replValue = replace.@apply(@undefined, replacerArgs);
replacement = @toString(replValue);
} else
replacement = getSubstitution(matched, str, position, captures, replace);
if (position >= nextSourcePosition && position >= lastPosition) {
accumulatedResult = accumulatedResult + str.substring(nextSourcePosition, position) + replacement;
nextSourcePosition = position + matchLength;
lastPosition = position;
}
}
if (nextSourcePosition >= stringLength)
return accumulatedResult;
return accumulatedResult + str.substring(nextSourcePosition);
}
@overriddenName="[Symbol.search]"
function search(strArg)
{
"use strict";
let regexp = this;
if (@isRegExpObject(regexp) && @tryGetById(regexp, "exec") === @regExpBuiltinExec)
return @regExpSearchFast.@call(regexp, strArg);
if (!@isObject(this))
@throwTypeError("RegExp.prototype.@@search requires that |this| be an Object");
let str = @toString(strArg)
let previousLastIndex = regexp.lastIndex;
if (previousLastIndex !== 0)
regexp.lastIndex = 0;
let result = @regExpExec(regexp, str);
if (regexp.lastIndex !== previousLastIndex)
regexp.lastIndex = previousLastIndex;
if (result === null)
return -1;
return result.index;
}
@globalPrivate
function hasObservableSideEffectsForRegExpSplit(regexp)
{
"use strict";
let regexpExec = @tryGetById(regexp, "exec");
if (regexpExec !== @regExpBuiltinExec)
return true;
let regexpFlags = @tryGetById(regexp, "flags");
if (regexpFlags !== @regExpProtoFlagsGetter)
return true;
let regexpGlobal = @tryGetById(regexp, "global");
if (regexpGlobal !== @regExpProtoGlobalGetter)
return true;
let regexpIgnoreCase = @tryGetById(regexp, "ignoreCase");
if (regexpIgnoreCase !== @regExpProtoIgnoreCaseGetter)
return true;
let regexpMultiline = @tryGetById(regexp, "multiline");
if (regexpMultiline !== @regExpProtoMultilineGetter)
return true;
let regexpSticky = @tryGetById(regexp, "sticky");
if (regexpSticky !== @regExpProtoStickyGetter)
return true;
let regexpUnicode = @tryGetById(regexp, "unicode");
if (regexpUnicode !== @regExpProtoUnicodeGetter)
return true;
let regexpSource = @tryGetById(regexp, "source");
if (regexpSource !== @regExpProtoSourceGetter)
return true;
return !@isRegExpObject(regexp);
}
@overriddenName="[Symbol.split]"
function split(string, limit)
{
"use strict";
if (!@isObject(this))
@throwTypeError("RegExp.prototype.@@split requires that |this| be an Object");
let regexp = this;
let str = @toString(string);
let speciesConstructor = @speciesConstructor(regexp, @RegExp);
if (speciesConstructor === @RegExp && !@hasObservableSideEffectsForRegExpSplit(regexp))
return @regExpSplitFast.@call(regexp, str, limit);
let flags = @toString(regexp.flags);
let unicodeMatching = @stringIncludesInternal.@call(flags, "u");
let newFlags = @stringIncludesInternal.@call(flags, "y") ? flags : flags + "y";
let splitter = new speciesConstructor(regexp, newFlags);
if (!@hasObservableSideEffectsForRegExpSplit(splitter))
return @regExpSplitFast.@call(splitter, str, limit);
let result = [];
limit = (limit === @undefined) ? 0xffffffff : limit >>> 0;
if (!limit)
return result;
let size = str.length;
if (!size) {
let z = @regExpExec(splitter, str);
if (z != null)
return result;
@putByValDirect(result, 0, str);
return result;
}
let position = 0;
let matchPosition = 0;
while (matchPosition < size) {
splitter.lastIndex = matchPosition;
let matches = @regExpExec(splitter, str);
if (matches === null)
matchPosition = @advanceStringIndex(str, matchPosition, unicodeMatching);
else {
let endPosition = @toLength(splitter.lastIndex);
endPosition = (endPosition <= size) ? endPosition : size;
if (endPosition === position)
matchPosition = @advanceStringIndex(str, matchPosition, unicodeMatching);
else {
let subStr = @stringSubstrInternal.@call(str, position, matchPosition - position);
@putByValDirect(result, result.length, subStr);
if (result.length == limit)
return result;
position = endPosition;
let numberOfCaptures = matches.length > 1 ? matches.length - 1 : 0;
let i = 1;
while (i <= numberOfCaptures) {
let nextCapture = matches[i];
@putByValDirect(result, result.length, nextCapture);
if (result.length == limit)
return result;
i++;
}
matchPosition = position;
}
}
}
let remainingStr = @stringSubstrInternal.@call(str, position, size);
@putByValDirect(result, result.length, remainingStr);
return result;
}
@intrinsic=RegExpTestIntrinsic
function test(strArg)
{
"use strict";
let regexp = this;
if (@isRegExpObject(regexp) && @tryGetById(regexp, "exec") === @regExpBuiltinExec)
return @regExpTestFast.@call(regexp, strArg);
if (!@isObject(regexp))
@throwTypeError("RegExp.prototype.test requires that |this| be an Object");
let str = @toString(strArg);
let match = @regExpExec(regexp, str);
if (match !== null)
return true;
return false;
}