Skip to content

Commit

Permalink
Implement Array.prototype.every and Array.prototype.some in JS
Browse files Browse the repository at this point in the history
Summary: Implement `Array.prototype.every` and `Array.prototype.some` in JS

Reviewed By: dulinriley

Differential Revision: D17619231

fbshipit-source-id: 8b85b1b74a759916ad91e45009b2d7eab947f0c9
  • Loading branch information
Kiwi137 authored and facebook-github-bot committed Oct 10, 2019
1 parent 39a3126 commit 32e1fc1
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 110 deletions.
4 changes: 2 additions & 2 deletions include/hermes/VM/NativeFunctions.def
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ NATIVE_FUNCTION(arrayIteratorPrototypeNext)
NATIVE_FUNCTION(arrayOf)
NATIVE_FUNCTION(arrayPrototypeConcat)
NATIVE_FUNCTION(arrayPrototypeCopyWithin)
NATIVE_FUNCTION(arrayPrototypeEvery)
NATIVE_FUNCTION(arrayPrototypeFill)
NATIVE_FUNCTION(arrayPrototypeFilter)
NATIVE_FUNCTION(arrayPrototypeFind)
Expand All @@ -54,18 +53,19 @@ NATIVE_FUNCTION(arrayPrototypeReduce)
NATIVE_FUNCTION(arrayPrototypeReduceRight)
NATIVE_FUNCTION(arrayPrototypeShift)
NATIVE_FUNCTION(arrayPrototypeSlice)
NATIVE_FUNCTION(arrayPrototypeSome)
NATIVE_FUNCTION(arrayPrototypeSort)
NATIVE_FUNCTION(arrayPrototypeSplice)
NATIVE_FUNCTION(arrayPrototypeToLocaleString)
NATIVE_FUNCTION(arrayPrototypeToString)
NATIVE_FUNCTION(arrayPrototypeUnshift)

#ifndef HERMESVM_USE_JS_LIBRARY_IMPLEMENTATION
NATIVE_FUNCTION(arrayPrototypeEvery)
NATIVE_FUNCTION(arrayPrototypeIndexOf)
NATIVE_FUNCTION(arrayPrototypeLastIndexOf)
NATIVE_FUNCTION(arrayPrototypeMap)
NATIVE_FUNCTION(arrayPrototypeReverse)
NATIVE_FUNCTION(arrayPrototypeSome)
#endif // HERMESVM_USE_JS_LIBRARY_IMPLEMENTATION

NATIVE_FUNCTION(booleanConstructor)
Expand Down
105 changes: 105 additions & 0 deletions lib/InternalBytecode/Array.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,107 @@ Array.prototype.lastIndexOf = function lastIndexOf(searchElement, fromIndex = un
return arrayPrototypeIndexOfHelper(this, searchElement, fromIndex, arguments.length > 1, true);
}

/**
* Helper function for Array.prototype.every and Array.prototypr.some,
* see their docuemntations for more details.
* @this an array-like value.
* @param callbackfn function to be invoked on the elements.
* @param thisArg parameter which will be used as the <this> value
* for each invocation of callbackfn.
* @return In case of every, return true if callbackfn returns true for
* all elements in the array, false otherwise.
* In case of some, return false if callbackfn returns false for
* all elements in the array, true otherwise.
* @throw TypeError if callbackfn is not a function
*/
function arrayPrototypeEverySomeHelper(arr, callbackfn, thisArg, isEvery) {
// Array.prototype.every and Array.prototype.some cannot be used as constructors
if (new.target) throw _TypeError('This function cannot be used as a constructor.');

// 1. Let O be ? ToObject(this value).
var O = HermesInternal.toObject(arr);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = HermesInternal.toLength(O.length);

// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
if (typeof callbackfn !== 'function') {
if (isEvery) throw _TypeError('Array.prototype.every() requires a callable argument');
throw _TypeError('Array.prototype.some() requires a callable argument');
}

// 4. If thisArg is present, let T be thisArg; else let T be undefined.

// 5. Let k be 0.
var k = 0;

// fast path which calls the function directly when thisArg is undefined
if (thisArg === undefined) {
while (k < len) {
if (k in O) {
var testResult = callbackfn(O[k], k, O);
if (isEvery && !testResult) return false;
if (!isEvery && testResult) return true;
}
k += 1;
}
return isEvery ? true : false;
}

// 6. Repeat, while k < len
while (k < len) {
// a. Let Pk be ! ToString(k).
// b. Let kPresent be ? HasProperty(O, Pk).
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be ? Get(O, Pk).
// ii. Let Let testResult be ToBoolean(? Call(callbackfn, T, « kValue, k, O »)).
var testResult = !!HermesInternal.executeCall(callbackfn, thisArg, O[k], k, O);
// iii. If testResult is false, return false. (every)
// If testResult is false, return false. (some)
if (isEvery && !testResult) return false;
if (!isEvery && testResult) return true;
}
// d. Increase k by 1.
k += 1;
}

// 7. Return true. (every)
// Return false. (some)
return isEvery;
}

/**
* ES10.0 22.1.3.5
* Invoke a callback function on elements in an array-like object until
* a return value is falsy or there's no more elments.
* @this an array-like value.
* @param callbackfn function to be invoked on the elements.
* @param [thisArg] optional parameter which will be used as the <this> value
* for each invocation of callbackfn.
* @return true if callbackfn returns true for all elements in the array,
* false otherwise.
* @throw TypeError if callbackfn is not a function
*/
Array.prototype.every = function every(callbackfn, thisArg = undefined) {
return arrayPrototypeEverySomeHelper(this, callbackfn, thisArg, true);
}

/**
* ES10.0 22.1.3.26
* Invoke a callback function on elements in an array-like object until
* a return value is truthy or there's no more elments.
* @this an array-like value.
* @param callbackfn function to be invoked on the elements.
* @param [thisArg] optional parameter which will be used as the <this> value
* for each invocation of callbackfn.
* @return false if callbackfn returns false for all elements in the array,
* true otherwise.
* @throw TypeError if callbackfn is not a function
*/
Array.prototype.some = function some(callbackfn, thisArg = undefined) {
return arrayPrototypeEverySomeHelper(this, callbackfn, thisArg, false);
}

var arrayFuncDescriptor = {
configurable: true,
enumerable: false,
Expand All @@ -258,8 +359,12 @@ Object.defineProperty(Array.prototype, 'reverse', arrayFuncDescriptor);
Object.defineProperty(Array.prototype, 'map', arrayFuncDescriptor);
Object.defineProperty(Array.prototype, 'indexOf', arrayFuncDescriptor);
Object.defineProperty(Array.prototype, 'lastIndexOf', arrayFuncDescriptor);
Object.defineProperty(Array.prototype, 'every', arrayFuncDescriptor);
Object.defineProperty(Array.prototype, 'some', arrayFuncDescriptor);

Array.prototype.reverse.prototype = undefined;
Array.prototype.map.prototype = undefined;
Array.prototype.indexOf.prototype = undefined;
Array.prototype.lastIndexOf.prototype = undefined;
Array.prototype.every.prototype = undefined;
Array.prototype.some.prototype = undefined;
216 changes: 108 additions & 108 deletions lib/VM/JSLib/Array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,6 @@ Handle<JSObject> createArrayConstructor(Runtime *runtime) {
nullptr,
arrayPrototypeUnshift,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::every),
nullptr,
arrayPrototypeEvery,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::some),
nullptr,
arrayPrototypeSome,
1);
defineMethod(
runtime,
arrayPrototype,
Expand Down Expand Up @@ -218,6 +204,20 @@ Handle<JSObject> createArrayConstructor(Runtime *runtime) {
nullptr,
arrayPrototypeLastIndexOf,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::every),
nullptr,
arrayPrototypeEvery,
1);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::some),
nullptr,
arrayPrototypeSome,
1);
defineMethod(
runtime,
arrayPrototype,
Expand Down Expand Up @@ -2054,100 +2054,6 @@ arrayPrototypeUnshift(void *, Runtime *runtime, NativeArgs args) {
return newLen;
}

/// Helper function for every/some.
/// \param every true if calling every(), false if calling some().
static inline CallResult<HermesValue>
everySomeHelper(Runtime *runtime, NativeArgs args, const bool every) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime->makeHandle<JSObject>(objRes.getValue());

auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime->makeHandle(*propRes));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = *intRes;

auto callbackFn = args.dyncastArg<Callable>(0);
if (!callbackFn) {
return runtime->raiseTypeError(
"Array.prototype.every() requires a callable argument");
}

// Index to check the callback on.
MutableHandle<> k{runtime, HermesValue::encodeDoubleValue(0)};

// Value at index k;
MutableHandle<JSObject> descObjHandle{runtime};
MutableHandle<> kValue{runtime};

// Loop through and run the callback.
auto marker = gcScope.createMarker();
while (k->getDouble() < len) {
gcScope.flushToMarker(marker);

ComputedPropertyDescriptor desc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, k, descObjHandle, desc);

if (descObjHandle) {
// kPresent is true, call the callback on the kth element.
if ((propRes = JSObject::getComputedPropertyValue(
O, runtime, descObjHandle, desc)) ==
ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
kValue = propRes.getValue();
auto callRes = Callable::executeCall3(
callbackFn,
runtime,
args.getArgHandle(1),
kValue.get(),
k.get(),
O.getHermesValue());
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto testResult = *callRes;
if (every) {
// Done if one is false.
if (!toBoolean(testResult)) {
return HermesValue::encodeBoolValue(false);
}
} else {
// Done if one is true.
if (toBoolean(testResult)) {
return HermesValue::encodeBoolValue(true);
}
}
}

k = HermesValue::encodeDoubleValue(k->getDouble() + 1);
}

// If we're looking for every, then we finished without returning true.
// If we're looking for some, then we finished without returning false.
return HermesValue::encodeBoolValue(every);
}

CallResult<HermesValue>
arrayPrototypeEvery(void *, Runtime *runtime, NativeArgs args) {
return everySomeHelper(runtime, args, true);
}

CallResult<HermesValue>
arrayPrototypeSome(void *, Runtime *runtime, NativeArgs args) {
return everySomeHelper(runtime, args, false);
}

inline CallResult<HermesValue>
arrayPrototypeForEach(void *, Runtime *runtime, NativeArgs args) {
GCScope gcScope(runtime);
Expand Down Expand Up @@ -3013,6 +2919,100 @@ arrayPrototypeLastIndexOf(void *, Runtime *runtime, NativeArgs args) {
return indexOfHelper(runtime, args, true);
}

/// Helper function for every/some.
/// \param every true if calling every(), false if calling some().
static inline CallResult<HermesValue>
everySomeHelper(Runtime *runtime, NativeArgs args, const bool every) {
GCScope gcScope(runtime);
auto objRes = toObject(runtime, args.getThisHandle());
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto O = runtime->makeHandle<JSObject>(objRes.getValue());

auto propRes = JSObject::getNamed_RJS(
O, runtime, Predefined::getSymbolID(Predefined::length));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto intRes = toLengthU64(runtime, runtime->makeHandle(*propRes));
if (LLVM_UNLIKELY(intRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
uint64_t len = *intRes;

auto callbackFn = args.dyncastArg<Callable>(0);
if (!callbackFn) {
return runtime->raiseTypeError(
"Array.prototype.every() requires a callable argument");
}

// Index to check the callback on.
MutableHandle<> k{runtime, HermesValue::encodeDoubleValue(0)};

// Value at index k;
MutableHandle<JSObject> descObjHandle{runtime};
MutableHandle<> kValue{runtime};

// Loop through and run the callback.
auto marker = gcScope.createMarker();
while (k->getDouble() < len) {
gcScope.flushToMarker(marker);

ComputedPropertyDescriptor desc;
JSObject::getComputedPrimitiveDescriptor(
O, runtime, k, descObjHandle, desc);

if (descObjHandle) {
// kPresent is true, call the callback on the kth element.
if ((propRes = JSObject::getComputedPropertyValue(
O, runtime, descObjHandle, desc)) ==
ExecutionStatus::EXCEPTION) {
return ExecutionStatus::EXCEPTION;
}
kValue = propRes.getValue();
auto callRes = Callable::executeCall3(
callbackFn,
runtime,
args.getArgHandle(1),
kValue.get(),
k.get(),
O.getHermesValue());
if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto testResult = *callRes;
if (every) {
// Done if one is false.
if (!toBoolean(testResult)) {
return HermesValue::encodeBoolValue(false);
}
} else {
// Done if one is true.
if (toBoolean(testResult)) {
return HermesValue::encodeBoolValue(true);
}
}
}

k = HermesValue::encodeDoubleValue(k->getDouble() + 1);
}

// If we're looking for every, then we finished without returning true.
// If we're looking for some, then we finished without returning false.
return HermesValue::encodeBoolValue(every);
}

CallResult<HermesValue>
arrayPrototypeEvery(void *, Runtime *runtime, NativeArgs args) {
return everySomeHelper(runtime, args, true);
}

CallResult<HermesValue>
arrayPrototypeSome(void *, Runtime *runtime, NativeArgs args) {
return everySomeHelper(runtime, args, false);
}

CallResult<HermesValue>
arrayPrototypeMap(void *, Runtime *runtime, NativeArgs args) {
GCScope gcScope(runtime);
Expand Down
Loading

0 comments on commit 32e1fc1

Please sign in to comment.