GenerateCSSPropertyID.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "GeneratorUtil.h"
  8. #include <AK/GenericShorthands.h>
  9. #include <AK/SourceGenerator.h>
  10. #include <AK/StringBuilder.h>
  11. #include <LibCore/ArgsParser.h>
  12. #include <LibMain/Main.h>
  13. ErrorOr<void> replace_logical_aliases(JsonObject& properties);
  14. ErrorOr<void> generate_header_file(JsonObject& properties, Core::File& file);
  15. ErrorOr<void> generate_implementation_file(JsonObject& properties, Core::File& file);
  16. static bool type_name_is_enum(StringView type_name)
  17. {
  18. return !AK::first_is_one_of(type_name, "angle"sv, "color"sv, "custom-ident"sv, "frequency"sv, "image"sv, "integer"sv, "length"sv, "number"sv, "percentage"sv, "rect"sv, "resolution"sv, "string"sv, "time"sv, "url"sv);
  19. }
  20. ErrorOr<int> serenity_main(Main::Arguments arguments)
  21. {
  22. StringView generated_header_path;
  23. StringView generated_implementation_path;
  24. StringView properties_json_path;
  25. Core::ArgsParser args_parser;
  26. args_parser.add_option(generated_header_path, "Path to the PropertyID header file to generate", "generated-header-path", 'h', "generated-header-path");
  27. args_parser.add_option(generated_implementation_path, "Path to the PropertyID implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
  28. args_parser.add_option(properties_json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path");
  29. args_parser.parse(arguments);
  30. auto json = TRY(read_entire_file_as_json(properties_json_path));
  31. VERIFY(json.is_object());
  32. auto properties = json.as_object();
  33. TRY(replace_logical_aliases(properties));
  34. auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write));
  35. auto generated_implementation_file = TRY(Core::File::open(generated_implementation_path, Core::File::OpenMode::Write));
  36. TRY(generate_header_file(properties, *generated_header_file));
  37. TRY(generate_implementation_file(properties, *generated_implementation_file));
  38. return 0;
  39. }
  40. ErrorOr<void> replace_logical_aliases(JsonObject& properties)
  41. {
  42. AK::HashMap<DeprecatedString, DeprecatedString> logical_aliases;
  43. properties.for_each_member([&](auto& name, auto& value) {
  44. VERIFY(value.is_object());
  45. const auto& value_as_object = value.as_object();
  46. const auto logical_alias_for = value_as_object.get_deprecated_string("logical-alias-for"sv);
  47. if (logical_alias_for.has_value()) {
  48. logical_aliases.set(name, logical_alias_for.value());
  49. }
  50. });
  51. for (auto& [name, alias] : logical_aliases) {
  52. auto const maybe_alias_object = properties.get_object(alias);
  53. if (!maybe_alias_object.has_value()) {
  54. dbgln("No property '{}' found for logical alias '{}'", alias, name);
  55. VERIFY_NOT_REACHED();
  56. }
  57. JsonObject alias_object = maybe_alias_object.value();
  58. // Copy over anything the logical property overrides
  59. properties.get_object(name).value().for_each_member([&](auto& key, auto& value) {
  60. alias_object.set(key, value);
  61. });
  62. properties.set(name, alias_object);
  63. }
  64. return {};
  65. }
  66. ErrorOr<void> generate_header_file(JsonObject& properties, Core::File& file)
  67. {
  68. StringBuilder builder;
  69. SourceGenerator generator { builder };
  70. generator.append(R"~~~(
  71. #pragma once
  72. #include <AK/NonnullRefPtr.h>
  73. #include <AK/StringView.h>
  74. #include <AK/Traits.h>
  75. #include <LibJS/Forward.h>
  76. #include <LibWeb/Forward.h>
  77. namespace Web::CSS {
  78. enum class PropertyID {
  79. Invalid,
  80. Custom,
  81. )~~~");
  82. Vector<DeprecatedString> shorthand_property_ids;
  83. Vector<DeprecatedString> longhand_property_ids;
  84. properties.for_each_member([&](auto& name, auto& value) {
  85. VERIFY(value.is_object());
  86. if (value.as_object().has("longhands"sv))
  87. shorthand_property_ids.append(name);
  88. else
  89. longhand_property_ids.append(name);
  90. });
  91. auto first_property_id = shorthand_property_ids.first();
  92. auto last_property_id = longhand_property_ids.last();
  93. for (auto& name : shorthand_property_ids) {
  94. auto member_generator = generator.fork();
  95. member_generator.set("name:titlecase", title_casify(name));
  96. member_generator.append(R"~~~(
  97. @name:titlecase@,
  98. )~~~");
  99. }
  100. for (auto& name : longhand_property_ids) {
  101. auto member_generator = generator.fork();
  102. member_generator.set("name:titlecase", title_casify(name));
  103. member_generator.append(R"~~~(
  104. @name:titlecase@,
  105. )~~~");
  106. }
  107. generator.set("first_property_id", title_casify(first_property_id));
  108. generator.set("last_property_id", title_casify(last_property_id));
  109. generator.set("first_shorthand_property_id", title_casify(shorthand_property_ids.first()));
  110. generator.set("last_shorthand_property_id", title_casify(shorthand_property_ids.last()));
  111. generator.set("first_longhand_property_id", title_casify(longhand_property_ids.first()));
  112. generator.set("last_longhand_property_id", title_casify(longhand_property_ids.last()));
  113. generator.append(R"~~~(
  114. };
  115. Optional<PropertyID> property_id_from_camel_case_string(StringView);
  116. Optional<PropertyID> property_id_from_string(StringView);
  117. StringView string_from_property_id(PropertyID);
  118. bool is_inherited_property(PropertyID);
  119. ErrorOr<NonnullRefPtr<StyleValue>> property_initial_value(JS::Realm&, PropertyID);
  120. enum class ValueType {
  121. Angle,
  122. Color,
  123. CustomIdent,
  124. FilterValueList,
  125. Frequency,
  126. Image,
  127. Integer,
  128. Length,
  129. Number,
  130. Paint,
  131. Percentage,
  132. Position,
  133. Rect,
  134. Resolution,
  135. String,
  136. Time,
  137. Url,
  138. };
  139. bool property_accepts_type(PropertyID, ValueType);
  140. bool property_accepts_identifier(PropertyID, ValueID);
  141. size_t property_maximum_value_count(PropertyID);
  142. bool property_affects_layout(PropertyID);
  143. bool property_affects_stacking_context(PropertyID);
  144. constexpr PropertyID first_property_id = PropertyID::@first_property_id@;
  145. constexpr PropertyID last_property_id = PropertyID::@last_property_id@;
  146. constexpr PropertyID first_shorthand_property_id = PropertyID::@first_shorthand_property_id@;
  147. constexpr PropertyID last_shorthand_property_id = PropertyID::@last_shorthand_property_id@;
  148. constexpr PropertyID first_longhand_property_id = PropertyID::@first_longhand_property_id@;
  149. constexpr PropertyID last_longhand_property_id = PropertyID::@last_longhand_property_id@;
  150. enum class Quirk {
  151. // https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk
  152. HashlessHexColor,
  153. // https://quirks.spec.whatwg.org/#the-unitless-length-quirk
  154. UnitlessLength,
  155. };
  156. bool property_has_quirk(PropertyID, Quirk);
  157. } // namespace Web::CSS
  158. namespace AK {
  159. template<>
  160. struct Traits<Web::CSS::PropertyID> : public GenericTraits<Web::CSS::PropertyID> {
  161. static unsigned hash(Web::CSS::PropertyID property_id) { return int_hash((unsigned)property_id); }
  162. };
  163. } // namespace AK
  164. )~~~");
  165. TRY(file.write_until_depleted(generator.as_string_view().bytes()));
  166. return {};
  167. }
  168. ErrorOr<void> generate_implementation_file(JsonObject& properties, Core::File& file)
  169. {
  170. StringBuilder builder;
  171. SourceGenerator generator { builder };
  172. generator.append(R"~~~(
  173. #include <AK/Assertions.h>
  174. #include <LibWeb/CSS/Enums.h>
  175. #include <LibWeb/CSS/Parser/Parser.h>
  176. #include <LibWeb/CSS/PropertyID.h>
  177. #include <LibWeb/CSS/StyleValue.h>
  178. #include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
  179. #include <LibWeb/Infra/Strings.h>
  180. namespace Web::CSS {
  181. Optional<PropertyID> property_id_from_camel_case_string(StringView string)
  182. {
  183. )~~~");
  184. properties.for_each_member([&](auto& name, auto& value) {
  185. VERIFY(value.is_object());
  186. auto member_generator = generator.fork();
  187. member_generator.set("name", name);
  188. member_generator.set("name:titlecase", title_casify(name));
  189. member_generator.set("name:camelcase", camel_casify(name));
  190. member_generator.append(R"~~~(
  191. if (string.equals_ignoring_ascii_case("@name:camelcase@"sv))
  192. return PropertyID::@name:titlecase@;
  193. )~~~");
  194. });
  195. generator.append(R"~~~(
  196. return {};
  197. }
  198. Optional<PropertyID> property_id_from_string(StringView string)
  199. {
  200. )~~~");
  201. properties.for_each_member([&](auto& name, auto& value) {
  202. VERIFY(value.is_object());
  203. auto member_generator = generator.fork();
  204. member_generator.set("name", name);
  205. member_generator.set("name:titlecase", title_casify(name));
  206. member_generator.append(R"~~~(
  207. if (Infra::is_ascii_case_insensitive_match(string, "@name@"sv))
  208. return PropertyID::@name:titlecase@;
  209. )~~~");
  210. });
  211. generator.append(R"~~~(
  212. return {};
  213. }
  214. StringView string_from_property_id(PropertyID property_id) {
  215. switch (property_id) {
  216. )~~~");
  217. properties.for_each_member([&](auto& name, auto& value) {
  218. VERIFY(value.is_object());
  219. auto member_generator = generator.fork();
  220. member_generator.set("name", name);
  221. member_generator.set("name:titlecase", title_casify(name));
  222. member_generator.append(R"~~~(
  223. case PropertyID::@name:titlecase@:
  224. return "@name@"sv;
  225. )~~~");
  226. });
  227. generator.append(R"~~~(
  228. default:
  229. return "(invalid CSS::PropertyID)"sv;
  230. }
  231. }
  232. bool is_inherited_property(PropertyID property_id)
  233. {
  234. switch (property_id) {
  235. )~~~");
  236. properties.for_each_member([&](auto& name, auto& value) {
  237. VERIFY(value.is_object());
  238. bool inherited = false;
  239. if (value.as_object().has("inherited"sv)) {
  240. auto inherited_value = value.as_object().get_bool("inherited"sv);
  241. VERIFY(inherited_value.has_value());
  242. inherited = inherited_value.value();
  243. }
  244. if (inherited) {
  245. auto member_generator = generator.fork();
  246. member_generator.set("name:titlecase", title_casify(name));
  247. member_generator.append(R"~~~(
  248. case PropertyID::@name:titlecase@:
  249. return true;
  250. )~~~");
  251. }
  252. });
  253. generator.append(R"~~~(
  254. default:
  255. return false;
  256. }
  257. }
  258. bool property_affects_layout(PropertyID property_id)
  259. {
  260. switch (property_id) {
  261. )~~~");
  262. properties.for_each_member([&](auto& name, auto& value) {
  263. VERIFY(value.is_object());
  264. bool affects_layout = true;
  265. if (value.as_object().has("affects-layout"sv))
  266. affects_layout = value.as_object().get_bool("affects-layout"sv).value_or(false);
  267. if (affects_layout) {
  268. auto member_generator = generator.fork();
  269. member_generator.set("name:titlecase", title_casify(name));
  270. member_generator.append(R"~~~(
  271. case PropertyID::@name:titlecase@:
  272. )~~~");
  273. }
  274. });
  275. generator.append(R"~~~(
  276. return true;
  277. default:
  278. return false;
  279. }
  280. }
  281. bool property_affects_stacking_context(PropertyID property_id)
  282. {
  283. switch (property_id) {
  284. )~~~");
  285. properties.for_each_member([&](auto& name, auto& value) {
  286. VERIFY(value.is_object());
  287. bool affects_stacking_context = false;
  288. if (value.as_object().has("affects-stacking-context"sv))
  289. affects_stacking_context = value.as_object().get_bool("affects-stacking-context"sv).value_or(false);
  290. if (affects_stacking_context) {
  291. auto member_generator = generator.fork();
  292. member_generator.set("name:titlecase", title_casify(name));
  293. member_generator.append(R"~~~(
  294. case PropertyID::@name:titlecase@:
  295. )~~~");
  296. }
  297. });
  298. generator.append(R"~~~(
  299. return true;
  300. default:
  301. return false;
  302. }
  303. }
  304. ErrorOr<NonnullRefPtr<StyleValue>> property_initial_value(JS::Realm& context_realm, PropertyID property_id)
  305. {
  306. static Array<RefPtr<StyleValue>, to_underlying(last_property_id) + 1> initial_values;
  307. if (auto initial_value = initial_values[to_underlying(property_id)])
  308. return initial_value.release_nonnull();
  309. // Lazily parse initial values as needed.
  310. // This ensures the shorthands will always be able to get the initial values of their longhands.
  311. // This also now allows a longhand have its own longhand (like background-position-x).
  312. Parser::ParsingContext parsing_context(context_realm);
  313. switch (property_id) {
  314. )~~~");
  315. auto output_initial_value_code = [&](auto& name, auto& object) {
  316. if (!object.has("initial"sv)) {
  317. dbgln("No initial value specified for property '{}'", name);
  318. VERIFY_NOT_REACHED();
  319. }
  320. auto initial_value = object.get_deprecated_string("initial"sv);
  321. VERIFY(initial_value.has_value());
  322. auto& initial_value_string = initial_value.value();
  323. auto member_generator = generator.fork();
  324. member_generator.set("name:titlecase", title_casify(name));
  325. member_generator.set("initial_value_string", initial_value_string);
  326. member_generator.append(
  327. R"~~~( case PropertyID::@name:titlecase@:
  328. {
  329. auto parsed_value = TRY(parse_css_value(parsing_context, "@initial_value_string@"sv, PropertyID::@name:titlecase@));
  330. VERIFY(!parsed_value.is_null());
  331. auto initial_value = parsed_value.release_nonnull();
  332. initial_values[to_underlying(PropertyID::@name:titlecase@)] = initial_value;
  333. return initial_value;
  334. }
  335. )~~~");
  336. };
  337. properties.for_each_member([&](auto& name, auto& value) {
  338. VERIFY(value.is_object());
  339. output_initial_value_code(name, value.as_object());
  340. });
  341. generator.append(
  342. R"~~~( default: VERIFY_NOT_REACHED();
  343. }
  344. VERIFY_NOT_REACHED();
  345. }
  346. bool property_has_quirk(PropertyID property_id, Quirk quirk)
  347. {
  348. switch (property_id) {
  349. )~~~");
  350. properties.for_each_member([&](auto& name, auto& value) {
  351. VERIFY(value.is_object());
  352. if (value.as_object().has("quirks"sv)) {
  353. auto quirks_value = value.as_object().get_array("quirks"sv);
  354. VERIFY(quirks_value.has_value());
  355. auto& quirks = quirks_value.value();
  356. if (!quirks.is_empty()) {
  357. auto property_generator = generator.fork();
  358. property_generator.set("name:titlecase", title_casify(name));
  359. property_generator.append(R"~~~(
  360. case PropertyID::@name:titlecase@: {
  361. switch (quirk) {
  362. )~~~");
  363. for (auto& quirk : quirks.values()) {
  364. VERIFY(quirk.is_string());
  365. auto quirk_generator = property_generator.fork();
  366. quirk_generator.set("quirk:titlecase", title_casify(quirk.as_string()));
  367. quirk_generator.append(R"~~~(
  368. case Quirk::@quirk:titlecase@:
  369. return true;
  370. )~~~");
  371. }
  372. property_generator.append(R"~~~(
  373. default:
  374. return false;
  375. }
  376. }
  377. )~~~");
  378. }
  379. }
  380. });
  381. generator.append(R"~~~(
  382. default:
  383. return false;
  384. }
  385. }
  386. bool property_accepts_type(PropertyID property_id, ValueType value_type)
  387. {
  388. switch (property_id) {
  389. )~~~");
  390. properties.for_each_member([&](auto& name, auto& value) {
  391. VERIFY(value.is_object());
  392. auto& object = value.as_object();
  393. if (auto maybe_valid_types = object.get_array("valid-types"sv); maybe_valid_types.has_value() && !maybe_valid_types->is_empty()) {
  394. auto& valid_types = maybe_valid_types.value();
  395. auto property_generator = generator.fork();
  396. property_generator.set("name:titlecase", title_casify(name));
  397. property_generator.append(R"~~~(
  398. case PropertyID::@name:titlecase@: {
  399. switch (value_type) {
  400. )~~~");
  401. bool did_output_accepted_type = false;
  402. for (auto& type : valid_types.values()) {
  403. VERIFY(type.is_string());
  404. auto type_name = type.as_string().split_view(' ').first();
  405. if (type_name_is_enum(type_name))
  406. continue;
  407. if (type_name == "angle") {
  408. property_generator.appendln(" case ValueType::Angle:");
  409. } else if (type_name == "color") {
  410. property_generator.appendln(" case ValueType::Color:");
  411. } else if (type_name == "custom-ident") {
  412. property_generator.appendln(" case ValueType::CustomIdent:");
  413. } else if (type_name == "frequency") {
  414. property_generator.appendln(" case ValueType::Frequency:");
  415. } else if (type_name == "image") {
  416. property_generator.appendln(" case ValueType::Image:");
  417. } else if (type_name == "integer") {
  418. property_generator.appendln(" case ValueType::Integer:");
  419. } else if (type_name == "length") {
  420. property_generator.appendln(" case ValueType::Length:");
  421. } else if (type_name == "number") {
  422. property_generator.appendln(" case ValueType::Number:");
  423. } else if (type_name == "percentage") {
  424. property_generator.appendln(" case ValueType::Percentage:");
  425. } else if (type_name == "rect") {
  426. property_generator.appendln(" case ValueType::Rect:");
  427. } else if (type_name == "resolution") {
  428. property_generator.appendln(" case ValueType::Resolution:");
  429. } else if (type_name == "string") {
  430. property_generator.appendln(" case ValueType::String:");
  431. } else if (type_name == "time") {
  432. property_generator.appendln(" case ValueType::Time:");
  433. } else if (type_name == "url") {
  434. property_generator.appendln(" case ValueType::Url:");
  435. } else {
  436. VERIFY_NOT_REACHED();
  437. }
  438. did_output_accepted_type = true;
  439. }
  440. if (did_output_accepted_type)
  441. property_generator.appendln(" return true;");
  442. property_generator.append(R"~~~(
  443. default:
  444. return false;
  445. }
  446. }
  447. )~~~");
  448. }
  449. });
  450. generator.append(R"~~~(
  451. default:
  452. return false;
  453. }
  454. }
  455. bool property_accepts_identifier(PropertyID property_id, ValueID identifier)
  456. {
  457. switch (property_id) {
  458. )~~~");
  459. properties.for_each_member([&](auto& name, auto& value) {
  460. VERIFY(value.is_object());
  461. auto& object = value.as_object();
  462. auto property_generator = generator.fork();
  463. property_generator.set("name:titlecase", title_casify(name));
  464. property_generator.appendln(" case PropertyID::@name:titlecase@: {");
  465. if (auto maybe_valid_identifiers = object.get_array("valid-identifiers"sv); maybe_valid_identifiers.has_value() && !maybe_valid_identifiers->is_empty()) {
  466. property_generator.appendln(" switch (identifier) {");
  467. auto& valid_identifiers = maybe_valid_identifiers.value();
  468. for (auto& identifier : valid_identifiers.values()) {
  469. auto identifier_generator = generator.fork();
  470. identifier_generator.set("identifier:titlecase", title_casify(identifier.as_string()));
  471. identifier_generator.appendln(" case ValueID::@identifier:titlecase@:");
  472. }
  473. property_generator.append(R"~~~(
  474. return true;
  475. default:
  476. break;
  477. }
  478. )~~~");
  479. }
  480. if (auto maybe_valid_types = object.get_array("valid-types"sv); maybe_valid_types.has_value() && !maybe_valid_types->is_empty()) {
  481. auto& valid_types = maybe_valid_types.value();
  482. for (auto& valid_type : valid_types.values()) {
  483. auto type_name = valid_type.as_string().split_view(' ').first();
  484. if (!type_name_is_enum(type_name))
  485. continue;
  486. auto type_generator = generator.fork();
  487. type_generator.set("type_name:snakecase", snake_casify(type_name));
  488. type_generator.append(R"~~~(
  489. if (value_id_to_@type_name:snakecase@(identifier).has_value())
  490. return true;
  491. )~~~");
  492. }
  493. }
  494. property_generator.append(R"~~~(
  495. return false;
  496. }
  497. )~~~");
  498. });
  499. generator.append(R"~~~(
  500. default:
  501. return false;
  502. }
  503. }
  504. size_t property_maximum_value_count(PropertyID property_id)
  505. {
  506. switch (property_id) {
  507. )~~~");
  508. properties.for_each_member([&](auto& name, auto& value) {
  509. VERIFY(value.is_object());
  510. if (value.as_object().has("max-values"sv)) {
  511. auto max_values = value.as_object().get("max-values"sv);
  512. VERIFY(max_values.has_value() && max_values->is_number() && !max_values->is_double());
  513. auto property_generator = generator.fork();
  514. property_generator.set("name:titlecase", title_casify(name));
  515. property_generator.set("max_values", max_values->to_deprecated_string());
  516. property_generator.append(R"~~~(
  517. case PropertyID::@name:titlecase@:
  518. return @max_values@;
  519. )~~~");
  520. }
  521. });
  522. generator.append(R"~~~(
  523. default:
  524. return 1;
  525. }
  526. }
  527. } // namespace Web::CSS
  528. )~~~");
  529. TRY(file.write_until_depleted(generator.as_string_view().bytes()));
  530. return {};
  531. }