diff options
-rw-r--r-- | Applications/Browser/ConsoleWidget.cpp | 156 | ||||
-rw-r--r-- | Applications/Browser/ConsoleWidget.h | 7 | ||||
-rw-r--r-- | Libraries/LibJS/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Libraries/LibJS/Forward.h | 1 | ||||
-rw-r--r-- | Libraries/LibJS/MarkupGenerator.cpp | 323 | ||||
-rw-r--r-- | Libraries/LibJS/MarkupGenerator.h | 64 |
6 files changed, 392 insertions, 160 deletions
diff --git a/Applications/Browser/ConsoleWidget.cpp b/Applications/Browser/ConsoleWidget.cpp index 9743e87bc9..a5ae9e61aa 100644 --- a/Applications/Browser/ConsoleWidget.cpp +++ b/Applications/Browser/ConsoleWidget.cpp @@ -30,6 +30,7 @@ #include <LibGUI/JSSyntaxHighlighter.h> #include <LibGUI/TextBox.h> #include <LibJS/Interpreter.h> +#include <LibJS/MarkupGenerator.h> #include <LibJS/Parser.h> #include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Date.h> @@ -93,16 +94,14 @@ ConsoleWidget::ConsoleWidget() if (m_interpreter->exception()) { StringBuilder output_html; output_html.append("Uncaught exception: "); - print_value(m_interpreter->exception()->value(), output_html); + output_html.append(JS::MarkupGenerator::html_from_value(m_interpreter->exception()->value())); print_html(output_html.string_view()); m_interpreter->clear_exception(); return; } - StringBuilder output_html; - print_value(m_interpreter->last_value(), output_html); - print_html(output_html.string_view()); + print_html(JS::MarkupGenerator::html_from_value(m_interpreter->last_value())); }; } @@ -174,155 +173,6 @@ void ConsoleWidget::print_source_line(const StringView& source) print_html(html.string_view()); } -void ConsoleWidget::print_value(JS::Value value, StringBuilder& output_html, HashTable<JS::Object*> seen_objects) -{ - // FIXME: Support output highlighting - - if (value.is_empty()) { - output_html.append("<span class=\"empty-object\">"); - output_html.append("<empty>"); - output_html.append("</span>"); - return; - } - - if (value.is_object()) { - if (seen_objects.contains(&value.as_object())) { - // FIXME: Maybe we should only do this for circular references, - // not for all reoccurring objects. - output_html.append("<span class=\"already-printed\">"); - output_html.appendf("<already printed Object %p>", &value.as_object()); - output_html.append("</span>"); - return; - } - seen_objects.set(&value.as_object()); - } - - if (value.is_array()) - return print_array(static_cast<const JS::Array&>(value.as_object()), output_html, seen_objects); - - if (value.is_object()) { - auto& object = value.as_object(); - if (object.is_function()) - return print_function(object, output_html, seen_objects); - if (object.is_date()) - return print_date(object, output_html, seen_objects); - if (object.is_error()) - return print_error(object, output_html, seen_objects); - return print_object(object, output_html, seen_objects); - } - - if (value.is_string()) - output_html.append("<span class=\"js-string\">"); - else if (value.is_number()) - output_html.append("<span class=\"js-number\">"); - else if (value.is_boolean()) - output_html.append("<span class=\"js-boolean\">"); - else if (value.is_null()) - output_html.append("<span class=\"js-null\">"); - else if (value.is_undefined()) - output_html.append("<span class=\"js-undefined\">"); - - if (value.is_string()) - output_html.append('"'); - output_html.append(value.to_string_without_side_effects()); - if (value.is_string()) - output_html.append('"'); - - output_html.append("</span>"); -} - -void ConsoleWidget::print_array(const JS::Array& array, StringBuilder& html_output, HashTable<JS::Object*>& seen_objects) -{ - html_output.append("<span class=\"js-array-open\">"); - html_output.append("[ "); - html_output.append("</span>"); - for (size_t i = 0; i < array.elements().size(); ++i) { - print_value(array.elements()[i], html_output, seen_objects); - if (i != array.elements().size() - 1) { - html_output.append("<span class=\"js-array-element-separator\">"); - html_output.append(", "); - html_output.append("</span>"); - } - } - html_output.append("<span class=\"js-array-close\">"); - html_output.append(" ]"); - html_output.append("</span>"); -} - -void ConsoleWidget::print_object(const JS::Object& object, StringBuilder& html_output, HashTable<JS::Object*>& seen_objects) -{ - html_output.append("<span class=\"js-object-open\">"); - html_output.append("{ "); - html_output.append("</span>"); - - for (size_t i = 0; i < object.elements().size(); ++i) { - if (object.elements()[i].is_empty()) - continue; - html_output.append("<span class=\"js-object-element-index\">"); - html_output.appendf("%zu", i); - html_output.append("</span>"); - html_output.append(": "); - print_value(object.elements()[i], html_output, seen_objects); - if (i != object.elements().size() - 1) { - html_output.append("<span class=\"js-object-element-separator\">"); - html_output.append(", "); - html_output.append("</span>"); - } - } - - if (!object.elements().is_empty() && object.shape().property_count()) { - html_output.append("<span class=\"js-object-element-separator\">"); - html_output.append(", "); - html_output.append("</span>"); - } - - size_t index = 0; - for (auto& it : object.shape().property_table_ordered()) { - html_output.append("<span class=\"js-object-element-key\">"); - html_output.appendf("\"%s\"", it.key.characters()); - html_output.append("</span>"); - html_output.append(": "); - print_value(object.get_direct(it.value.offset), html_output, seen_objects); - if (index != object.shape().property_count() - 1) { - html_output.append("<span class=\"js-object-element-separator\">"); - html_output.append(", "); - html_output.append("</span>"); - } - ++index; - } - - html_output.append("<span class=\"js-object-close\">"); - html_output.append(" }"); - html_output.append("</span>"); -} - -void ConsoleWidget::print_function(const JS::Object& function, StringBuilder& html_output, HashTable<JS::Object*>&) -{ - html_output.append("<span class=\"js-function\">"); - html_output.appendf("[%s]", function.class_name()); - html_output.append("</span>"); -} - -void ConsoleWidget::print_date(const JS::Object& date, StringBuilder& html_output, HashTable<JS::Object*>&) -{ - html_output.append("<span class=\"js-date\">"); - html_output.appendf("Date %s", static_cast<const JS::Date&>(date).string().characters()); - html_output.append("</span>"); -} - -void ConsoleWidget::print_error(const JS::Object& object, StringBuilder& html_output, HashTable<JS::Object*>&) -{ - auto& error = static_cast<const JS::Error&>(object); - html_output.append("<span class=\"js-error-name\">"); - html_output.appendf("[%s]", error.name().characters()); - html_output.append("</span>"); - if (!error.message().is_empty()) { - html_output.append("<span class=\"js-error-message\">"); - html_output.appendf(": %s", error.message().characters()); - html_output.append("</span>"); - } -} - void ConsoleWidget::print_html(const StringView& line) { auto paragraph = create_element(m_console_output_container->document(), "p"); diff --git a/Applications/Browser/ConsoleWidget.h b/Applications/Browser/ConsoleWidget.h index 5a0d0d7412..50516ed5e6 100644 --- a/Applications/Browser/ConsoleWidget.h +++ b/Applications/Browser/ConsoleWidget.h @@ -47,13 +47,6 @@ private: String create_document_style(); - void print_value(JS::Value, StringBuilder& output_html, HashTable<JS::Object*> seen_objects = {}); - void print_array(const JS::Array&, StringBuilder& output_html, HashTable<JS::Object*>&); - void print_object(const JS::Object&, StringBuilder& output_html, HashTable<JS::Object*>&); - void print_function(const JS::Object&, StringBuilder& output_html, HashTable<JS::Object*>&); - void print_date(const JS::Object&, StringBuilder& output_html, HashTable<JS::Object*>&); - void print_error(const JS::Object&, StringBuilder& output_html, HashTable<JS::Object*>&); - RefPtr<GUI::TextBox> m_console_input; RefPtr<Web::HtmlView> m_console_output_view; RefPtr<Web::Element> m_console_output_container; diff --git a/Libraries/LibJS/CMakeLists.txt b/Libraries/LibJS/CMakeLists.txt index d9928f9188..f47cf3dbb8 100644 --- a/Libraries/LibJS/CMakeLists.txt +++ b/Libraries/LibJS/CMakeLists.txt @@ -6,6 +6,7 @@ set(SOURCES Heap/Heap.cpp Interpreter.cpp Lexer.cpp + MarkupGenerator.cpp Parser.cpp Runtime/ArrayConstructor.cpp Runtime/Array.cpp diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index 32b4887193..0776632dc2 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -73,6 +73,7 @@ class ScopeNode; class Shape; class Statement; class Symbol; +class Token; class Uint8ClampedArray; class Value; enum class DeclarationKind; diff --git a/Libraries/LibJS/MarkupGenerator.cpp b/Libraries/LibJS/MarkupGenerator.cpp new file mode 100644 index 0000000000..7724d7d1a0 --- /dev/null +++ b/Libraries/LibJS/MarkupGenerator.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <AK/HashTable.h> +#include <AK/StringBuilder.h> +#include <LibJS/Lexer.h> +#include <LibJS/MarkupGenerator.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/Date.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/Object.h> + +namespace JS { + +String MarkupGenerator::html_from_source(String source) +{ + auto lexer = Lexer(source); + StringBuilder builder; + size_t source_cursor = 0; + + for (auto token = lexer.next(); token.type() != TokenType::Eof; token = lexer.next()) { + auto length = token.value().length(); + auto start = token.line_column(); + // FIXME: Why do we need to do this magic math? This math isn't even accurate enough, code like "let x = 10" renders incorrectly. + start = start < 2 ? 0 : start - 2; + + if (start > source_cursor) { + builder.append(source.substring_view(source_cursor, start - source_cursor)); + } + + builder.append(wrap_string_in_style(token.value(), style_type_for_token(token))); + source_cursor = length; + } + + if (source_cursor < source.length()) + builder.append(source.substring_view(source_cursor, source.length() - source_cursor)); + + return builder.to_string(); +} + +String MarkupGenerator::html_from_value(Value value) +{ + StringBuilder output_html; + value_to_html(value, output_html); + return output_html.to_string(); +} + +void MarkupGenerator::value_to_html(Value value, StringBuilder& output_html, HashTable<Object*> seen_objects) +{ + if (value.is_empty()) { + output_html.append("<empty>"); + return; + } + + if (value.is_object()) { + if (seen_objects.contains(&value.as_object())) { + // FIXME: Maybe we should only do this for circular references, + // not for all reoccurring objects. + output_html.appendf("<already printed Object %p>", &value.as_object()); + return; + } + seen_objects.set(&value.as_object()); + } + + if (value.is_array()) + return array_to_html(static_cast<const Array&>(value.as_object()), output_html, seen_objects); + + if (value.is_object()) { + auto& object = value.as_object(); + if (object.is_function()) + return function_to_html(object, output_html, seen_objects); + if (object.is_date()) + return date_to_html(object, output_html, seen_objects); + if (object.is_error()) + return error_to_html(object, output_html, seen_objects); + return object_to_html(object, output_html, seen_objects); + } + + if (value.is_string()) + output_html.append(open_style_type(StyleType::String)); + else if (value.is_number()) + output_html.append(open_style_type(StyleType::Number)); + else if (value.is_boolean() || value.is_null() || value.is_undefined()) + output_html.append(open_style_type(StyleType::KeywordBold)); + + if (value.is_string()) + output_html.append('"'); + output_html.append(value.to_string_without_side_effects()); + if (value.is_string()) + output_html.append('"'); + + output_html.append("</span>"); +} + +void MarkupGenerator::array_to_html(const Array& array, StringBuilder& html_output, HashTable<Object*>& seen_objects) +{ + html_output.append(wrap_string_in_style("[ ", StyleType::Punctuation)); + for (size_t i = 0; i < array.elements().size(); ++i) { + value_to_html(array.elements()[i], html_output, seen_objects); + if (i != array.elements().size() - 1) + html_output.append(wrap_string_in_style(", ", StyleType::Punctuation)); + } + html_output.append(wrap_string_in_style(" ]", StyleType::Punctuation)); +} + +void MarkupGenerator::object_to_html(const Object& object, StringBuilder& html_output, HashTable<Object*>& seen_objects) +{ + html_output.append(wrap_string_in_style("{ ", StyleType::Punctuation)); + + for (size_t i = 0; i < object.elements().size(); ++i) { + if (object.elements()[i].is_empty()) + continue; + html_output.append(wrap_string_in_style(String::format("%zu", i), StyleType::Number)); + html_output.append(wrap_string_in_style(": ", StyleType::Punctuation)); + value_to_html(object.elements()[i], html_output, seen_objects); + if (i != object.elements().size() - 1) + html_output.append(wrap_string_in_style(", ", StyleType::Punctuation)); + } + + if (!object.elements().is_empty() && object.shape().property_count()) + html_output.append(wrap_string_in_style(", ", StyleType::Punctuation)); + + size_t index = 0; + for (auto& it : object.shape().property_table_ordered()) { + html_output.append(wrap_string_in_style(String::format("\"%s\"", it.key.characters()), StyleType::String)); + html_output.append(wrap_string_in_style(": ", StyleType::Punctuation)); + value_to_html(object.get_direct(it.value.offset), html_output, seen_objects); + if (index != object.shape().property_count() - 1) + html_output.append(wrap_string_in_style(", ", StyleType::Punctuation)); + ++index; + } + + html_output.append(wrap_string_in_style(" }", StyleType::Punctuation)); +} + +void MarkupGenerator::function_to_html(const Object& function, StringBuilder& html_output, HashTable<Object*>&) +{ + html_output.appendf("[%s]", function.class_name()); +} + +void MarkupGenerator::date_to_html(const Object& date, StringBuilder& html_output, HashTable<Object*>&) +{ + html_output.appendf("Date %s", static_cast<const JS::Date&>(date).string().characters()); +} + +void MarkupGenerator::error_to_html(const Object& object, StringBuilder& html_output, HashTable<Object*>&) +{ + auto& error = static_cast<const Error&>(object); + html_output.append(wrap_string_in_style(String::format("[%s]", error.name().characters()), StyleType::Invalid)); + if (!error.message().is_empty()) { + html_output.appendf(": %s", error.message().characters()); + } +} + +String MarkupGenerator::style_from_style_type(StyleType type) +{ + switch (type) { + case StyleType::Invalid: + return "color: red;"; + case StyleType::String: + return "color: -libweb-palette-syntax-string;"; + case StyleType::Number: + return "color: -libweb-palette-syntax-number;"; + case StyleType::KeywordBold: + return "color: -libweb-palette-syntax-keyword; font-weight: bold;"; + case StyleType::Punctuation: + return "color: -libweb-palette-syntax-punctuation;"; + case StyleType::Operator: + return "color: -libweb-palette-syntax-operator;"; + case StyleType::Keyword: + return "color: -libweb-palette-syntax-keyword;"; + case StyleType::ControlKeyword: + return "color: -libweb-palette-syntax-control-keyword;"; + case StyleType::Identifier: + return "color: -libweb-palette-syntax-identifier;"; + default: + ASSERT_NOT_REACHED(); + } +} + +MarkupGenerator::StyleType MarkupGenerator::style_type_for_token(Token token) +{ + switch (token.type()) { + case TokenType::Invalid: + case TokenType::Eof: + return StyleType::Invalid; + case TokenType::NumericLiteral: + return StyleType::Number; + case TokenType::StringLiteral: + case TokenType::TemplateLiteralStart: + case TokenType::TemplateLiteralEnd: + case TokenType::TemplateLiteralString: + case TokenType::RegexLiteral: + case TokenType::UnterminatedStringLiteral: + return StyleType::String; + case TokenType::BracketClose: + case TokenType::BracketOpen: + case TokenType::Comma: + case TokenType::CurlyClose: + case TokenType::CurlyOpen: + case TokenType::ParenClose: + case TokenType::ParenOpen: + case TokenType::Semicolon: + case TokenType::Period: + return StyleType::Punctuation; + case TokenType::Ampersand: + case TokenType::AmpersandEquals: + case TokenType::Asterisk: + case TokenType::DoubleAsteriskEquals: + case TokenType::AsteriskEquals: + case TokenType::Caret: + case TokenType::CaretEquals: + case TokenType::DoubleAmpersand: + case TokenType::DoubleAsterisk: + case TokenType::DoublePipe: + case TokenType::DoubleQuestionMark: + case TokenType::Equals: + case TokenType::EqualsEquals: + case TokenType::EqualsEqualsEquals: + case TokenType::ExclamationMark: + case TokenType::ExclamationMarkEquals: + case TokenType::ExclamationMarkEqualsEquals: + case TokenType::GreaterThan: + case TokenType::GreaterThanEquals: + case TokenType::LessThan: + case TokenType::LessThanEquals: + case TokenType::Minus: + case TokenType::MinusEquals: + case TokenType::MinusMinus: + case TokenType::Percent: + case TokenType::PercentEquals: + case TokenType::Pipe: + case TokenType::PipeEquals: + case TokenType::Plus: + case TokenType::PlusEquals: + case TokenType::PlusPlus: + case TokenType::QuestionMark: + case TokenType::QuestionMarkPeriod: + case TokenType::ShiftLeft: + case TokenType::ShiftLeftEquals: + case TokenType::ShiftRight: + case TokenType::ShiftRightEquals: + case TokenType::Slash: + case TokenType::SlashEquals: + case TokenType::Tilde: + case TokenType::UnsignedShiftRight: + case TokenType::UnsignedShiftRightEquals: + return StyleType::Operator; + case TokenType::BoolLiteral: + case TokenType::NullLiteral: + return StyleType::KeywordBold; + case TokenType::Class: + case TokenType::Const: + case TokenType::Debugger: + case TokenType::Delete: + case TokenType::Function: + case TokenType::In: + case TokenType::Instanceof: + case TokenType::Interface: + case TokenType::Let: + case TokenType::New: + case TokenType::TemplateLiteralExprStart: + case TokenType::TemplateLiteralExprEnd: + case TokenType::Throw: + case TokenType::Typeof: + case TokenType::Var: + case TokenType::Void: + return StyleType::Keyword; + case TokenType::Await: + case TokenType::Case: + case TokenType::Catch: + case TokenType::Do: + case TokenType::Else: + case TokenType::Finally: + case TokenType::For: + case TokenType::If: + case TokenType::Return: + case TokenType::Switch: + case TokenType::Try: + case TokenType::While: + case TokenType::Yield: + return StyleType::ControlKeyword; + case TokenType::Identifier: + return StyleType::Identifier; + default: + ASSERT_NOT_REACHED(); + } +} + +String MarkupGenerator::open_style_type(StyleType type) +{ + return String::format("<span style=\"%s\">", style_from_style_type(type).characters()); +} + +String MarkupGenerator::wrap_string_in_style(String source, StyleType type) +{ + return String::format("<span style=\"%s\">%s</span>", style_from_style_type(type).characters(), source.characters()); +} + +}
\ No newline at end of file diff --git a/Libraries/LibJS/MarkupGenerator.h b/Libraries/LibJS/MarkupGenerator.h new file mode 100644 index 0000000000..94909d50ea --- /dev/null +++ b/Libraries/LibJS/MarkupGenerator.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <AK/HashTable.h> +#include <AK/String.h> +#include <LibJS/Forward.h> + +namespace JS { + +class MarkupGenerator { +public: + static String html_from_source(String); + static String html_from_value(Value); + +private: + enum class StyleType { + Invalid, + String, + Number, + KeywordBold, + Punctuation, + Operator, + Keyword, + ControlKeyword, + Identifier + }; + + static void value_to_html(Value, StringBuilder& output_html, HashTable<Object*> seen_objects = {}); + static void array_to_html(const Array&, StringBuilder& output_html, HashTable<Object*>&); + static void object_to_html(const Object&, StringBuilder& output_html, HashTable<Object*>&); + static void function_to_html(const Object&, StringBuilder& output_html, HashTable<Object*>&); + static void date_to_html(const Object&, StringBuilder& output_html, HashTable<Object*>&); + static void error_to_html(const Object&, StringBuilder& output_html, HashTable<Object*>&); + + static String style_from_style_type(StyleType); + static StyleType style_type_for_token(Token); + static String open_style_type(StyleType type); + static String wrap_string_in_style(String source, StyleType type); +}; + +}
\ No newline at end of file |