123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- /*
- * Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>.
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #pragma once
- #include <AK/Concepts.h>
- #include <AK/Error.h>
- #include <AK/Forward.h>
- #include <AK/HashMap.h>
- #include <AK/IterationDecision.h>
- #include <AK/JsonArray.h>
- #include <AK/JsonValue.h>
- #include <AK/NonnullRefPtr.h>
- #include <AK/NonnullRefPtrVector.h>
- #include <AK/RefCounted.h>
- #include <AK/RefPtr.h>
- #include <AK/String.h>
- #include <AK/StringBuilder.h>
- #include <AK/TypeCasts.h>
- #include <LibGUI/GML/Lexer.h>
- namespace GUI::GML {
- class Comment;
- class JsonValueNode;
- // Base of the GML Abstract Syntax Tree (AST).
- class Node : public RefCounted<Node> {
- public:
- virtual ~Node() = default;
- template<typename NodeT>
- requires(IsBaseOf<Node, NodeT>) static ErrorOr<NonnullRefPtr<NodeT>> from_token(Token token)
- {
- return try_make_ref_counted<NodeT>(token.m_view);
- }
- String to_string() const
- {
- StringBuilder builder;
- format(builder, 0, false);
- return builder.to_string();
- }
- // Format this AST node with the builder at the given indentation level.
- // is_inline controls whether we are starting on a new line.
- virtual void format(StringBuilder& builder, size_t indentation, bool is_inline) const = 0;
- // FIXME: We can't change the kind of indentation right now.
- static void indent(StringBuilder& builder, size_t indentation)
- {
- for (size_t i = 0; i < indentation; ++i)
- builder.append(" ");
- }
- };
- // AST nodes that actually hold data and can be in a KeyValuePair.
- class ValueNode
- : public Node {
- public:
- virtual ~ValueNode() = default;
- };
- // Single line comments with //.
- class Comment : public Node {
- public:
- Comment(String text)
- : m_text(move(text))
- {
- }
- virtual void format(StringBuilder& builder, size_t indentation, bool is_inline) const override
- {
- if (is_inline) {
- builder.append(m_text);
- } else {
- indent(builder, indentation);
- builder.append(m_text);
- }
- builder.append("\n");
- }
- virtual ~Comment() override = default;
- private:
- String m_text {};
- };
- // Any JSON-like key: value pair.
- class KeyValuePair : public Node {
- public:
- KeyValuePair(String key, NonnullRefPtr<ValueNode> value)
- : m_key(move(key))
- , m_value(move(value))
- {
- }
- virtual ~KeyValuePair() override = default;
- virtual void format(StringBuilder& builder, size_t indentation, bool is_inline) const override
- {
- if (!is_inline)
- indent(builder, indentation);
- builder.appendff("{}: ", m_key);
- m_value->format(builder, indentation, true);
- if (!is_inline)
- builder.append("\n");
- }
- String key() const { return m_key; }
- NonnullRefPtr<ValueNode> value() const { return m_value; }
- private:
- String m_key;
- NonnullRefPtr<ValueNode> m_value;
- };
- // Just a mixin so that we can use JSON values in the AST
- // FIXME: Use a specialized value type for all the possible GML property values. Right now that's all possible JSON values (?)
- class JsonValueNode : public ValueNode
- , public JsonValue {
- public:
- JsonValueNode(JsonValue const& value)
- : JsonValue(value)
- {
- }
- virtual void format(StringBuilder& builder, size_t indentation, bool is_inline) const override
- {
- if (!is_inline)
- indent(builder, indentation);
- if (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;
- as_array().for_each([&](auto& value) {
- if (!first)
- builder.append(", ");
- first = false;
- value.serialize(builder);
- });
- builder.append("]");
- } else {
- serialize(builder);
- }
- if (!is_inline)
- builder.append("\n");
- }
- };
- // GML class declaration, starting with '@'
- class Object : public ValueNode {
- public:
- Object() = default;
- Object(String name, NonnullRefPtrVector<Node> properties, NonnullRefPtrVector<Node> sub_objects)
- : m_properties(move(properties))
- , m_sub_objects(move(sub_objects))
- , m_name(move(name))
- {
- }
- virtual ~Object() override = default;
- StringView name() const { return m_name; }
- void set_name(String name) { m_name = move(name); }
- ErrorOr<void> add_sub_object_child(NonnullRefPtr<Node> child)
- {
- VERIFY(is<Object>(child.ptr()) || is<Comment>(child.ptr()));
- return m_sub_objects.try_append(move(child));
- }
- ErrorOr<void> add_property_child(NonnullRefPtr<Node> child)
- {
- VERIFY(is<KeyValuePair>(child.ptr()) || is<Comment>(child.ptr()));
- return m_properties.try_append(move(child));
- }
- // Does not return key-value pair `layout: ...`!
- template<typename Callback>
- void for_each_property(Callback callback)
- {
- for (auto const& child : m_properties) {
- if (is<KeyValuePair>(child)) {
- auto const& property = static_cast<KeyValuePair const&>(child);
- if (property.key() != "layout" && is<JsonValueNode>(property.value().ptr()))
- callback(property.key(), static_ptr_cast<JsonValueNode>(property.value()));
- }
- }
- }
- template<typename Callback>
- void for_each_child_object(Callback callback)
- {
- for (NonnullRefPtr<Node> child : m_sub_objects) {
- // doesn't capture layout as intended, as that's behind a kv-pair
- if (is<Object>(child.ptr())) {
- auto object = static_ptr_cast<Object>(child);
- callback(object);
- }
- }
- }
- // Uses IterationDecision to allow the callback to interrupt the iteration, like a for-loop break.
- template<IteratorFunction<NonnullRefPtr<Object>> Callback>
- void for_each_child_object_interruptible(Callback callback)
- {
- for (NonnullRefPtr<Node> child : m_sub_objects) {
- // doesn't capture layout as intended, as that's behind a kv-pair
- if (is<Object>(child.ptr())) {
- auto object = static_ptr_cast<Object>(child);
- if (callback(object) == IterationDecision::Break)
- return;
- }
- }
- }
- RefPtr<Object> layout_object() const
- {
- for (NonnullRefPtr<Node> child : m_properties) {
- if (is<KeyValuePair>(child.ptr())) {
- auto property = static_ptr_cast<KeyValuePair>(child);
- if (property->key() == "layout") {
- VERIFY(is<Object>(property->value().ptr()));
- return static_ptr_cast<Object>(property->value());
- }
- }
- }
- return nullptr;
- }
- RefPtr<ValueNode> get_property(StringView property_name)
- {
- for (NonnullRefPtr<Node> child : m_properties) {
- if (is<KeyValuePair>(child.ptr())) {
- auto property = static_ptr_cast<KeyValuePair>(child);
- if (property->key() == property_name)
- return property->value();
- }
- }
- return nullptr;
- }
- virtual void format(StringBuilder& builder, size_t indentation, bool is_inline) const override
- {
- if (!is_inline)
- indent(builder, indentation);
- builder.append('@');
- builder.append(m_name);
- builder.append(" {");
- if (!m_properties.is_empty() || !m_sub_objects.is_empty()) {
- builder.append('\n');
- for (auto const& property : m_properties)
- property.format(builder, indentation + 1, false);
- if (!m_properties.is_empty() && !m_sub_objects.is_empty())
- builder.append('\n');
- // This loop is necessary as we need to know what the last child is.
- for (size_t i = 0; i < m_sub_objects.size(); ++i) {
- auto const& child = m_sub_objects[i];
- child.format(builder, indentation + 1, false);
- if (is<Object>(child) && i != m_sub_objects.size() - 1)
- builder.append('\n');
- }
- indent(builder, indentation);
- }
- builder.append('}');
- if (!is_inline)
- builder.append('\n');
- }
- private:
- // Properties and comments
- NonnullRefPtrVector<Node> m_properties;
- // Sub objects and comments
- NonnullRefPtrVector<Node> m_sub_objects;
- String m_name {};
- };
- class GMLFile : public Node {
- public:
- virtual ~GMLFile() override = default;
- ErrorOr<void> add_child(NonnullRefPtr<Node> child)
- {
- if (!has_main_class()) {
- if (is<Comment>(child.ptr())) {
- return m_leading_comments.try_append(*static_ptr_cast<Comment>(child));
- }
- if (is<Object>(child.ptr())) {
- m_main_class = static_ptr_cast<Object>(child);
- return {};
- }
- return Error::from_string_literal("Unexpected data before main class");
- }
- // After the main class, only comments are allowed.
- if (!is<Comment>(child.ptr()))
- return Error::from_string_literal("Data not allowed after main class");
- return m_trailing_comments.try_append(*static_ptr_cast<Comment>(child));
- }
- bool has_main_class() const { return m_main_class != nullptr; }
- NonnullRefPtrVector<Comment> leading_comments() const { return m_leading_comments; }
- Object& main_class()
- {
- VERIFY(!m_main_class.is_null());
- return *m_main_class.ptr();
- }
- NonnullRefPtrVector<Comment> trailing_comments() const { return m_trailing_comments; }
- virtual void format(StringBuilder& builder, size_t indentation, [[maybe_unused]] bool is_inline) const override
- {
- for (auto const& comment : m_leading_comments)
- comment.format(builder, indentation, false);
- if (!m_leading_comments.is_empty())
- builder.append('\n');
- m_main_class->format(builder, indentation, false);
- if (!m_trailing_comments.is_empty())
- builder.append('\n');
- for (auto const& comment : m_trailing_comments)
- comment.format(builder, indentation, false);
- }
- private:
- NonnullRefPtrVector<Comment> m_leading_comments;
- RefPtr<Object> m_main_class;
- NonnullRefPtrVector<Comment> m_trailing_comments;
- };
- }
|