Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement groupBy, withResolvers method #1352

Merged
merged 3 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/builtins/BuiltinMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "runtime/Context.h"
#include "runtime/VMInstance.h"
#include "runtime/MapObject.h"
#include "runtime/ArrayObject.h"
#include "runtime/IteratorObject.h"
#include "runtime/NativeFunctionObject.h"

Expand Down Expand Up @@ -194,6 +195,24 @@ static Value builtinMapIteratorNext(ExecutionState& state, Value thisValue, size
return iter->next(state);
}

// https://tc39.es/ecma262/multipage/keyed-collections.html#sec-map.groupby
static Value builtinMapGroupBy(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
{
// Let groups be ? GroupBy(items, callbackfn, COLLECTION).
auto groups = IteratorObject::groupBy(state, argv[0], argv[1], IteratorObject::GroupByKeyCoercion::Collection);
// Let map be ! Construct(%Map%).
auto map = new MapObject(state);
// For each Record { [[Key]], [[Elements]] } g of groups, do
for (size_t i = 0; i < groups.size(); i++) {
// Let elements be CreateArrayFromList(g.[[Elements]]).
// Let entry be the Record { [[Key]]: g.[[Key]], [[Value]]: elements }.
// Append entry to map.[[MapData]].
map->set(state, groups[i]->key, Value(Object::createArrayFromList(state, groups[i]->elements)));
}
// Return map.
return map;
}

void GlobalObject::initializeMap(ExecutionState& state)
{
ObjectPropertyNativeGetterSetterData* nativeData = new ObjectPropertyNativeGetterSetterData(true, false, true,
Expand All @@ -220,6 +239,10 @@ void GlobalObject::installMap(ExecutionState& state)
m_map->directDefineOwnProperty(state, ObjectPropertyName(state.context()->vmInstance()->globalSymbols().species), desc);
}

// Map.groupBy
m_map->directDefineOwnProperty(state, ObjectPropertyName(state.context()->staticStrings().groupBy),
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(state.context()->staticStrings().groupBy, builtinMapGroupBy, 2, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));

m_mapPrototype = new MapObject(state, m_objectPrototype);
m_mapPrototype->setGlobalIntrinsicObject(state, true);
m_mapPrototype->directDefineOwnProperty(state, ObjectPropertyName(state.context()->staticStrings().constructor), ObjectPropertyDescriptor(m_map, (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
Expand Down
22 changes: 22 additions & 0 deletions src/builtins/BuiltinObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,24 @@ static Value builtinLookupSetter(ExecutionState& state, Value thisValue, size_t
return Value();
}

// https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.groupby
static Value builtinObjectGroupBy(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
{
// Let groups be ? GroupBy(items, callbackfn, PROPERTY).
auto groups = IteratorObject::groupBy(state, argv[0], argv[1], IteratorObject::GroupByKeyCoercion::Property);
// Let obj be OrdinaryObjectCreate(null).
auto obj = new Object(state, Object::PrototypeIsNull);
// For each Record { [[Key]], [[Elements]] } g of groups, do
for (size_t i = 0; i < groups.size(); i++) {
// Let elements be CreateArrayFromList(g.[[Elements]]).
// Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements).
obj->defineOwnPropertyThrowsException(state, ObjectPropertyName(state, groups[i]->key),
ObjectPropertyDescriptor(Object::createArrayFromList(state, groups[i]->elements), ObjectPropertyDescriptor::AllPresent));
}
// Return obj.
return obj;
}

void GlobalObject::initializeObject(ExecutionState& state)
{
// Object should be installed at the start time
Expand Down Expand Up @@ -877,6 +895,10 @@ void GlobalObject::installObject(ExecutionState& state)
m_object->directDefineOwnProperty(state, ObjectPropertyName(strings.hasOwn),
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings.hasOwn, builtinObjectHasOwn, 2, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));

// Object.groupBy
m_object->directDefineOwnProperty(state, ObjectPropertyName(strings.groupBy),
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings.groupBy, builtinObjectGroupBy, 2, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));

// $19.1.3.2 Object.prototype.hasOwnProperty(V)
m_objectPrototype->directDefineOwnProperty(state, ObjectPropertyName(strings.hasOwnProperty),
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings.hasOwnProperty, builtinObjectHasOwnProperty, 1, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
Expand Down
33 changes: 33 additions & 0 deletions src/builtins/BuiltinPromise.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,34 @@ static Value builtinPromiseAny(ExecutionState& state, Value thisValue, size_t ar
return result;
}

// https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promise.withResolvers
static Value builtinPromiseWithResolvers(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
{
// Let C be the this value.
const Value& C = thisValue;
// Let promiseCapability be ? NewPromiseCapability(C).
if (!C.isObject()) {
// NOTE: isConstructor will be checked on PromiseObject::newPromiseCapability function
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, ErrorObject::Messages::Not_Constructor);
}
auto promiseCapability = PromiseObject::newPromiseCapability(state, C.asObject());
// Let obj be OrdinaryObjectCreate(%Object.prototype%).
Object* obj = new Object(state);

StaticStrings* strings = &state.context()->staticStrings();
// Perform ! CreateDataPropertyOrThrow(obj, "promise", promiseCapability.[[Promise]]).
obj->defineOwnPropertyThrowsException(state, ObjectPropertyName(strings->lazyPromise()),
ObjectPropertyDescriptor(promiseCapability.m_promise, ObjectPropertyDescriptor::AllPresent));
// Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]).
obj->defineOwnPropertyThrowsException(state, ObjectPropertyName(strings->resolve),
ObjectPropertyDescriptor(promiseCapability.m_resolveFunction, ObjectPropertyDescriptor::AllPresent));
// Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]).
obj->defineOwnPropertyThrowsException(state, ObjectPropertyName(strings->reject),
ObjectPropertyDescriptor(promiseCapability.m_rejectFunction, ObjectPropertyDescriptor::AllPresent));
// Return obj.
return obj;
}

void GlobalObject::initializePromise(ExecutionState& state)
{
ObjectPropertyNativeGetterSetterData* nativeData = new ObjectPropertyNativeGetterSetterData(true, false, true,
Expand Down Expand Up @@ -819,6 +847,11 @@ void GlobalObject::installPromise(ExecutionState& state)
ObjectPropertyDescriptor(m_promiseAny,
(ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));

// Promise.withResolvers()
m_promise->directDefineOwnProperty(state, ObjectPropertyName(strings->withResolvers),
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->withResolvers, builtinPromiseWithResolvers, 0, NativeFunctionInfo::Strict)),
(ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));


redefineOwnProperty(state, ObjectPropertyName(strings->Promise),
ObjectPropertyDescriptor(m_promise, (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
Expand Down
95 changes: 95 additions & 0 deletions src/runtime/IteratorObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,99 @@ ValueVector IteratorObject::iterableToListOfType(ExecutionState& state, const Va
return values;
}

// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-add-value-to-keyed-group
static void addValueToKeyedGroup(ExecutionState& state, IteratorObject::KeyedGroupVector& groups, const Value& key, const Value& value)
{
// For each Record { [[Key]], [[Elements]] } g of groups, do
for (size_t i = 0; i < groups.size(); i++) {
// If SameValue(g.[[Key]], key) is true, then
if (groups[i]->key.equalsToByTheSameValueAlgorithm(state, key)) {
// Assert: Exactly one element of groups meets this criterion.
// Append value to g.[[Elements]].
groups[i]->elements.pushBack(value);
// Return UNUSED.
return;
}
}
// Let group be the Record { [[Key]]: key, [[Elements]]: « value » }.
// Append group to groups.
IteratorObject::KeyedGroup* kg = new IteratorObject::KeyedGroup();
kg->key = key;
kg->elements.pushBack(value);
groups.pushBack(kg);
// Return UNUSED.
return;
}

IteratorObject::KeyedGroupVector IteratorObject::groupBy(ExecutionState& state, const Value& items, const Value& callbackfn, GroupByKeyCoercion keyCoercion)
{
// Perform ? RequireObjectCoercible(items).
if (items.isUndefinedOrNull()) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "GroupBy called on undefined or null");
}
// If IsCallable(callbackfn) is false, throw a TypeError exception.
if (!callbackfn.isCallable()) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "GroupBy called on non-callable value");
}
// Let groups be a new empty List.
KeyedGroupVector groups;
// Let iteratorRecord be ? GetIterator(items, SYNC).
IteratorRecord* iteratorRecord = IteratorObject::getIterator(state, items, true);
// Let k be 0.
int64_t k = 0;

// Repeat,
while (true) {
// a. If k ≥ 2**53 - 1, then
if (k >= ((1LL << 53LL) - 1LL)) {
// Let error be ThrowCompletion(a newly created TypeError object).
Value error = ErrorObject::createError(state, ErrorCode::TypeError, new ASCIIString("Too many result on GroupBy function"));
// Return ? IteratorClose(iteratorRecord, error).
IteratorObject::iteratorClose(state, iteratorRecord, error, true);
ASSERT_NOT_REACHED();
}

// Let next be ? IteratorStepValue(iteratorRecord).
Optional<Object*> next = IteratorObject::iteratorStep(state, iteratorRecord);
// If next is DONE, then
if (!next) {
// Return groups.
return groups;
}

// Let value be next.
Value value = IteratorObject::iteratorValue(state, next.value());
// Let key be Completion(Call(callbackfn, undefined, « value, 𝔽(k) »)).
Value key;
try {
Value argv[2] = { value, Value(k) };
key = Object::call(state, callbackfn, Value(), 2, argv);
} catch (const Value& error) {
// IfAbruptCloseIterator(key, iteratorRecord).
IteratorObject::iteratorClose(state, iteratorRecord, error, true);
ASSERT_NOT_REACHED();
}
// If keyCoercion is PROPERTY, then
if (keyCoercion == GroupByKeyCoercion::Property) {
// Set key to Completion(ToPropertyKey(key)).
try {
key = key.toPropertyKey(state);
} catch (const Value& error) {
// IfAbruptCloseIterator(key, iteratorRecord).
IteratorObject::iteratorClose(state, iteratorRecord, error, true);
ASSERT_NOT_REACHED();
}
} else {
// Assert: keyCoercion is COLLECTION.
ASSERT(keyCoercion == GroupByKeyCoercion::Collection);
// Set key to CanonicalizeKeyedCollectionKey(key).
key = key.toCanonicalizeKeyedCollectionKey(state);
}
// Perform AddValueToKeyedGroup(groups, key, value).
addValueToKeyedGroup(state, groups, key, value);
// Set k to k + 1.
k = k + 1;
}
}

} // namespace Escargot
11 changes: 11 additions & 0 deletions src/runtime/IteratorObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ class IteratorObject : public DerivedObject {
// https://www.ecma-international.org/ecma-262/10.0/#sec-iterabletolist
static ValueVectorWithInlineStorage iterableToList(ExecutionState& state, const Value& items, Optional<Value> method = Optional<Value>());
static ValueVector iterableToListOfType(ExecutionState& state, const Value items, const String* elementTypes);
// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-groupby
enum GroupByKeyCoercion {
Property,
Collection
};
struct KeyedGroup : public gc {
Value key;
ValueVector elements;
};
typedef Vector<KeyedGroup*, GCUtil::gc_malloc_allocator<KeyedGroup*>> KeyedGroupVector;
static KeyedGroupVector groupBy(ExecutionState& state, const Value& items, const Value& callbackfn, GroupByKeyCoercion keyCoercion);
};
} // namespace Escargot

Expand Down
3 changes: 3 additions & 0 deletions src/runtime/StaticStrings.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ namespace Escargot {
F(global) \
F(globalThis) \
F(groups) \
F(groupBy) \
F(grow) \
F(growable) \
F(has) \
Expand Down Expand Up @@ -485,6 +486,7 @@ namespace Escargot {
F(values) \
F(var) \
F(with) \
F(withResolvers) \
F(writable) \
F(yield)

Expand Down Expand Up @@ -682,6 +684,7 @@ namespace Escargot {
F(ObjectRegExpToString, "[object RegExp]") \
F(ObjectStringToString, "[object String]") \
F(ObjectUndefinedToString, "[object Undefined]") \
F(Promise, "promise") \
F(Reason, "reason") \
F(Rejected, "rejected") \
F(Status, "status") \
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/Value.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@ class Value {
inline uint32_t tryToUseAsIndex32(ExecutionState& ec) const;
inline uint32_t tryToUseAsIndexProperty(ExecutionState& ec) const;

// https://tc39.es/ecma262/multipage/keyed-collections.html#sec-canonicalizekeyedcollectionkey
Value toCanonicalizeKeyedCollectionKey(ExecutionState& state) const;

inline bool abstractEqualsTo(ExecutionState& ec, const Value& val) const;
bool abstractEqualsToSlowCase(ExecutionState& ec, const Value& val) const;
inline bool equalsTo(ExecutionState& ec, const Value& val) const;
Expand Down
14 changes: 14 additions & 0 deletions src/runtime/ValueInlines.h
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,20 @@ inline uint64_t Value::toLength(ExecutionState& state) const
}
return std::min(len, maximumLength());
}

inline Value Value::toCanonicalizeKeyedCollectionKey(ExecutionState&) const
{
if (isDouble()) {
double d = asDouble();
// convert -0.0 into 0.0
// in c++, d = -0.0, d == 0.0 is true
if (d == 0.0) {
return Value(0);
}
}
return *this;
}

} // namespace Escargot

#endif
26 changes: 0 additions & 26 deletions tools/test/test262/excludelist.orig.xml
Original file line number Diff line number Diff line change
Expand Up @@ -581,38 +581,12 @@
<test id="built-ins/JSON/parse/reviver-call-order"><reason>TODO</reason></test>
<test id="built-ins/JSON/stringify/value-bigint-tojson-receiver"><reason>TODO</reason></test>
<test id="built-ins/JSON/stringify/value-string-escape-unicode"><reason>TODO</reason></test>
<test id="built-ins/Map/groupBy/callback-arg"><reason>TODO</reason></test>
<test id="built-ins/Map/groupBy/callback-throws"><reason>TODO</reason></test>
<test id="built-ins/Map/groupBy/emptyList"><reason>TODO</reason></test>
<test id="built-ins/Map/groupBy/evenOdd"><reason>TODO</reason></test>
<test id="built-ins/Map/groupBy/groupLength"><reason>TODO</reason></test>
<test id="built-ins/Map/groupBy/iterator-next-throws"><reason>TODO</reason></test>
<test id="built-ins/Map/groupBy/length"><reason>TODO</reason></test>
<test id="built-ins/Map/groupBy/map-instance"><reason>TODO</reason></test>
<test id="built-ins/Map/groupBy/name"><reason>TODO</reason></test>
<test id="built-ins/Map/groupBy/negativeZero"><reason>TODO</reason></test>
<test id="built-ins/Map/groupBy/toPropertyKey"><reason>TODO</reason></test>
<test id="built-ins/Object/freeze/proxy-with-defineProperty-handler"><reason>TODO</reason></test>
<test id="built-ins/Object/groupBy/callback-arg"><reason>TODO</reason></test>
<test id="built-ins/Object/groupBy/callback-throws"><reason>TODO</reason></test>
<test id="built-ins/Object/groupBy/emptyList"><reason>TODO</reason></test>
<test id="built-ins/Object/groupBy/evenOdd"><reason>TODO</reason></test>
<test id="built-ins/Object/groupBy/groupLength"><reason>TODO</reason></test>
<test id="built-ins/Object/groupBy/invalid-property-key"><reason>TODO</reason></test>
<test id="built-ins/Object/groupBy/iterator-next-throws"><reason>TODO</reason></test>
<test id="built-ins/Object/groupBy/length"><reason>TODO</reason></test>
<test id="built-ins/Object/groupBy/name"><reason>TODO</reason></test>
<test id="built-ins/Object/groupBy/null-prototype"><reason>TODO</reason></test>
<test id="built-ins/Object/groupBy/toPropertyKey"><reason>TODO</reason></test>
<test id="built-ins/Object/prototype/toString/symbol-tag-array-builtin"><reason>TODO</reason></test>
<test id="built-ins/Object/prototype/toString/symbol-tag-map-builtin"><reason>TODO</reason></test>
<test id="built-ins/Object/prototype/toString/symbol-tag-set-builtin"><reason>TODO</reason></test>
<test id="built-ins/Object/prototype/toString/symbol-tag-string-builtin"><reason>TODO</reason></test>
<test id="built-ins/Object/seal/proxy-with-defineProperty-handler"><reason>TODO</reason></test>
<test id="built-ins/Promise/withResolvers/ctx-ctor"><reason>TODO</reason></test>
<test id="built-ins/Promise/withResolvers/promise"><reason>TODO</reason></test>
<test id="built-ins/Promise/withResolvers/resolvers"><reason>TODO</reason></test>
<test id="built-ins/Promise/withResolvers/result"><reason>TODO</reason></test>
<test id="built-ins/RegExp/duplicate-flags"><reason>TODO</reason></test>
<test id="built-ins/RegExp/duplicate-named-capturing-groups-syntax"><reason>TODO</reason></test>
<test id="built-ins/RegExp/lookBehind/alternations"><reason>TODO</reason></test>
Expand Down
Loading