ladybird/Userland/Libraries/LibGUI/GML/Parser.cpp
sin-ack e5f09ea170 Everywhere: Split Error::from_string_literal and Error::from_string_view
Error::from_string_literal now takes direct char const*s, while
Error::from_string_view does what Error::from_string_literal used to do:
taking StringViews. This change will remove the need to insert `sv`
after error strings when returning string literal errors once
StringView(char const*) is removed.

No functional changes.
2022-07-12 23:11:35 +02:00

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");
tokens.dequeue();
if (peek() != Token::Type::ClassName)
return Error::from_string_literal("Expected class name");
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_first()));
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_first()));
auto property_name = tokens.dequeue();
if (property_name.m_view.is_empty())
return Error::from_string_literal("Expected non-empty property name");
if (peek() != Token::Type::Colon)
return Error::from_string_literal("Expected ':'");
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 }}");
}
}
// 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 }}");
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;
}
}