Serenity Operating System
1/*
2 * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "GeneratorUtil.h"
8#include <AK/SourceGenerator.h>
9#include <AK/StringBuilder.h>
10#include <LibCore/ArgsParser.h>
11#include <LibMain/Main.h>
12
13ErrorOr<void> generate_header_file(JsonObject& enums_data, Core::File& file);
14ErrorOr<void> generate_implementation_file(JsonObject& enums_data, Core::File& file);
15
16ErrorOr<int> serenity_main(Main::Arguments arguments)
17{
18 StringView generated_header_path;
19 StringView generated_implementation_path;
20 StringView identifiers_json_path;
21
22 Core::ArgsParser args_parser;
23 args_parser.add_option(generated_header_path, "Path to the Enums header file to generate", "generated-header-path", 'h', "generated-header-path");
24 args_parser.add_option(generated_implementation_path, "Path to the Enums implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
25 args_parser.add_option(identifiers_json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path");
26 args_parser.parse(arguments);
27
28 auto json = TRY(read_entire_file_as_json(identifiers_json_path));
29 VERIFY(json.is_object());
30 auto enums_data = json.as_object();
31
32 auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write));
33 auto generated_implementation_file = TRY(Core::File::open(generated_implementation_path, Core::File::OpenMode::Write));
34
35 TRY(generate_header_file(enums_data, *generated_header_file));
36 TRY(generate_implementation_file(enums_data, *generated_implementation_file));
37
38 return 0;
39}
40
41ErrorOr<void> generate_header_file(JsonObject& enums_data, Core::File& file)
42{
43 StringBuilder builder;
44 SourceGenerator generator { builder };
45
46 generator.append(R"~~~(
47#pragma once
48
49#include <AK/Optional.h>
50
51namespace Web::CSS {
52
53enum class ValueID;
54
55)~~~");
56
57 enums_data.for_each_member([&](auto& name, auto& value) {
58 VERIFY(value.is_array());
59 auto& members = value.as_array();
60
61 auto enum_generator = generator.fork();
62 enum_generator.set("name:titlecase", title_casify(name));
63 enum_generator.set("name:snakecase", snake_casify(name));
64
65 // Find the smallest possible type to use.
66 auto member_max_value = members.size() - 1;
67 if (NumericLimits<u8>::max() >= member_max_value) {
68 enum_generator.set("enum_type", "u8");
69 } else if (NumericLimits<u16>::max() >= member_max_value) {
70 enum_generator.set("enum_type", "u16");
71 } else if (NumericLimits<u32>::max() >= member_max_value) {
72 enum_generator.set("enum_type", "u32");
73 } else {
74 enum_generator.set("enum_type", "u64");
75 }
76
77 enum_generator.appendln("enum class @name:titlecase@ : @enum_type@ {");
78
79 for (auto& member : members.values()) {
80 auto member_name = member.to_deprecated_string();
81 // Don't include aliases in the enum.
82 if (member_name.contains('='))
83 continue;
84 auto member_generator = enum_generator.fork();
85 member_generator.set("member:titlecase", title_casify(member_name));
86 member_generator.appendln(" @member:titlecase@,");
87 }
88
89 enum_generator.appendln("};");
90 enum_generator.appendln("Optional<@name:titlecase@> value_id_to_@name:snakecase@(ValueID);");
91 enum_generator.appendln("ValueID to_value_id(@name:titlecase@);");
92 enum_generator.appendln("StringView to_string(@name:titlecase@);");
93 enum_generator.append("\n");
94 });
95
96 generator.appendln("}");
97
98 TRY(file.write_until_depleted(generator.as_string_view().bytes()));
99 return {};
100}
101
102ErrorOr<void> generate_implementation_file(JsonObject& enums_data, Core::File& file)
103{
104 StringBuilder builder;
105 SourceGenerator generator { builder };
106
107 generator.append(R"~~~(
108#include <LibWeb/CSS/Enums.h>
109#include <LibWeb/CSS/ValueID.h>
110
111namespace Web::CSS {
112)~~~");
113
114 enums_data.for_each_member([&](auto& name, auto& value) {
115 VERIFY(value.is_array());
116 auto& members = value.as_array();
117
118 auto enum_generator = generator.fork();
119 enum_generator.set("name:titlecase", title_casify(name));
120 enum_generator.set("name:snakecase", snake_casify(name));
121
122 enum_generator.append(R"~~~(
123Optional<@name:titlecase@> value_id_to_@name:snakecase@(ValueID value_id)
124{
125 switch (value_id) {)~~~");
126
127 for (auto& member : members.values()) {
128 auto member_generator = enum_generator.fork();
129 auto member_name = member.to_deprecated_string();
130 if (member_name.contains('=')) {
131 auto parts = member_name.split_view('=');
132 member_generator.set("valueid:titlecase", title_casify(parts[0]));
133 member_generator.set("member:titlecase", title_casify(parts[1]));
134 } else {
135 member_generator.set("valueid:titlecase", title_casify(member_name));
136 member_generator.set("member:titlecase", title_casify(member_name));
137 }
138 member_generator.append(R"~~~(
139 case ValueID::@valueid:titlecase@:
140 return @name:titlecase@::@member:titlecase@;)~~~");
141 }
142
143 enum_generator.append(R"~~~(
144 default:
145 return {};
146 }
147}
148)~~~");
149
150 enum_generator.append(R"~~~(
151ValueID to_value_id(@name:titlecase@ @name:snakecase@_value)
152{
153 switch (@name:snakecase@_value) {)~~~");
154
155 for (auto& member : members.values()) {
156 auto member_generator = enum_generator.fork();
157 auto member_name = member.to_deprecated_string();
158 if (member_name.contains('='))
159 continue;
160 member_generator.set("member:titlecase", title_casify(member_name));
161
162 member_generator.append(R"~~~(
163 case @name:titlecase@::@member:titlecase@:
164 return ValueID::@member:titlecase@;)~~~");
165 }
166
167 enum_generator.append(R"~~~(
168 default:
169 VERIFY_NOT_REACHED();
170 }
171}
172)~~~");
173
174 enum_generator.append(R"~~~(
175StringView to_string(@name:titlecase@ value)
176{
177 switch (value) {)~~~");
178
179 for (auto& member : members.values()) {
180 auto member_generator = enum_generator.fork();
181 auto member_name = member.to_deprecated_string();
182 if (member_name.contains('='))
183 continue;
184 member_generator.set("member:css", member_name);
185 member_generator.set("member:titlecase", title_casify(member_name));
186
187 member_generator.append(R"~~~(
188 case @name:titlecase@::@member:titlecase@:
189 return "@member:css@"sv;)~~~");
190 }
191
192 enum_generator.append(R"~~~(
193 default:
194 VERIFY_NOT_REACHED();
195 }
196}
197)~~~");
198 });
199
200 generator.appendln("}");
201
202 TRY(file.write_until_depleted(generator.as_string_view().bytes()));
203 return {};
204}