summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkleines Filmröllchen <filmroellchen@serenityos.org>2022-02-04 17:19:22 +0100
committerAndreas Kling <kling@serenityos.org>2022-02-07 18:39:50 +0100
commit41ef4f11dcd595441e44d03e3bbd5f84f2c4a268 (patch)
tree95872899f72d7434960f9185daa57d7c49476fd3
parent1806b297b6d555162b45337fb33552c12337d76d (diff)
downloadserenity-41ef4f11dcd595441e44d03e3bbd5f84f2c4a268.zip
LibGUI: Move GML parsing and formatting to new AST
This commit introduces a couple of connected changes that are hard to untangle, unfortunately: - Parse GML into the AST instead of JSON - Change the load_from_json API on Widget to load_from_gml_ast - Remove this same API from Core::Object as it isn't used outside of LibGUI and was a workaround for the object registration detection; by verifying the objects we're getting and casting we can remove this constraint. - Format GML by calling the formating APIs on the AST itself; remove GMLFormatter.cpp as it's not needed anymore. After this change, GML formatting already respects comments :^)
-rw-r--r--Userland/Libraries/LibCore/Object.h2
-rw-r--r--Userland/Libraries/LibGUI/CMakeLists.txt1
-rw-r--r--Userland/Libraries/LibGUI/GML/Formatter.cpp101
-rw-r--r--Userland/Libraries/LibGUI/GML/Formatter.h11
-rw-r--r--Userland/Libraries/LibGUI/GML/Parser.cpp117
-rw-r--r--Userland/Libraries/LibGUI/GML/Parser.h3
-rw-r--r--Userland/Libraries/LibGUI/ScrollableContainerWidget.cpp41
-rw-r--r--Userland/Libraries/LibGUI/ScrollableContainerWidget.h2
-rw-r--r--Userland/Libraries/LibGUI/Widget.cpp80
-rw-r--r--Userland/Libraries/LibGUI/Widget.h7
10 files changed, 128 insertions, 237 deletions
diff --git a/Userland/Libraries/LibCore/Object.h b/Userland/Libraries/LibCore/Object.h
index 8d468b54b0..6bcb75b675 100644
--- a/Userland/Libraries/LibCore/Object.h
+++ b/Userland/Libraries/LibCore/Object.h
@@ -181,8 +181,6 @@ public:
void increment_inspector_count(Badge<InspectorServerConnection>);
void decrement_inspector_count(Badge<InspectorServerConnection>);
- virtual bool load_from_json(const JsonObject&, RefPtr<Core::Object> (*)(const String&)) { return false; }
-
protected:
explicit Object(Object* parent = nullptr);
diff --git a/Userland/Libraries/LibGUI/CMakeLists.txt b/Userland/Libraries/LibGUI/CMakeLists.txt
index 5c26a05a85..41dc578768 100644
--- a/Userland/Libraries/LibGUI/CMakeLists.txt
+++ b/Userland/Libraries/LibGUI/CMakeLists.txt
@@ -47,7 +47,6 @@ set(SOURCES
GitCommitSyntaxHighlighter.cpp
GlyphMapWidget.cpp
GML/AutocompleteProvider.cpp
- GML/Formatter.cpp
GML/Lexer.cpp
GML/Parser.cpp
GML/SyntaxHighlighter.cpp
diff --git a/Userland/Libraries/LibGUI/GML/Formatter.cpp b/Userland/Libraries/LibGUI/GML/Formatter.cpp
deleted file mode 100644
index b1fcbc93fe..0000000000
--- a/Userland/Libraries/LibGUI/GML/Formatter.cpp
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
- * Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#include "Formatter.h"
-#include "Parser.h"
-#include <AK/JsonObject.h>
-#include <AK/JsonValue.h>
-#include <AK/StringBuilder.h>
-
-namespace GUI::GML {
-
-static String format_gml_object(const JsonObject& node, size_t indentation = 0, bool is_inline = false)
-{
- StringBuilder builder;
-
- auto indent = [&builder](size_t indentation) {
- for (size_t i = 0; i < indentation; ++i)
- builder.append(" ");
- };
-
- struct Property {
- String key;
- JsonValue value;
- };
- Vector<Property> properties;
- node.for_each_member([&](auto& key, auto& value) {
- if (key != "class" && key != "layout" && key != "children")
- properties.append({ key, value });
- return IterationDecision::Continue;
- });
-
- if (!is_inline)
- indent(indentation);
- builder.append('@');
- builder.append(node.get("class").as_string());
- builder.append(" {\n");
-
- for (auto& property : properties) {
- indent(indentation + 1);
- builder.append(property.key);
- builder.append(": ");
- if (property.value.is_array()) {
- // custom array serialization as AK's doesn't pretty-print
- // objects and arrays (we only care about arrays (for now))
- builder.append("[");
- auto first = true;
- property.value.as_array().for_each([&](auto& value) {
- if (!first)
- builder.append(", ");
- first = false;
- value.serialize(builder);
- });
- builder.append("]");
- } else {
- property.value.serialize(builder);
- }
- builder.append("\n");
- }
-
- if (node.has("layout")) {
- auto layout = node.get("layout").as_object();
- if (!properties.is_empty())
- builder.append("\n");
- indent(indentation + 1);
- builder.append("layout: ");
- builder.append(format_gml_object(move(layout), indentation + 1, true));
- }
-
- if (node.has("children")) {
- auto children = node.get("children").as_array();
- auto first = properties.is_empty() && !node.has("layout");
- children.for_each([&](auto& value) {
- if (!first)
- builder.append("\n");
- first = false;
- builder.append(format_gml_object(value.as_object(), indentation + 1));
- });
- }
-
- indent(indentation);
- builder.append("}\n");
-
- return builder.to_string();
-}
-
-String format_gml(StringView string)
-{
- // FIXME: Preserve comments somehow, they're not contained
- // in the JSON object returned by parse_gml()
- auto ast = parse_gml(string);
- if (ast.is_null())
- return {};
- VERIFY(ast.is_object());
- return format_gml_object(ast.as_object());
-}
-
-}
diff --git a/Userland/Libraries/LibGUI/GML/Formatter.h b/Userland/Libraries/LibGUI/GML/Formatter.h
index 91eec86a43..c5d7cfe809 100644
--- a/Userland/Libraries/LibGUI/GML/Formatter.h
+++ b/Userland/Libraries/LibGUI/GML/Formatter.h
@@ -7,9 +7,18 @@
#pragma once
#include <AK/Forward.h>
+#include <AK/String.h>
+#include <LibGUI/GML/AST.h>
+#include <LibGUI/GML/Parser.h>
namespace GUI::GML {
-String format_gml(StringView);
+inline String format_gml(StringView string)
+{
+ auto ast = parse_gml(string);
+ if (ast.is_error())
+ return {};
+ return ast.value()->to_string();
+}
}
diff --git a/Userland/Libraries/LibGUI/GML/Parser.cpp b/Userland/Libraries/LibGUI/GML/Parser.cpp
index 2e7e71b225..013bb6607c 100644
--- a/Userland/Libraries/LibGUI/GML/Parser.cpp
+++ b/Userland/Libraries/LibGUI/GML/Parser.cpp
@@ -6,18 +6,20 @@
*/
#include "Parser.h"
+#include "AST.h"
#include "Lexer.h"
+#include <AK/Error.h>
#include <AK/GenericLexer.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <AK/Queue.h>
+#include <AK/RefPtr.h>
namespace GUI::GML {
-static Optional<JsonValue> parse_core_object(Queue<Token>& tokens)
+static ErrorOr<NonnullRefPtr<Object>> parse_gml_object(Queue<Token>& tokens)
{
- JsonObject object;
- JsonArray children;
+ auto object = TRY(try_make_ref_counted<Object>());
auto peek = [&] {
if (tokens.is_empty())
@@ -25,23 +27,21 @@ static Optional<JsonValue> parse_core_object(Queue<Token>& tokens)
return tokens.head().m_type;
};
- while (peek() == Token::Type::Comment)
- tokens.dequeue();
-
- if (peek() != Token::Type::ClassMarker) {
- dbgln("Expected class marker");
- return {};
+ while (peek() == Token::Type::Comment) {
+ dbgln("found comment {}", tokens.head().m_view);
+ TRY(object->add_child(TRY(Node::from_token<Comment>(tokens.dequeue()))));
}
+ if (peek() != Token::Type::ClassMarker)
+ return Error::from_string_literal("Expected class marker"sv);
+
tokens.dequeue();
- if (peek() != Token::Type::ClassName) {
- dbgln("Expected class name");
- return {};
- }
+ if (peek() != Token::Type::ClassName)
+ return Error::from_string_literal("Expected class name"sv);
auto class_name = tokens.dequeue();
- object.set("class", JsonValue(class_name.m_view));
+ object->set_name(class_name.m_view);
if (peek() != Token::Type::LeftCurly) {
// Empty object
@@ -57,72 +57,44 @@ static Optional<JsonValue> parse_core_object(Queue<Token>& tokens)
if (peek() == Token::Type::ClassMarker) {
// It's a child object.
- auto value = parse_core_object(tokens);
- if (!value.has_value()) {
- dbgln("Parsing child object failed");
- return {};
- }
- if (!value.value().is_object()) {
- dbgln("Expected child to be Core::Object");
- return {};
- }
- children.append(value.release_value());
+ TRY(object->add_child(TRY(parse_gml_object(tokens))));
} else if (peek() == Token::Type::Identifier) {
// It's a property.
auto property_name = tokens.dequeue();
- if (property_name.m_view.is_empty()) {
- dbgln("Expected non-empty property name");
- return {};
- }
+ if (property_name.m_view.is_empty())
+ return Error::from_string_literal("Expected non-empty property name"sv);
+
+ if (peek() != Token::Type::Colon)
+ return Error::from_string_literal("Expected ':'"sv);
- if (peek() != Token::Type::Colon) {
- dbgln("Expected ':'");
- return {};
- }
tokens.dequeue();
- JsonValue value;
- if (peek() == Token::Type::ClassMarker) {
- auto parsed_value = parse_core_object(tokens);
- if (!parsed_value.has_value())
- return {};
- if (!parsed_value.value().is_object()) {
- dbgln("Expected property to be Core::Object");
- return {};
- }
- value = parsed_value.release_value();
- } else if (peek() == Token::Type::JsonValue) {
- auto value_string = tokens.dequeue();
- auto parsed_value = JsonValue::from_string(value_string.m_view);
- if (parsed_value.is_error()) {
- dbgln("Expected property to be JSON value");
- return {};
- }
- value = parsed_value.release_value();
- }
- object.set(property_name.m_view, move(value));
+ RefPtr<ValueNode> value;
+ if (peek() == Token::Type::ClassMarker)
+ value = TRY(parse_gml_object(tokens));
+ else if (peek() == Token::Type::JsonValue)
+ value = TRY(try_make_ref_counted<JsonValueNode>(TRY(JsonValueNode::from_string(tokens.dequeue().m_view))));
+
+ auto property = TRY(try_make_ref_counted<KeyValuePair>(property_name.m_view, value.release_nonnull()));
+ TRY(object->add_child(property));
+
} else if (peek() == Token::Type::Comment) {
- tokens.dequeue();
+ TRY(object->add_child(TRY(Node::from_token<Comment>(tokens.dequeue()))));
} else {
- dbgln("Expected child, property, comment, or }}");
- return {};
+ return Error::from_string_literal("Expected child, property, comment, or }}"sv);
}
}
- if (peek() != Token::Type::RightCurly) {
- dbgln("Expected }}");
- return {};
- }
- tokens.dequeue();
+ if (peek() != Token::Type::RightCurly)
+ return Error::from_string_literal("Expected }}"sv);
- if (!children.is_empty())
- object.set("children", move(children));
+ tokens.dequeue();
return object;
}
-JsonValue parse_gml(StringView string)
+ErrorOr<NonnullRefPtr<GMLFile>> parse_gml(StringView string)
{
auto lexer = Lexer(string);
@@ -130,12 +102,23 @@ JsonValue parse_gml(StringView string)
for (auto& token : lexer.lex())
tokens.enqueue(token);
- auto root = parse_core_object(tokens);
+ auto file = TRY(try_make_ref_counted<GMLFile>());
+
+ auto peek = [&] {
+ if (tokens.is_empty())
+ return Token::Type::Unknown;
+ return tokens.head().m_type;
+ };
+
+ while (peek() == Token::Type::Comment)
+ TRY(file->add_child(TRY(Node::from_token<Comment>(tokens.dequeue()))));
+
+ TRY(file->add_child(TRY(parse_gml_object(tokens))));
- if (!root.has_value())
- return JsonValue();
+ while (!tokens.is_empty())
+ TRY(file->add_child(TRY(Node::from_token<Comment>(tokens.dequeue()))));
- return root.release_value();
+ return file;
}
}
diff --git a/Userland/Libraries/LibGUI/GML/Parser.h b/Userland/Libraries/LibGUI/GML/Parser.h
index 041fd76914..5642221e52 100644
--- a/Userland/Libraries/LibGUI/GML/Parser.h
+++ b/Userland/Libraries/LibGUI/GML/Parser.h
@@ -8,9 +8,10 @@
#pragma once
#include <AK/Forward.h>
+#include <LibGUI/GML/AST.h>
namespace GUI::GML {
-JsonValue parse_gml(StringView);
+ErrorOr<NonnullRefPtr<GMLFile>> parse_gml(StringView);
}
diff --git a/Userland/Libraries/LibGUI/ScrollableContainerWidget.cpp b/Userland/Libraries/LibGUI/ScrollableContainerWidget.cpp
index 3d608450b5..e8020143d5 100644
--- a/Userland/Libraries/LibGUI/ScrollableContainerWidget.cpp
+++ b/Userland/Libraries/LibGUI/ScrollableContainerWidget.cpp
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
+#include <AK/RefPtr.h>
#include <LibGUI/Layout.h>
#include <LibGUI/ScrollableContainerWidget.h>
@@ -85,48 +86,46 @@ void ScrollableContainerWidget::set_widget(GUI::Widget* widget)
update_widget_position();
}
-bool ScrollableContainerWidget::load_from_json(const JsonObject& json, RefPtr<Core::Object> (*unregistered_child_handler)(const String&))
+bool ScrollableContainerWidget::load_from_gml_ast(NonnullRefPtr<GUI::GML::Node> ast, RefPtr<Core::Object> (*unregistered_child_handler)(const String&))
{
- json.for_each_member([&](auto& key, auto& value) {
+ if (is<GUI::GML::GMLFile>(ast.ptr()))
+ return load_from_gml_ast(static_ptr_cast<GUI::GML::GMLFile>(ast)->main_class(), unregistered_child_handler);
+
+ VERIFY(is<GUI::GML::Object>(ast.ptr()));
+ auto object = static_ptr_cast<GUI::GML::Object>(ast);
+
+ object->for_each_property([&](auto key, auto value) {
set_property(key, value);
});
- auto layout_value = json.get("layout");
- if (!layout_value.is_null()) {
- dbgln("Layout specified in ScrollableContainerWidget, this is not supported.");
- return false;
- }
-
- auto content_widget_value = json.get("content_widget");
- if (!content_widget_value.is_null() && !content_widget_value.is_object()) {
+ auto content_widget_value = object->get_property("content_widget"sv);
+ if (!content_widget_value.is_null() && !is<Object>(content_widget_value.ptr())) {
dbgln("content widget is not an object");
return false;
}
- if (!json.get("children").is_null()) {
+ auto has_children = false;
+ object->for_each_child_object([&](auto) { has_children = true; });
+ if (has_children) {
dbgln("children specified for ScrollableContainerWidget, but only 1 widget as content_widget is supported");
return false;
}
- if (content_widget_value.is_object()) {
- auto& content_widget = content_widget_value.as_object();
- auto class_name = content_widget.get("class");
- if (!class_name.is_string()) {
- dbgln("Invalid content widget class name");
- return false;
- }
+ if (!content_widget_value.is_null() && is<Object>(content_widget_value.ptr())) {
+ auto content_widget = static_ptr_cast<GUI::GML::Object>(content_widget_value);
+ auto class_name = content_widget->name();
RefPtr<Core::Object> child;
- if (auto* registration = Core::ObjectClassRegistration::find(class_name.as_string())) {
+ if (auto* registration = Core::ObjectClassRegistration::find(class_name)) {
child = registration->construct();
} else {
- child = unregistered_child_handler(class_name.as_string());
+ child = unregistered_child_handler(class_name);
}
if (!child)
return false;
auto widget_ptr = verify_cast<GUI::Widget>(child.ptr());
set_widget(widget_ptr);
- child->load_from_json(content_widget, unregistered_child_handler);
+ static_ptr_cast<Widget>(child)->load_from_gml_ast(content_widget.release_nonnull(), unregistered_child_handler);
return true;
}
diff --git a/Userland/Libraries/LibGUI/ScrollableContainerWidget.h b/Userland/Libraries/LibGUI/ScrollableContainerWidget.h
index 755a0966d6..5fc3f8b0a5 100644
--- a/Userland/Libraries/LibGUI/ScrollableContainerWidget.h
+++ b/Userland/Libraries/LibGUI/ScrollableContainerWidget.h
@@ -27,7 +27,7 @@ protected:
private:
void update_widget_size();
void update_widget_position();
- virtual bool load_from_json(const JsonObject&, RefPtr<Core::Object> (*unregistered_child_handler)(const String&)) override;
+ virtual bool load_from_gml_ast(NonnullRefPtr<GUI::GML::Node> ast, RefPtr<Core::Object> (*unregistered_child_handler)(const String&)) override;
ScrollableContainerWidget();
diff --git a/Userland/Libraries/LibGUI/Widget.cpp b/Userland/Libraries/LibGUI/Widget.cpp
index f24ad8f835..6429b96fc5 100644
--- a/Userland/Libraries/LibGUI/Widget.cpp
+++ b/Userland/Libraries/LibGUI/Widget.cpp
@@ -6,11 +6,15 @@
#include <AK/Assertions.h>
#include <AK/Debug.h>
+#include <AK/IterationDecision.h>
#include <AK/JsonObject.h>
+#include <AK/NonnullRefPtr.h>
+#include <AK/RefPtr.h>
#include <LibGUI/Action.h>
#include <LibGUI/Application.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Event.h>
+#include <LibGUI/GML/AST.h>
#include <LibGUI/GML/Parser.h>
#include <LibGUI/Layout.h>
#include <LibGUI/Menu.h>
@@ -1067,32 +1071,36 @@ bool Widget::load_from_gml(StringView gml_string)
bool Widget::load_from_gml(StringView gml_string, RefPtr<Core::Object> (*unregistered_child_handler)(const String&))
{
auto value = GML::parse_gml(gml_string);
- if (!value.is_object())
+ if (value.is_error()) {
+ // FIXME: We don't report the error, so at least print it.
+ dbgln("Error while parsing GML: {}", value.error());
return false;
- return load_from_json(value.as_object(), unregistered_child_handler);
+ }
+ return load_from_gml_ast(value.release_value(), unregistered_child_handler);
}
-bool Widget::load_from_json(const JsonObject& json, RefPtr<Core::Object> (*unregistered_child_handler)(const String&))
+bool Widget::load_from_gml_ast(NonnullRefPtr<GUI::GML::Node> ast, RefPtr<Core::Object> (*unregistered_child_handler)(const String&))
{
- json.for_each_member([&](auto& key, auto& value) {
+ if (is<GUI::GML::GMLFile>(ast.ptr()))
+ return load_from_gml_ast(static_ptr_cast<GUI::GML::GMLFile>(ast)->main_class(), unregistered_child_handler);
+
+ VERIFY(is<GUI::GML::Object>(ast.ptr()));
+ auto object = static_ptr_cast<GUI::GML::Object>(ast);
+
+ object->for_each_property([&](auto key, auto value) {
set_property(key, value);
});
- auto layout_value = json.get("layout");
- if (!layout_value.is_null() && !layout_value.is_object()) {
- dbgln("layout is not an object");
- return false;
- }
- if (layout_value.is_object()) {
- auto& layout = layout_value.as_object();
- auto class_name = layout.get("class");
+ auto layout = object->layout_object();
+ if (!layout.is_null()) {
+ auto class_name = layout->name();
if (class_name.is_null()) {
dbgln("Invalid layout class name");
return false;
}
auto& layout_class = *Core::ObjectClassRegistration::find("GUI::Layout");
- if (auto* registration = Core::ObjectClassRegistration::find(class_name.as_string())) {
+ if (auto* registration = Core::ObjectClassRegistration::find(class_name)) {
auto layout = registration->construct();
if (!layout || !registration->is_derived_from(layout_class)) {
dbgln("Invalid layout class: '{}'", class_name.to_string());
@@ -1104,40 +1112,32 @@ bool Widget::load_from_json(const JsonObject& json, RefPtr<Core::Object> (*unreg
return false;
}
- layout.for_each_member([&](auto& key, auto& value) {
+ layout->for_each_property([&](auto key, auto value) {
this->layout()->set_property(key, value);
});
}
auto& widget_class = *Core::ObjectClassRegistration::find("GUI::Widget");
- auto children = json.get("children");
- if (children.is_array()) {
- for (auto& child_json_value : children.as_array().values()) {
- if (!child_json_value.is_object())
- return false;
- auto& child_json = child_json_value.as_object();
- auto class_name = child_json.get("class");
- if (!class_name.is_string()) {
- dbgln("No class name in entry");
- return false;
+ object->for_each_child_object_interruptible([&](auto child_data) {
+ auto class_name = child_data->name();
+
+ RefPtr<Core::Object> child;
+ if (auto* registration = Core::ObjectClassRegistration::find(class_name)) {
+ child = registration->construct();
+ if (!child || !registration->is_derived_from(widget_class)) {
+ dbgln("Invalid widget class: '{}'", class_name);
+ return IterationDecision::Break;
}
-
- RefPtr<Core::Object> child;
- if (auto* registration = Core::ObjectClassRegistration::find(class_name.as_string())) {
- child = registration->construct();
- if (!child || !registration->is_derived_from(widget_class)) {
- dbgln("Invalid widget class: '{}'", class_name.to_string());
- return false;
- }
- } else {
- child = unregistered_child_handler(class_name.as_string());
- }
- if (!child)
- return false;
- add_child(*child);
- child->load_from_json(child_json, unregistered_child_handler);
+ } else {
+ child = unregistered_child_handler(class_name);
}
- }
+ if (!child)
+ return IterationDecision::Break;
+ add_child(*child);
+ // This is possible as we ensure that Widget is a base class above.
+ static_ptr_cast<Widget>(child)->load_from_gml_ast(child_data, unregistered_child_handler);
+ return IterationDecision::Continue;
+ });
return true;
}
diff --git a/Userland/Libraries/LibGUI/Widget.h b/Userland/Libraries/LibGUI/Widget.h
index b8c7979d2d..c6e1391269 100644
--- a/Userland/Libraries/LibGUI/Widget.h
+++ b/Userland/Libraries/LibGUI/Widget.h
@@ -8,12 +8,14 @@
#include <AK/EnumBits.h>
#include <AK/JsonObject.h>
+#include <AK/NonnullRefPtr.h>
#include <AK/String.h>
#include <AK/Variant.h>
#include <LibCore/Object.h>
#include <LibGUI/Event.h>
#include <LibGUI/FocusPolicy.h>
#include <LibGUI/Forward.h>
+#include <LibGUI/GML/AST.h>
#include <LibGUI/Margins.h>
#include <LibGfx/Color.h>
#include <LibGfx/Forward.h>
@@ -299,6 +301,9 @@ public:
bool has_pending_drop() const;
+ // In order for others to be able to call this, it needs to be public.
+ virtual bool load_from_gml_ast(NonnullRefPtr<GUI::GML::Node> ast, RefPtr<Core::Object> (*unregistered_child_handler)(const String&));
+
protected:
Widget();
@@ -354,8 +359,6 @@ private:
void focus_previous_widget(FocusSource, bool siblings_only);
void focus_next_widget(FocusSource, bool siblings_only);
- virtual bool load_from_json(const JsonObject&, RefPtr<Core::Object> (*unregistered_child_handler)(const String&)) override;
-
// HACK: These are used as property getters for the fixed_* size property aliases.
int dummy_fixed_width() { return 0; }
int dummy_fixed_height() { return 0; }