зеркало из https://github.com/mozilla/rhino.git
Implement ES2024 `groupBy`
This commit is contained in:
Родитель
c0936cae9b
Коммит
4e6523b61b
|
@ -1,5 +1,10 @@
|
|||
package org.mozilla.javascript;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Abstract Object Operations as defined by EcmaScript
|
||||
*
|
||||
|
@ -24,6 +29,11 @@ public class AbstractEcmaObjectOperations {
|
|||
SEALED
|
||||
}
|
||||
|
||||
enum KEY_COERCION {
|
||||
PROPERTY,
|
||||
COLLECTION,
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Abstract Object operation HasOwnProperty as defined by EcmaScript
|
||||
*
|
||||
|
@ -232,4 +242,65 @@ public class AbstractEcmaObjectOperations {
|
|||
base.put(p, o, v);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement the ECMAScript abstract operation "GroupBy" defined in section 7.3.35 of ECMA262.
|
||||
*
|
||||
* @param cx
|
||||
* @param scope
|
||||
* @param items
|
||||
* @param callback
|
||||
* @param keyCoercion
|
||||
* @see <a href="https://tc39.es/ecma262/#sec-groupby"></a>
|
||||
*/
|
||||
static Map<Object, List<Object>> groupBy(
|
||||
Context cx,
|
||||
Scriptable scope,
|
||||
IdFunctionObject f,
|
||||
Object items,
|
||||
Object callback,
|
||||
KEY_COERCION keyCoercion) {
|
||||
if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
|
||||
ScriptRuntimeES6.requireObjectCoercible(cx, items, f);
|
||||
}
|
||||
if (!(callback instanceof Callable)) {
|
||||
throw ScriptRuntime.typeErrorById(
|
||||
"msg.isnt.function", callback, ScriptRuntime.typeof(callback));
|
||||
}
|
||||
|
||||
// LinkedHashMap used to preserve key creation order
|
||||
Map<Object, List<Object>> groups = new LinkedHashMap<>();
|
||||
final Object iterator = ScriptRuntime.callIterator(items, cx, scope);
|
||||
try (IteratorLikeIterable it = new IteratorLikeIterable(cx, scope, iterator)) {
|
||||
double i = 0;
|
||||
for (Object o : it) {
|
||||
if (i > NativeNumber.MAX_SAFE_INTEGER) {
|
||||
it.close();
|
||||
throw ScriptRuntime.typeError("Too many values to iterate");
|
||||
}
|
||||
|
||||
Object[] args = {o, i};
|
||||
Object key =
|
||||
((Callable) callback).call(cx, scope, Undefined.SCRIPTABLE_UNDEFINED, args);
|
||||
if (keyCoercion == KEY_COERCION.PROPERTY) {
|
||||
if (!ScriptRuntime.isSymbol(key)) {
|
||||
key = ScriptRuntime.toString(key);
|
||||
}
|
||||
} else {
|
||||
assert keyCoercion == KEY_COERCION.COLLECTION;
|
||||
if ((key instanceof Number)
|
||||
&& ((Number) key).doubleValue() == ScriptRuntime.negativeZero) {
|
||||
key = ScriptRuntime.zeroObj;
|
||||
}
|
||||
}
|
||||
|
||||
List<Object> group = groups.computeIfAbsent(key, (k) -> new ArrayList<>());
|
||||
group.add(o);
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
|
||||
package org.mozilla.javascript;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class NativeMap extends IdScriptableObject {
|
||||
private static final long serialVersionUID = 1171922614280016891L;
|
||||
private static final Object MAP_TAG = "Map";
|
||||
|
@ -37,6 +40,12 @@ public class NativeMap extends IdScriptableObject {
|
|||
return "Map";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillConstructorProperties(IdFunctionObject ctor) {
|
||||
addIdFunctionProperty(ctor, MAP_TAG, ConstructorId_groupBy, "groupBy", 2);
|
||||
super.fillConstructorProperties(ctor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execIdCall(
|
||||
IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
|
||||
|
@ -82,6 +91,30 @@ public class NativeMap extends IdScriptableObject {
|
|||
args.length > 1 ? args[1] : Undefined.instance);
|
||||
case SymbolId_getSize:
|
||||
return realThis(thisObj, f).js_getSize();
|
||||
|
||||
case ConstructorId_groupBy:
|
||||
{
|
||||
Object items = args.length < 1 ? Undefined.instance : args[0];
|
||||
Object callback = args.length < 2 ? Undefined.instance : args[1];
|
||||
|
||||
Map<Object, List<Object>> groups =
|
||||
AbstractEcmaObjectOperations.groupBy(
|
||||
cx,
|
||||
scope,
|
||||
f,
|
||||
items,
|
||||
callback,
|
||||
AbstractEcmaObjectOperations.KEY_COERCION.COLLECTION);
|
||||
|
||||
NativeMap map = (NativeMap) cx.newObject(scope, "Map");
|
||||
|
||||
for (Map.Entry<Object, List<Object>> entry : groups.entrySet()) {
|
||||
Scriptable elements = cx.newArray(scope, entry.getValue().toArray());
|
||||
map.entries.put(entry.getKey(), elements);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Map.prototype has no method: " + f.getFunctionName());
|
||||
}
|
||||
|
@ -315,7 +348,8 @@ public class NativeMap extends IdScriptableObject {
|
|||
|
||||
// Note that "SymbolId_iterator" is not present here. That's because the spec
|
||||
// requires that it be the same value as the "entries" prototype property.
|
||||
private static final int Id_constructor = 1,
|
||||
private static final int ConstructorId_groupBy = -1,
|
||||
Id_constructor = 1,
|
||||
Id_set = 2,
|
||||
Id_get = 3,
|
||||
Id_delete = 4,
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
|
@ -85,6 +86,7 @@ public class NativeObject extends IdScriptableObject implements Map {
|
|||
addIdFunctionProperty(ctor, OBJECT_TAG, ConstructorId_freeze, "freeze", 1);
|
||||
addIdFunctionProperty(ctor, OBJECT_TAG, ConstructorId_assign, "assign", 2);
|
||||
addIdFunctionProperty(ctor, OBJECT_TAG, ConstructorId_is, "is", 2);
|
||||
addIdFunctionProperty(ctor, OBJECT_TAG, ConstructorId_groupBy, "groupBy", 2);
|
||||
super.fillConstructorProperties(ctor);
|
||||
}
|
||||
|
||||
|
@ -687,6 +689,37 @@ public class NativeObject extends IdScriptableObject implements Map {
|
|||
return ScriptRuntime.wrapBoolean(ScriptRuntime.same(a1, a2));
|
||||
}
|
||||
|
||||
case ConstructorId_groupBy:
|
||||
{
|
||||
Object items = args.length < 1 ? Undefined.instance : args[0];
|
||||
Object callback = args.length < 2 ? Undefined.instance : args[1];
|
||||
|
||||
Map<Object, List<Object>> groups =
|
||||
AbstractEcmaObjectOperations.groupBy(
|
||||
cx,
|
||||
scope,
|
||||
f,
|
||||
items,
|
||||
callback,
|
||||
AbstractEcmaObjectOperations.KEY_COERCION.PROPERTY);
|
||||
|
||||
NativeObject obj = (NativeObject) cx.newObject(scope);
|
||||
obj.setPrototype(null);
|
||||
|
||||
for (Map.Entry<Object, List<Object>> entry : groups.entrySet()) {
|
||||
Scriptable elements = cx.newArray(scope, entry.getValue().toArray());
|
||||
|
||||
ScriptableObject desc = (ScriptableObject) cx.newObject(scope);
|
||||
desc.put("enumerable", desc, Boolean.TRUE);
|
||||
desc.put("configurable", desc, Boolean.TRUE);
|
||||
desc.put("value", desc, elements);
|
||||
|
||||
obj.defineOwnProperty(cx, entry.getKey(), desc);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException(String.valueOf(id));
|
||||
}
|
||||
|
@ -1021,6 +1054,7 @@ public class NativeObject extends IdScriptableObject implements Map {
|
|||
ConstructorId_fromEntries = -20,
|
||||
ConstructorId_values = -21,
|
||||
ConstructorId_hasOwn = -22,
|
||||
ConstructorId_groupBy = -23,
|
||||
Id_constructor = 1,
|
||||
Id_toString = 2,
|
||||
Id_toLocaleString = 3,
|
||||
|
|
|
@ -991,19 +991,7 @@ built-ins/JSON 37/144 (25.69%)
|
|||
stringify/value-object-proxy-revoked.js {unsupported: [Proxy]}
|
||||
stringify/value-string-escape-unicode.js
|
||||
|
||||
built-ins/Map 25/171 (14.62%)
|
||||
groupBy/callback-arg.js
|
||||
groupBy/callback-throws.js
|
||||
groupBy/emptyList.js
|
||||
groupBy/evenOdd.js
|
||||
groupBy/groupLength.js
|
||||
groupBy/iterator-next-throws.js
|
||||
groupBy/length.js
|
||||
groupBy/map-instance.js
|
||||
groupBy/name.js
|
||||
groupBy/negativeZero.js
|
||||
groupBy/string.js
|
||||
groupBy/toPropertyKey.js
|
||||
built-ins/Map 13/171 (7.6%)
|
||||
prototype/clear/not-a-constructor.js {unsupported: [Reflect.construct]}
|
||||
prototype/delete/not-a-constructor.js {unsupported: [Reflect.construct]}
|
||||
prototype/entries/not-a-constructor.js {unsupported: [Reflect.construct]}
|
||||
|
@ -1110,7 +1098,7 @@ built-ins/Number 24/335 (7.16%)
|
|||
S9.3.1_A3_T1_U180E.js {unsupported: [u180e]}
|
||||
S9.3.1_A3_T2_U180E.js {unsupported: [u180e]}
|
||||
|
||||
built-ins/Object 230/3403 (6.76%)
|
||||
built-ins/Object 218/3403 (6.41%)
|
||||
assign/assignment-to-readonly-property-of-target-must-throw-a-typeerror-exception.js
|
||||
assign/not-a-constructor.js {unsupported: [Reflect.construct]}
|
||||
assign/source-own-prop-desc-missing.js {unsupported: [Proxy]}
|
||||
|
@ -1200,18 +1188,6 @@ built-ins/Object 230/3403 (6.76%)
|
|||
getOwnPropertySymbols/proxy-invariant-not-extensible-absent-string-key.js {unsupported: [Proxy]}
|
||||
getOwnPropertySymbols/proxy-invariant-not-extensible-extra-string-key.js {unsupported: [Proxy]}
|
||||
getPrototypeOf/not-a-constructor.js {unsupported: [Reflect.construct]}
|
||||
groupBy/callback-arg.js
|
||||
groupBy/callback-throws.js
|
||||
groupBy/emptyList.js
|
||||
groupBy/evenOdd.js
|
||||
groupBy/groupLength.js
|
||||
groupBy/invalid-property-key.js
|
||||
groupBy/iterator-next-throws.js
|
||||
groupBy/length.js
|
||||
groupBy/name.js
|
||||
groupBy/null-prototype.js
|
||||
groupBy/string.js
|
||||
groupBy/toPropertyKey.js
|
||||
hasOwn/length.js
|
||||
hasOwn/not-a-constructor.js {unsupported: [Reflect.construct]}
|
||||
hasOwn/symbol_property_toPrimitive.js
|
||||
|
@ -5306,7 +5282,7 @@ language/expressions/new 41/59 (69.49%)
|
|||
|
||||
~language/expressions/new.target
|
||||
|
||||
language/expressions/object 867/1169 (74.17%)
|
||||
language/expressions/object 866/1169 (74.08%)
|
||||
dstr/async-gen-meth-ary-init-iter-close.js {unsupported: [async-iteration, async]}
|
||||
dstr/async-gen-meth-ary-init-iter-get-err.js {unsupported: [async-iteration]}
|
||||
dstr/async-gen-meth-ary-init-iter-get-err-array-prototype.js {unsupported: [async-iteration]}
|
||||
|
|
Загрузка…
Ссылка в новой задаче