mirror of
https://github.com/RGBCube/serenity
synced 2025-05-14 07:04:57 +00:00

This is a bit of a hack, but it is an easy way to finally get spacers into GML. This will translate well if spacers are later to become child objects of the continer widget.
134 lines
4.2 KiB
C++
134 lines
4.2 KiB
C++
/*
|
|
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
|
* Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>
|
|
* Copyright (c) 2022, Idan Horowitz <idan.horowitz@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#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 ErrorOr<NonnullRefPtr<Object>> parse_gml_object(Queue<Token>& tokens)
|
|
{
|
|
auto object = TRY(try_make_ref_counted<Object>());
|
|
|
|
auto peek = [&] {
|
|
if (tokens.is_empty())
|
|
return Token::Type::Unknown;
|
|
return tokens.head().m_type;
|
|
};
|
|
|
|
while (peek() == Token::Type::Comment)
|
|
TRY(object->add_property_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)
|
|
return Error::from_string_literal("Expected class name"sv);
|
|
|
|
auto class_name = tokens.dequeue();
|
|
object->set_name(class_name.m_view);
|
|
|
|
if (peek() == Token::Type::LeftCurly) {
|
|
|
|
tokens.dequeue();
|
|
|
|
NonnullRefPtrVector<Comment> pending_comments;
|
|
for (;;) {
|
|
if (peek() == Token::Type::RightCurly) {
|
|
// End of object
|
|
break;
|
|
}
|
|
|
|
if (peek() == Token::Type::ClassMarker) {
|
|
// It's a child object.
|
|
|
|
while (!pending_comments.is_empty())
|
|
TRY(object->add_sub_object_child(pending_comments.take_last()));
|
|
|
|
TRY(object->add_sub_object_child(TRY(parse_gml_object(tokens))));
|
|
} else if (peek() == Token::Type::Identifier) {
|
|
// It's a property.
|
|
|
|
while (!pending_comments.is_empty())
|
|
TRY(object->add_property_child(pending_comments.take_last()));
|
|
|
|
auto property_name = tokens.dequeue();
|
|
|
|
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);
|
|
|
|
tokens.dequeue();
|
|
|
|
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_property_child(property));
|
|
} else if (peek() == Token::Type::Comment) {
|
|
pending_comments.append(TRY(Node::from_token<Comment>(tokens.dequeue())));
|
|
} else {
|
|
return Error::from_string_literal("Expected child, property, comment, or }}"sv);
|
|
}
|
|
}
|
|
|
|
// Insert any left-over comments as sub object children, as these will be serialized last
|
|
while (!pending_comments.is_empty())
|
|
TRY(object->add_sub_object_child(pending_comments.take_first()));
|
|
|
|
if (peek() != Token::Type::RightCurly)
|
|
return Error::from_string_literal("Expected }}"sv);
|
|
|
|
tokens.dequeue();
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<GMLFile>> parse_gml(StringView string)
|
|
{
|
|
auto lexer = Lexer(string);
|
|
|
|
Queue<Token> tokens;
|
|
for (auto& token : lexer.lex())
|
|
tokens.enqueue(token);
|
|
|
|
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))));
|
|
|
|
while (!tokens.is_empty())
|
|
TRY(file->add_child(TRY(Node::from_token<Comment>(tokens.dequeue()))));
|
|
|
|
return file;
|
|
}
|
|
|
|
}
|