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& media_feature_data, Core::File& file);
14ErrorOr<void> generate_implementation_file(JsonObject& media_feature_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 media_features_json_path;
21
22 Core::ArgsParser args_parser;
23 args_parser.add_option(generated_header_path, "Path to the MediaFeatureID header file to generate", "generated-header-path", 'h', "generated-header-path");
24 args_parser.add_option(generated_implementation_path, "Path to the MediaFeatureID implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
25 args_parser.add_option(media_features_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(media_features_json_path));
29 VERIFY(json.is_object());
30 auto media_feature_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(media_feature_data, *generated_header_file));
36 TRY(generate_implementation_file(media_feature_data, *generated_implementation_file));
37
38 return 0;
39}
40
41ErrorOr<void> generate_header_file(JsonObject& media_feature_data, Core::File& file)
42{
43 StringBuilder builder;
44 SourceGenerator generator { builder };
45 generator.append(R"~~~(#pragma once
46
47#include <AK/StringView.h>
48#include <AK/Traits.h>
49#include <LibWeb/CSS/ValueID.h>
50
51namespace Web::CSS {
52
53enum class MediaFeatureValueType {
54 Boolean,
55 Integer,
56 Length,
57 Ratio,
58 Resolution,
59};
60
61enum class MediaFeatureID {)~~~");
62
63 media_feature_data.for_each_member([&](auto& name, auto&) {
64 auto member_generator = generator.fork();
65 member_generator.set("name:titlecase", title_casify(name));
66 member_generator.append(R"~~~(
67 @name:titlecase@,)~~~");
68 });
69
70 generator.append(R"~~~(
71};
72
73Optional<MediaFeatureID> media_feature_id_from_string(StringView);
74StringView string_from_media_feature_id(MediaFeatureID);
75
76bool media_feature_type_is_range(MediaFeatureID);
77bool media_feature_accepts_type(MediaFeatureID, MediaFeatureValueType);
78bool media_feature_accepts_identifier(MediaFeatureID, ValueID);
79
80}
81)~~~");
82
83 TRY(file.write_until_depleted(generator.as_string_view().bytes()));
84 return {};
85}
86
87ErrorOr<void> generate_implementation_file(JsonObject& media_feature_data, Core::File& file)
88{
89 StringBuilder builder;
90 SourceGenerator generator { builder };
91 generator.append(R"~~~(
92#include <LibWeb/CSS/MediaFeatureID.h>
93#include <LibWeb/Infra/Strings.h>
94
95namespace Web::CSS {
96
97Optional<MediaFeatureID> media_feature_id_from_string(StringView string)
98{)~~~");
99
100 media_feature_data.for_each_member([&](auto& name, auto&) {
101 auto member_generator = generator.fork();
102 member_generator.set("name", name);
103 member_generator.set("name:titlecase", title_casify(name));
104 member_generator.append(R"~~~(
105 if (Infra::is_ascii_case_insensitive_match(string, "@name@"sv))
106 return MediaFeatureID::@name:titlecase@;
107)~~~");
108 });
109
110 generator.append(R"~~~(
111 return {};
112}
113
114StringView string_from_media_feature_id(MediaFeatureID media_feature_id)
115{
116 switch (media_feature_id) {)~~~");
117
118 media_feature_data.for_each_member([&](auto& name, auto&) {
119 auto member_generator = generator.fork();
120 member_generator.set("name", name);
121 member_generator.set("name:titlecase", title_casify(name));
122 member_generator.append(R"~~~(
123 case MediaFeatureID::@name:titlecase@:
124 return "@name@"sv;)~~~");
125 });
126
127 generator.append(R"~~~(
128 }
129 VERIFY_NOT_REACHED();
130}
131
132bool media_feature_type_is_range(MediaFeatureID media_feature_id)
133{
134 switch (media_feature_id) {)~~~");
135
136 media_feature_data.for_each_member([&](auto& name, auto& value) {
137 VERIFY(value.is_object());
138 auto& feature = value.as_object();
139
140 auto member_generator = generator.fork();
141 member_generator.set("name:titlecase", title_casify(name));
142 VERIFY(feature.has("type"sv));
143 auto feature_type = feature.get_deprecated_string("type"sv);
144 VERIFY(feature_type.has_value());
145 member_generator.set("is_range", feature_type.value() == "range" ? "true" : "false");
146 member_generator.append(R"~~~(
147 case MediaFeatureID::@name:titlecase@:
148 return @is_range@;)~~~");
149 });
150
151 generator.append(R"~~~(
152 }
153 VERIFY_NOT_REACHED();
154}
155
156bool media_feature_accepts_type(MediaFeatureID media_feature_id, MediaFeatureValueType value_type)
157{
158 switch (media_feature_id) {)~~~");
159
160 media_feature_data.for_each_member([&](auto& name, auto& member) {
161 VERIFY(member.is_object());
162 auto& feature = member.as_object();
163
164 auto member_generator = generator.fork();
165 member_generator.set("name:titlecase", title_casify(name));
166 member_generator.append(R"~~~(
167 case MediaFeatureID::@name:titlecase@:)~~~");
168
169 bool have_output_value_type_switch = false;
170 if (feature.has("values"sv)) {
171 auto append_value_type_switch_if_needed = [&]() {
172 if (!have_output_value_type_switch) {
173 member_generator.append(R"~~~(
174 switch (value_type) {)~~~");
175 }
176 have_output_value_type_switch = true;
177 };
178 auto values = feature.get_array("values"sv);
179 VERIFY(values.has_value());
180 auto& values_array = values.value();
181 for (auto& type : values_array.values()) {
182 VERIFY(type.is_string());
183 auto type_name = type.as_string();
184 // Skip identifiers.
185 if (type_name[0] != '<')
186 continue;
187 if (type_name == "<mq-boolean>") {
188 append_value_type_switch_if_needed();
189 member_generator.append(R"~~~(
190 case MediaFeatureValueType::Boolean:
191 return true;)~~~");
192 } else if (type_name == "<integer>") {
193 append_value_type_switch_if_needed();
194 member_generator.append(R"~~~(
195 case MediaFeatureValueType::Integer:
196 return true;)~~~");
197 } else if (type_name == "<length>") {
198 append_value_type_switch_if_needed();
199 member_generator.append(R"~~~(
200 case MediaFeatureValueType::Length:
201 return true;)~~~");
202 } else if (type_name == "<ratio>") {
203 append_value_type_switch_if_needed();
204 member_generator.append(R"~~~(
205 case MediaFeatureValueType::Ratio:
206 return true;)~~~");
207 } else if (type_name == "<resolution>") {
208 append_value_type_switch_if_needed();
209 member_generator.append(R"~~~(
210 case MediaFeatureValueType::Resolution:
211 return true;)~~~");
212 } else {
213 warnln("Unrecognized media-feature value type: `{}`", type_name);
214 VERIFY_NOT_REACHED();
215 }
216 }
217 }
218 if (have_output_value_type_switch) {
219 member_generator.append(R"~~~(
220 default:
221 return false;
222 })~~~");
223 } else {
224 member_generator.append(R"~~~(
225 return false;)~~~");
226 }
227 });
228
229 generator.append(R"~~~(
230 }
231 VERIFY_NOT_REACHED();
232}
233
234bool media_feature_accepts_identifier(MediaFeatureID media_feature_id, ValueID identifier)
235{
236 switch (media_feature_id) {)~~~");
237
238 media_feature_data.for_each_member([&](auto& name, auto& member) {
239 VERIFY(member.is_object());
240 auto& feature = member.as_object();
241
242 auto member_generator = generator.fork();
243 member_generator.set("name:titlecase", title_casify(name));
244 member_generator.append(R"~~~(
245 case MediaFeatureID::@name:titlecase@:)~~~");
246
247 bool have_output_identifier_switch = false;
248 if (feature.has("values"sv)) {
249 auto append_identifier_switch_if_needed = [&]() {
250 if (!have_output_identifier_switch) {
251 member_generator.append(R"~~~(
252 switch (identifier) {)~~~");
253 }
254 have_output_identifier_switch = true;
255 };
256 auto values = feature.get_array("values"sv);
257 VERIFY(values.has_value());
258 auto& values_array = values.value();
259 for (auto& identifier : values_array.values()) {
260 VERIFY(identifier.is_string());
261 auto identifier_name = identifier.as_string();
262 // Skip types.
263 if (identifier_name[0] == '<')
264 continue;
265 append_identifier_switch_if_needed();
266
267 auto ident_generator = member_generator.fork();
268 ident_generator.set("identifier:titlecase", title_casify(identifier_name));
269 ident_generator.append(R"~~~(
270 case ValueID::@identifier:titlecase@:
271 return true;)~~~");
272 }
273 }
274 if (have_output_identifier_switch) {
275 member_generator.append(R"~~~(
276 default:
277 return false;
278 })~~~");
279 } else {
280 member_generator.append(R"~~~(
281 return false;)~~~");
282 }
283 });
284
285 generator.append(R"~~~(
286 }
287 VERIFY_NOT_REACHED();
288}
289
290}
291)~~~");
292
293 TRY(file.write_until_depleted(generator.as_string_view().bytes()));
294 return {};
295}