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 various missing methods in TypedArray #1524

Closed
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* will be put in the JavaDoc of each method that implements an Abstract Operations
* </ul>
*/
class AbstractEcmaObjectOperations {
public class AbstractEcmaObjectOperations {
enum INTEGRITY_LEVEL {
FROZEN,
SEALED
Expand Down Expand Up @@ -161,7 +161,7 @@ static boolean setIntegrityLevel(Context cx, Object o, INTEGRITY_LEVEL level) {
* constructor on "s" or if the "species" symbol is not set.
* @see <a href="https://tc39.es/ecma262/#sec-speciesconstructor"></a>
*/
static Constructable speciesConstructor(
public static Constructable speciesConstructor(
Context cx, Scriptable s, Constructable defaultConstructor) {
/*
The abstract operation SpeciesConstructor takes arguments O (an Object) and
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
package org.mozilla.javascript;

import static org.mozilla.javascript.NativeArray.getLengthProperty;
import static org.mozilla.javascript.ScriptRuntimeES6.requireObjectCoercible;
import static org.mozilla.javascript.Scriptable.NOT_FOUND;

import java.io.Serializable;
import java.util.Comparator;
import org.mozilla.javascript.regexp.NativeRegExp;

/** Contains implementation of shared methods useful for arrays and typed arrays. */
public class ArrayLikeAbstractOperations {
public enum IterativeOperation {
EVERY,
FILTER,
FOR_EACH,
MAP,
SOME,
FIND,
FIND_INDEX,
}

public enum ReduceOperation {
REDUCE,
REDUCE_RIGHT,
}

/** Implements the methods "every", "filter", "forEach", "map", and "some". */
public static Object iterativeMethod(
Context cx,
IdFunctionObject fun,
IterativeOperation operation,
Scriptable scope,
Scriptable thisObj,
Object[] args) {
Scriptable o = ScriptRuntime.toObject(cx, scope, thisObj);

if (IterativeOperation.FIND == operation || IterativeOperation.FIND_INDEX == operation) {
requireObjectCoercible(cx, o, fun);
}

long length = getLengthProperty(cx, o);
if (operation == IterativeOperation.MAP && length > Integer.MAX_VALUE) {
String msg = ScriptRuntime.getMessageById("msg.arraylength.bad");
throw ScriptRuntime.rangeError(msg);
}

Object callbackArg = args.length > 0 ? args[0] : Undefined.instance;

Function f = getCallbackArg(cx, callbackArg);
Scriptable parent = ScriptableObject.getTopLevelScope(f);
Scriptable thisArg;
if (args.length < 2 || args[1] == null || args[1] == Undefined.instance) {
thisArg = parent;
} else {
thisArg = ScriptRuntime.toObject(cx, scope, args[1]);
}

Scriptable array = null;
if (operation == IterativeOperation.FILTER || operation == IterativeOperation.MAP) {
int resultLength = operation == IterativeOperation.MAP ? (int) length : 0;
array = cx.newArray(scope, resultLength);
}
long j = 0;
for (long i = 0; i < length; i++) {
Object[] innerArgs = new Object[3];
Object elem = getRawElem(o, i);
if (elem == NOT_FOUND) {
if (operation == IterativeOperation.FIND
|| operation == IterativeOperation.FIND_INDEX) {
elem = Undefined.instance;
} else {
continue;
}
}
innerArgs[0] = elem;
innerArgs[1] = Long.valueOf(i);
innerArgs[2] = o;
Object result = f.call(cx, parent, thisArg, innerArgs);
switch (operation) {
case EVERY:
if (!ScriptRuntime.toBoolean(result)) return Boolean.FALSE;
break;
case FILTER:
if (ScriptRuntime.toBoolean(result)) defineElem(cx, array, j++, innerArgs[0]);
break;
case FOR_EACH:
break;
case MAP:
defineElem(cx, array, i, result);
break;
case SOME:
if (ScriptRuntime.toBoolean(result)) return Boolean.TRUE;
break;
case FIND:
if (ScriptRuntime.toBoolean(result)) return elem;
break;
case FIND_INDEX:
if (ScriptRuntime.toBoolean(result)) return ScriptRuntime.wrapNumber(i);
break;
}
}
switch (operation) {
case EVERY:
return Boolean.TRUE;
case FILTER:
case MAP:
return array;
case SOME:
return Boolean.FALSE;
case FIND_INDEX:
return ScriptRuntime.wrapNumber(-1);
case FOR_EACH:
default:
return Undefined.instance;
}
}

static Function getCallbackArg(Context cx, Object callbackArg) {
if (!(callbackArg instanceof Function)) {
throw ScriptRuntime.notFunctionError(callbackArg);
}
if (cx.getLanguageVersion() >= Context.VERSION_ES6
&& (callbackArg instanceof NativeRegExp)) {
// Previously, it was allowed to pass RegExp instance as a callback (it implements
// Function)
// But according to ES2015 21.2.6 Properties of RegExp Instances:
// > RegExp instances are ordinary objects that inherit properties from the RegExp
// prototype object.
// > RegExp instances have internal slots [[RegExpMatcher]], [[OriginalSource]], and
// [[OriginalFlags]].
// so, no [[Call]] for RegExp-s
throw ScriptRuntime.notFunctionError(callbackArg);
}

Function f = (Function) callbackArg;
return f;
}

static void defineElem(Context cx, Scriptable target, long index, Object value) {
if (index > Integer.MAX_VALUE) {
String id = Long.toString(index);
target.put(id, target, value);
} else {
target.put((int) index, target, value);
}
}

// same as NativeArray::getElem, but without converting NOT_FOUND to undefined
static Object getRawElem(Scriptable target, long index) {
if (index > Integer.MAX_VALUE) {
return ScriptableObject.getProperty(target, Long.toString(index));
}
return ScriptableObject.getProperty(target, (int) index);
}

public static long toSliceIndex(double value, long length) {
long result;
if (value < 0.0) {
if (value + length < 0.0) {
result = 0;
} else {
result = (long) (value + length);
}
} else if (value > length) {
result = length;
} else {
result = (long) value;
}
return result;
}

/** Implements the methods "reduce" and "reduceRight". */
public static Object reduceMethod(
Context cx,
ReduceOperation operation,
Scriptable scope,
Scriptable thisObj,
Object[] args) {
Scriptable o = ScriptRuntime.toObject(cx, scope, thisObj);

long length = getLengthProperty(cx, o);
Object callbackArg = args.length > 0 ? args[0] : Undefined.instance;
if (callbackArg == null || !(callbackArg instanceof Function)) {
throw ScriptRuntime.notFunctionError(callbackArg);
}
Function f = (Function) callbackArg;
Scriptable parent = ScriptableObject.getTopLevelScope(f);
// hack to serve both reduce and reduceRight with the same loop
boolean movingLeft = operation == ReduceOperation.REDUCE;
Object value = args.length > 1 ? args[1] : NOT_FOUND;
for (long i = 0; i < length; i++) {
long index = movingLeft ? i : (length - 1 - i);
Object elem = getRawElem(o, index);
if (elem == NOT_FOUND) {
continue;
}
if (value == NOT_FOUND) {
// no initial value passed, use first element found as inital value
value = elem;
} else {
Object[] innerArgs = {value, elem, index, o};
value = f.call(cx, parent, parent, innerArgs);
}
}
if (value == NOT_FOUND) {
// reproduce spidermonkey error message
throw ScriptRuntime.typeErrorById("msg.empty.array.reduce");
}
return value;
}

public static Comparator<Object> getSortComparator(
final Context cx, final Scriptable scope, final Object[] args) {
if (args.length > 0 && Undefined.instance != args[0]) {
return getSortComparatorFromArguments(cx, scope, args);
} else {
return DEFAULT_COMPARATOR;
}
}

public static ElementComparator getSortComparatorFromArguments(
Context cx, Scriptable scope, Object[] args) {
final Callable jsCompareFunction = ScriptRuntime.getValueFunctionAndThis(args[0], cx);
final Scriptable funThis = ScriptRuntime.lastStoredScriptable(cx);
final Object[] cmpBuf = new Object[2]; // Buffer for cmp arguments
return new ElementComparator(
new Comparator<Object>() {
@Override
public int compare(final Object x, final Object y) {
// This comparator is invoked only for non-undefined objects
cmpBuf[0] = x;
cmpBuf[1] = y;
Object ret = jsCompareFunction.call(cx, scope, funThis, cmpBuf);
double d = ScriptRuntime.toNumber(ret);
int cmp = Double.compare(d, 0);
if (cmp < 0) {
return -1;
} else if (cmp > 0) {
return +1;
}
return 0;
}
});
}

// Comparators for the js_sort method. Putting them here lets us unit-test them better.

private static final Comparator<Object> STRING_COMPARATOR = new StringLikeComparator();
private static final Comparator<Object> DEFAULT_COMPARATOR = new ElementComparator();

public static final class StringLikeComparator implements Comparator<Object>, Serializable {

private static final long serialVersionUID = 5299017659728190979L;

@Override
public int compare(final Object x, final Object y) {
final String a = ScriptRuntime.toString(x);
final String b = ScriptRuntime.toString(y);
return a.compareTo(b);
}
}

public static final class ElementComparator implements Comparator<Object>, Serializable {

private static final long serialVersionUID = -1189948017688708858L;

private final Comparator<Object> child;

public ElementComparator() {
child = STRING_COMPARATOR;
}

public ElementComparator(Comparator<Object> c) {
child = c;
}

@Override
public int compare(final Object x, final Object y) {
// Sort NOT_FOUND to very end, Undefined before that, exclusively, as per
// ECMA 22.1.3.25.1.
if (x == Undefined.instance) {
if (y == Undefined.instance) {
return 0;
}
if (y == NOT_FOUND) {
return -1;
}
return 1;
} else if (x == NOT_FOUND) {
return y == NOT_FOUND ? 0 : 1;
}

if (y == NOT_FOUND) {
return -1;
}
if (y == Undefined.instance) {
return -1;
}

return child.compare(x, y);
}
}
}
Loading
Loading