LibJS: Refactor Array.prototype callback functions and make them generic

This commit is contained in:
Linus Groh 2020-05-21 21:24:43 +01:00 committed by Andreas Kling
parent 5db9becc4a
commit a4d04cc748
Notes: sideshowbarker 2024-07-19 06:16:13 +09:00
2 changed files with 146 additions and 206 deletions

View file

@ -84,100 +84,85 @@ static Function* callback_from_args(Interpreter& interpreter, const String& name
return &callback.as_function();
}
Value ArrayPrototype::filter(Interpreter& interpreter)
static size_t get_length(Interpreter& interpreter, Object& object)
{
auto* array = array_from(interpreter);
if (!array)
return {};
auto* callback = callback_from_args(interpreter, "filter");
if (!callback)
return {};
auto this_value = interpreter.argument(1);
auto initial_array_size = array->elements().size();
auto* new_array = Array::create(interpreter.global_object());
auto length_property = object.get("length");
if (interpreter.exception())
return 0;
return length_property.to_size_t(interpreter);
}
for (size_t i = 0; i < initial_array_size; ++i) {
if (i >= array->elements().size())
break;
auto value = array->elements()[i];
if (value.is_empty())
static void for_each_item(Interpreter& interpreter, const String& name, AK::Function<IterationDecision(size_t index, Value value, Value callback_result)> callback, bool skip_empty = true)
{
auto* this_object = interpreter.this_value().to_object(interpreter);
if (!this_object)
return;
auto initial_length = get_length(interpreter, *this_object);
if (interpreter.exception())
return;
auto* callback_function = callback_from_args(interpreter, name);
if (!callback_function)
return;
auto this_value = interpreter.argument(1);
for (size_t i = 0; i < initial_length; ++i) {
auto value = this_object->get_by_index(i);
if (value.is_empty()) {
if (skip_empty)
continue;
value = js_undefined();
}
MarkedValueList arguments(interpreter.heap());
arguments.append(value);
arguments.append(Value((i32)i));
arguments.append(array);
auto result = interpreter.call(*callback, this_value, move(arguments));
arguments.append(this_object);
auto callback_result = interpreter.call(*callback_function, this_value, move(arguments));
if (interpreter.exception())
return {};
if (result.to_boolean())
new_array->elements().append(value);
return;
if (callback(i, value, callback_result) == IterationDecision::Break)
break;
}
}
Value ArrayPrototype::filter(Interpreter& interpreter)
{
auto* new_array = Array::create(interpreter.global_object());
for_each_item(interpreter, "filter", [&](auto, auto value, auto callback_result) {
if (callback_result.to_boolean())
new_array->elements().append(value);
return IterationDecision::Continue;
});
return Value(new_array);
}
Value ArrayPrototype::for_each(Interpreter& interpreter)
{
auto* array = array_from(interpreter);
if (!array)
return {};
auto* callback = callback_from_args(interpreter, "forEach");
if (!callback)
return {};
auto this_value = interpreter.argument(1);
auto initial_array_size = array->elements().size();
for (size_t i = 0; i < initial_array_size; ++i) {
if (i >= array->elements().size())
break;
auto value = array->elements()[i];
if (value.is_empty())
continue;
MarkedValueList arguments(interpreter.heap());
arguments.append(value);
arguments.append(Value((i32)i));
arguments.append(array);
interpreter.call(*callback, this_value, move(arguments));
if (interpreter.exception())
return {};
}
for_each_item(interpreter, "forEach", [](auto, auto, auto) {
return IterationDecision::Continue;
});
return js_undefined();
}
Value ArrayPrototype::map(Interpreter& interpreter)
{
// FIXME: Make generic, i.e. work with length and numeric properties only
// This should work: Array.prototype.map.call("abc", ch => ...)
auto* array = array_from(interpreter);
if (!array)
auto* this_object = interpreter.this_value().to_object(interpreter);
if (!this_object)
return {};
auto* callback = callback_from_args(interpreter, "map");
if (!callback)
return {};
auto this_value = interpreter.argument(1);
auto initial_array_size = array->elements().size();
auto* new_array = Array::create(interpreter.global_object());
new_array->elements().resize(initial_array_size);
for (size_t i = 0; i < initial_array_size; ++i) {
if (i >= array->elements().size())
break;
auto value = array->elements()[i];
if (value.is_empty())
continue;
MarkedValueList arguments(interpreter.heap());
arguments.append(value);
arguments.append(Value((i32)i));
arguments.append(array);
auto result = interpreter.call(*callback, this_value, move(arguments));
auto initial_length = get_length(interpreter, *this_object);
if (interpreter.exception())
return {};
new_array->elements()[i] = result;
}
auto* new_array = Array::create(interpreter.global_object());
new_array->elements().resize(initial_length);
for_each_item(interpreter, "map", [&](auto index, auto, auto callback_result) {
new_array->elements()[index] = callback_result;
return IterationDecision::Continue;
});
return Value(new_array);
}
@ -452,150 +437,58 @@ Value ArrayPrototype::includes(Interpreter& interpreter)
Value ArrayPrototype::find(Interpreter& interpreter)
{
auto* array = array_from(interpreter);
if (!array)
return {};
auto* callback = callback_from_args(interpreter, "find");
if (!callback)
return {};
auto this_value = interpreter.argument(1);
auto array_size = array->elements().size();
for (size_t i = 0; i < array_size; ++i) {
auto value = js_undefined();
if (i < array->elements().size()) {
value = array->elements().at(i);
if (value.is_empty())
value = js_undefined();
auto result = js_undefined();
for_each_item(
interpreter, "find", [&](auto, auto value, auto callback_result) {
if (callback_result.to_boolean()) {
result = value;
return IterationDecision::Break;
}
MarkedValueList arguments(interpreter.heap());
arguments.append(value);
arguments.append(Value((i32)i));
arguments.append(array);
auto result = interpreter.call(*callback, this_value, move(arguments));
if (interpreter.exception())
return {};
if (result.to_boolean())
return value;
}
return js_undefined();
return IterationDecision::Continue;
},
false);
return result;
}
Value ArrayPrototype::find_index(Interpreter& interpreter)
{
auto* array = array_from(interpreter);
if (!array)
return {};
auto* callback = callback_from_args(interpreter, "findIndex");
if (!callback)
return {};
auto this_value = interpreter.argument(1);
auto array_size = array->elements().size();
for (size_t i = 0; i < array_size; ++i) {
auto value = js_undefined();
if (i < array->elements().size()) {
value = array->elements().at(i);
if (value.is_empty())
value = js_undefined();
auto result_index = -1;
for_each_item(
interpreter, "findIndex", [&](auto index, auto, auto callback_result) {
if (callback_result.to_boolean()) {
result_index = index;
return IterationDecision::Break;
}
MarkedValueList arguments(interpreter.heap());
arguments.append(value);
arguments.append(Value((i32)i));
arguments.append(array);
auto result = interpreter.call(*callback, this_value, move(arguments));
if (interpreter.exception())
return {};
if (result.to_boolean())
return Value((i32)i);
}
return Value(-1);
return IterationDecision::Continue;
},
false);
return Value(result_index);
}
Value ArrayPrototype::some(Interpreter& interpreter)
{
auto* array = array_from(interpreter);
if (!array)
return {};
auto* callback = callback_from_args(interpreter, "some");
if (!callback)
return {};
auto this_value = interpreter.argument(1);
auto array_size = array->elements().size();
for (size_t i = 0; i < array_size; ++i) {
if (i >= array->elements().size())
break;
auto value = array->elements().at(i);
if (value.is_empty())
continue;
MarkedValueList arguments(interpreter.heap());
arguments.append(value);
arguments.append(Value((i32)i));
arguments.append(array);
auto result = interpreter.call(*callback, this_value, move(arguments));
if (interpreter.exception())
return {};
if (result.to_boolean())
return Value(true);
auto result = false;
for_each_item(interpreter, "some", [&](auto, auto, auto callback_result) {
if (callback_result.to_boolean()) {
result = true;
return IterationDecision::Break;
}
return Value(false);
return IterationDecision::Continue;
});
return Value(result);
}
Value ArrayPrototype::every(Interpreter& interpreter)
{
auto* array = array_from(interpreter);
if (!array)
return {};
auto* callback = callback_from_args(interpreter, "every");
if (!callback)
return {};
auto this_value = interpreter.argument(1);
auto array_size = array->elements().size();
for (size_t i = 0; i < array_size; ++i) {
if (i >= array->elements().size())
break;
auto value = array->elements().at(i);
if (value.is_empty())
continue;
MarkedValueList arguments(interpreter.heap());
arguments.append(value);
arguments.append(Value((i32)i));
arguments.append(array);
auto result = interpreter.call(*callback, this_value, move(arguments));
if (interpreter.exception())
return {};
if (!result.to_boolean())
return Value(false);
auto result = true;
for_each_item(interpreter, "every", [&](auto, auto, auto callback_result) {
if (!callback_result.to_boolean()) {
result = false;
return IterationDecision::Break;
}
return Value(true);
return IterationDecision::Continue;
});
return Value(result);
}
}

View file

@ -0,0 +1,47 @@
load("test-common.js");
try {
const o = { length: 5, 0: "foo", 1: "bar", 3: "baz" };
["every"].forEach(name => {
const visited = [];
Array.prototype[name].call(o, function (value) {
visited.push(value);
return true;
});
assert(visited.length === 3);
assert(visited[0] === "foo");
assert(visited[1] === "bar");
assert(visited[2] === "baz");
});
["find", "findIndex"].forEach(name => {
const visited = [];
Array.prototype[name].call(o, function (value) {
visited.push(value);
return false;
});
assert(visited.length === 5);
assert(visited[0] === "foo");
assert(visited[1] === "bar");
assert(visited[2] === undefined);
assert(visited[3] === "baz");
assert(visited[4] === undefined);
});
["filter", "forEach", "map", "some"].forEach(name => {
const visited = [];
Array.prototype[name].call(o, function (value) {
visited.push(value);
return false;
});
assert(visited.length === 3);
assert(visited[0] === "foo");
assert(visited[1] === "bar");
assert(visited[2] === "baz");
});
console.log("PASS");
} catch (e) {
console.log("FAIL: " + e);
}