Skip to content

Commit

Permalink
Implement String.prototype.replace in JS
Browse files Browse the repository at this point in the history
Summary: Implement `String.prototype.replace` in JS

Reviewed By: dulinriley

Differential Revision: D18249270

fbshipit-source-id: dc5e4456c51941dee0cee5584075a776273fb033
  • Loading branch information
Kiwi137 authored and facebook-github-bot committed Nov 8, 2019
1 parent c31cbb9 commit e359bee
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 146 deletions.
3 changes: 2 additions & 1 deletion include/hermes/VM/NativeFunctions.def
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ NATIVE_FUNCTION(hermesInternalGetWeakSize)

#ifdef HERMESVM_USE_JS_LIBRARY_IMPLEMENTATION
NATIVE_FUNCTION(hermesInternalExecuteCall)
NATIVE_FUNCTION(hermesInternalGetSubstitution)
NATIVE_FUNCTION(hermesInternalIsConstructor)
NATIVE_FUNCTION(hermesInternalIsRegExp)
NATIVE_FUNCTION(hermesInternalJSArraySetElementAt)
Expand Down Expand Up @@ -292,7 +293,6 @@ NATIVE_FUNCTION(stringPrototypeConcat)
NATIVE_FUNCTION(stringPrototypeLocaleCompare)
NATIVE_FUNCTION(stringPrototypeNormalize)
NATIVE_FUNCTION(stringPrototypeRepeat)
NATIVE_FUNCTION(stringPrototypeReplace)
NATIVE_FUNCTION(stringPrototypeSubstr)
NATIVE_FUNCTION(stringPrototypeSubstring)
NATIVE_FUNCTION(stringPrototypeSymbolIterator)
Expand All @@ -314,6 +314,7 @@ NATIVE_FUNCTION(stringPrototypeIndexOf)
NATIVE_FUNCTION(stringPrototypeLastIndexOf)
NATIVE_FUNCTION(stringPrototypeMatch)
NATIVE_FUNCTION(stringPrototypePad)
NATIVE_FUNCTION(stringPrototypeReplace)
NATIVE_FUNCTION(stringPrototypeSearch)
NATIVE_FUNCTION(stringPrototypeSlice)
NATIVE_FUNCTION(stringPrototypeSplit)
Expand Down
92 changes: 92 additions & 0 deletions lib/InternalBytecode/String.js
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,96 @@ String.prototype.padEnd = function padEnd(maxLength, fillString = undefined) {
return stringPrototypePadHelper(this, maxLength, fillString, false);
};

/**
* ES10.0 21.1.3.16
* Search for searchValue in `this` string and replace the first
* occurrence with replaceValue.
* @this a value that's coercible to string.
* @param searchValue pattern to search for in `this` string, can be both
* string or regexp. If it's a regexp, behavior can change
* based on its flags.
* @param replaceValue value to be replaced with. Can be both string or
* function. If it's a function, the return value will
* be used.
* @return the resulting string after replacing searchValue with replaceValue
* in `this` string.
*/
String.prototype.replace = function replace(searchValue, replaceValue) {
if (new.target)
throw _TypeError('This function cannot be used as a constructor.');

// 1. Let O be ? RequireObjectCoercible(this value).
if (this === undefined || this === null) {
throw _TypeError('Value not coercible to object');
}

// 2. If searchValue is neither undefined nor null, then
if (searchValue !== undefined && searchValue != null) {
// a. Let replacer be ? GetMethod(searchValue, @@replace).
var replacer = getMethod(searchValue, _SymbolReplace);
// b. If replacer is not undefined, then
if (replacer !== undefined) {
// i. Return ? Call(replacer, searchValue, « O, replaceValue »).
return HermesInternal.executeCall(
replacer,
searchValue,
this,
replaceValue,
);
}
}

// 3. Let string be ? ToString(O).
var string = HermesInternal.toString(this);
// 4. Let searchString be ? ToString(searchValue).
var searchString = HermesInternal.toString(searchValue);
// 5. Let functionalReplace be IsCallable(replaceValue).
var functionalReplace = typeof replaceValue === 'function';
// 6. If functionalReplace is false, then
if (!functionalReplace) {
// a. Set replaceValue to ? ToString(replaceValue).
replaceValue = HermesInternal.toString(replaceValue);
}
// 7. Search string for the first occurrence of searchString and let pos be
// the index within string of the first code unit of the matched substring
// and let matched be searchString. If no occurrences of searchString were
// found, return string.
var pos = HermesInternal.searchString(string, searchString);
if (pos < 0) return string;
// 8. If functionalReplace is true, then
var replStr;
if (functionalReplace) {
// a. Let replValue be ? Call(replaceValue, undefined,
// « matched, pos, string »).
// b. Let replStr be ? ToString(replValue).
replStr = HermesInternal.toString(replaceValue(searchString, pos, string));
// 9. Else,
} else {
// a. Let captures be a new empty List.
// b. Let replStr be GetSubstitution(matched, string, pos, captures,
// undefined, replaceValue).
replStr = HermesInternal.getSubstitution(
searchString,
string,
pos,
[],
replaceValue,
);
}
// 10. Let tailPos be pos + the number of code units in matched.
var tailPos = pos + searchString.length;
// 11. Let newString be the string-concatenation of the first pos code units
// of string, replStr, and the trailing substring of string starting at
// index tailPos. If pos is 0, the first element of the concatenation will
// be the empty String.
// 12. Return newString.
return (
HermesInternal.executeCall(_StringPrototypeSubstring, string, 0, pos) +
replStr +
HermesInternal.executeCall(_StringPrototypeSubstring, string, tailPos)
);
};

Object.defineProperty(String.prototype, 'charAt', builtinFuncDescriptor);
Object.defineProperty(String.prototype, 'match', builtinFuncDescriptor);
Object.defineProperty(String.prototype, 'search', builtinFuncDescriptor);
Expand All @@ -658,6 +748,7 @@ Object.defineProperty(String.prototype, 'split', builtinFuncDescriptor);
Object.defineProperty(String.prototype, 'slice', builtinFuncDescriptor);
Object.defineProperty(String.prototype, 'padStart', builtinFuncDescriptor);
Object.defineProperty(String.prototype, 'padEnd', builtinFuncDescriptor);
Object.defineProperty(String.prototype, 'replace', builtinFuncDescriptor);

String.prototype.charAt.prototype = undefined;
String.prototype.match.prototype = undefined;
Expand All @@ -671,3 +762,4 @@ String.prototype.split.prototype = undefined;
String.prototype.slice.prototype = undefined;
String.prototype.padStart.prototype = undefined;
String.prototype.padEnd.prototype = undefined;
String.prototype.replace.prototype = undefined;
1 change: 1 addition & 0 deletions lib/InternalBytecode/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
var _StringPrototypeSubstring = String.prototype.substring;
var _SymbolIterator = Symbol.iterator;
var _SymbolMatch = Symbol.match;
var _SymbolReplace = Symbol.replace;
var _SymbolSearch = Symbol.search;
var _SymbolSplit = Symbol.split;
var _TypeError = TypeError;
Expand Down
44 changes: 44 additions & 0 deletions lib/VM/JSLib/HermesInternal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,49 @@ hermesInternalExecuteCall(void *, Runtime *runtime, NativeArgs args) {
return Callable::call(func, runtime);
}

/// HermesInternal.getSubstitution
/// = function (matched,str, position, captures, replacement) {}
/// \encode
/// Returns true if func is a valid constructor, false otherwise.
CallResult<HermesValue>
hermesInternalGetSubstitution(void *, Runtime *runtime, NativeArgs args) {
auto matched = args.dyncastArg<StringPrimitive>(0);
auto str = args.dyncastArg<StringPrimitive>(1);
auto replacement = args.dyncastArg<StringPrimitive>(4);
if (!matched || !str || !replacement) {
return runtime->raiseTypeError(
"First, second, and fifth arguments should be strings");
}

auto posArg = args.getArg(2);
if (!posArg.isNumber()) {
return runtime->raiseTypeError("Third argument should be a number");
}
uint32_t position = posArg.getNumberAs<uint32_t>();

Handle<JSArray> capturesArg = args.dyncastArg<JSArray>(3);
if (!capturesArg) {
return runtime->raiseTypeError("Fourth argument should be an array");
}

uint32_t capturesLen = JSArray::getLength(*capturesArg);
auto arrRes = ArrayStorage::create(runtime, capturesLen /* capacity */);
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
MutableHandle<ArrayStorage> captures{runtime,
vmcast<ArrayStorage>(arrRes.getValue())};

for (uint i = 0; i < capturesLen; ++i) {
GCScopeMarkerRAII marker{runtime};
ArrayStorage::push_back(
captures, runtime, capturesArg->handleAt(runtime, i));
}

return getSubstitution(
runtime, matched, str, position, captures, replacement);
}

/// \code
/// HermesInternal.isConstructor = function (func) {}
/// \endcode
Expand Down Expand Up @@ -1221,6 +1264,7 @@ Handle<JSObject> createHermesInternalObject(Runtime *runtime) {
defineInternMethod(P::exponentiationOperator, mathPow);
#ifdef HERMESVM_USE_JS_LIBRARY_IMPLEMENTATION
defineInternMethodAndSymbol("executeCall", hermesInternalExecuteCall);
defineInternMethodAndSymbol("getSubstitution", hermesInternalGetSubstitution);
defineInternMethodAndSymbol("isConstructor", hermesInternalIsConstructor);
defineInternMethodAndSymbol("isRegExp", hermesInternalIsRegExp);
defineInternMethodAndSymbol(
Expand Down
Loading

0 comments on commit e359bee

Please sign in to comment.