1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 08:27:45 +00:00

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

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);
}