summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGUI/GMLParser.cpp
blob: 659fad1a9a8a593d211d3b31a7ff6346f743f759 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/GenericLexer.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <AK/Queue.h>
#include <LibGUI/GMLLexer.h>
#include <LibGUI/GMLParser.h>
#include <ctype.h>

namespace GUI {

static Optional<JsonValue> parse_core_object(Queue<GMLToken>& tokens)
{
    JsonObject object;
    JsonArray children;

    auto peek = [&] {
        if (tokens.is_empty())
            return GMLToken::Type::Unknown;
        return tokens.head().m_type;
    };

    while (peek() == GMLToken::Type::Comment)
        tokens.dequeue();

    if (peek() != GMLToken::Type::ClassMarker) {
        dbgln("Expected class marker");
        return {};
    }

    tokens.dequeue();

    if (peek() != GMLToken::Type::ClassName) {
        dbgln("Expected class name");
        return {};
    }

    auto class_name = tokens.dequeue();
    object.set("class", JsonValue(class_name.m_view));

    if (peek() != GMLToken::Type::LeftCurly) {
        // Empty object
        return object;
    }
    tokens.dequeue();

    for (;;) {
        if (peek() == GMLToken::Type::RightCurly) {
            // End of object
            break;
        }

        if (peek() == GMLToken::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());
        } else if (peek() == GMLToken::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 (peek() != GMLToken::Type::Colon) {
                dbgln("Expected ':'");
                return {};
            }
            tokens.dequeue();

            JsonValue value;
            if (peek() == GMLToken::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() == GMLToken::Type::JsonValue) {
                auto value_string = tokens.dequeue();
                auto parsed_value = JsonValue::from_string(value_string.m_view);
                if (!parsed_value.has_value()) {
                    dbgln("Expected property to be JSON value");
                    return {};
                }
                value = parsed_value.release_value();
            }
            object.set(property_name.m_view, move(value));
        } else if (peek() == GMLToken::Type::Comment) {
            tokens.dequeue();
        } else {
            dbgln("Expected child, property, comment, or }}");
            return {};
        }
    }

    if (peek() != GMLToken::Type::RightCurly) {
        dbgln("Expected }}");
        return {};
    }
    tokens.dequeue();

    if (!children.is_empty())
        object.set("children", move(children));

    return object;
}

JsonValue parse_gml(const StringView& string)
{
    auto lexer = GMLLexer(string);

    Queue<GMLToken> tokens;
    for (auto& token : lexer.lex())
        tokens.enqueue(token);

    auto root = parse_core_object(tokens);

    if (!root.has_value())
        return JsonValue();

    return root.release_value();
}

}