summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
Diffstat (limited to 'Userland')
-rw-r--r--Userland/Libraries/LibJS/Console.cpp125
1 files changed, 115 insertions, 10 deletions
diff --git a/Userland/Libraries/LibJS/Console.cpp b/Userland/Libraries/LibJS/Console.cpp
index 1ce35acb9f..8eab7ceddb 100644
--- a/Userland/Libraries/LibJS/Console.cpp
+++ b/Userland/Libraries/LibJS/Console.cpp
@@ -1,13 +1,14 @@
/*
* Copyright (c) 2020, Emanuele Torre <torreemanuele6@gmail.com>
* Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
- * Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
+ * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Console.h>
-#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/AbstractOperations.h>
+#include <LibJS/Runtime/StringConstructor.h>
#include <LibJS/Runtime/Temporal/Duration.h>
namespace JS {
@@ -511,24 +512,128 @@ ThrowCompletionOr<Value> ConsoleClient::logger(Console::LogLevel log_level, Mark
return printer(log_level, move(first_as_vector));
}
- // 5. If first does not contain any format specifiers, perform Printer(logLevel, args).
- if (!TRY(first.to_string(vm)).contains('%')) {
- TRY(printer(log_level, args));
- } else {
- // 6. Otherwise, perform Printer(logLevel, Formatter(args)).
+ // 5. Otherwise, perform Printer(logLevel, Formatter(args)).
+ else {
auto formatted = TRY(formatter(args));
TRY(printer(log_level, formatted));
}
- // 7. Return undefined.
+ // 6. Return undefined.
return js_undefined();
}
// 2.2. Formatter(args), https://console.spec.whatwg.org/#formatter
ThrowCompletionOr<MarkedVector<Value>> ConsoleClient::formatter(MarkedVector<Value> const& args)
{
- // TODO: Actually implement formatting
- return args;
+ auto& vm = m_console.vm();
+ auto& realm = *vm.current_realm();
+
+ // 1. If args’s size is 1, return args.
+ if (args.size() == 1)
+ return args;
+
+ // 2. Let target be the first element of args.
+ auto target = (!args.is_empty()) ? TRY(args.first().to_string(vm)) : "";
+
+ // 3. Let current be the second element of args.
+ auto current = (args.size() > 1) ? args[1] : js_undefined();
+
+ // 4. Find the first possible format specifier specifier, from the left to the right in target.
+ auto find_specifier = [](StringView target) -> Optional<StringView> {
+ size_t start_index = 0;
+ while (start_index < target.length()) {
+ auto maybe_index = target.find('%');
+ if (!maybe_index.has_value())
+ return {};
+
+ auto index = maybe_index.value();
+ if (index + 1 >= target.length())
+ return {};
+
+ switch (target[index + 1]) {
+ case 'c':
+ case 'd':
+ case 'f':
+ case 'i':
+ case 'o':
+ case 'O':
+ case 's':
+ return target.substring_view(index, 2);
+ }
+
+ start_index = index + 1;
+ }
+ return {};
+ };
+ auto maybe_specifier = find_specifier(target);
+
+ // 5. If no format specifier was found, return args.
+ if (!maybe_specifier.has_value()) {
+ return args;
+ }
+ // 6. Otherwise:
+ else {
+ auto specifier = maybe_specifier.release_value();
+ Optional<Value> converted;
+
+ // 1. If specifier is %s, let converted be the result of Call(%String%, undefined, « current »).
+ if (specifier == "%s"sv) {
+ converted = TRY(call(vm, realm.intrinsics().string_constructor(), js_undefined(), current));
+ }
+ // 2. If specifier is %d or %i:
+ else if (specifier.is_one_of("%d"sv, "%i"sv)) {
+ // 1. If Type(current) is Symbol, let converted be NaN
+ if (current.is_symbol()) {
+ converted = js_nan();
+ }
+ // 2. Otherwise, let converted be the result of Call(%parseInt%, undefined, « current, 10 »).
+ else {
+ converted = TRY(call(vm, realm.intrinsics().parse_int_function(), js_undefined(), current, Value { 10 }));
+ }
+ }
+ // 3. If specifier is %f:
+ else if (specifier == "%f"sv) {
+ // 1. If Type(current) is Symbol, let converted be NaN
+ if (current.is_symbol()) {
+ converted = js_nan();
+ }
+ // 2. Otherwise, let converted be the result of Call(% parseFloat %, undefined, « current »).
+ else {
+ converted = TRY(call(vm, realm.intrinsics().parse_float_function(), js_undefined(), current));
+ }
+ }
+ // 4. If specifier is %o, optionally let converted be current with optimally useful formatting applied.
+ else if (specifier == "%o"sv) {
+ // TODO: "Optimally-useful formatting"
+ converted = current;
+ }
+ // 5. If specifier is %O, optionally let converted be current with generic JavaScript object formatting applied.
+ else if (specifier == "%O"sv) {
+ // TODO: "generic JavaScript object formatting"
+ converted = current;
+ }
+ // 6. TODO: process %c
+ else if (specifier == "%c"sv) {
+ // NOTE: This has no spec yet. `%c` specifiers treat the argument as CSS styling for the log message.
+ // For now, we'll just consume the specifier and the argument.
+ // FIXME: Actually style the message somehow.
+ converted = js_string(vm, "");
+ }
+
+ // 7. If any of the previous steps set converted, replace specifier in target with converted.
+ if (converted.has_value())
+ target = target.replace(specifier, TRY(converted->to_string(vm)), ReplaceMode::FirstOnly);
+ }
+
+ // 7. Let result be a list containing target together with the elements of args starting from the third onward.
+ MarkedVector<Value> result { vm.heap() };
+ result.ensure_capacity(args.size() - 1);
+ result.empend(js_string(vm, target));
+ for (size_t i = 2; i < args.size(); ++i)
+ result.unchecked_append(args[i]);
+
+ // 8. Return Formatter(result).
+ return formatter(result);
}
}