Serenity Operating System
at master 295 lines 10 kB view raw
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}