Generate_CSS_PropertyID_cpp.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "GeneratorUtil.h"
  8. #include <AK/Array.h>
  9. #include <AK/SourceGenerator.h>
  10. #include <AK/StringBuilder.h>
  11. #include <LibMain/Main.h>
  12. ErrorOr<int> serenity_main(Main::Arguments arguments)
  13. {
  14. if (arguments.argc != 2) {
  15. warnln("usage: {} <path/to/CSS/Properties.json>", arguments.strings[0]);
  16. return 1;
  17. }
  18. auto json = TRY(read_entire_file_as_json(arguments.strings[1]));
  19. VERIFY(json.is_object());
  20. auto& properties = json.as_object();
  21. StringBuilder builder;
  22. SourceGenerator generator { builder };
  23. generator.append(R"~~~(
  24. #include <AK/Assertions.h>
  25. #include <LibWeb/CSS/Parser/Parser.h>
  26. #include <LibWeb/CSS/PropertyID.h>
  27. #include <LibWeb/CSS/StyleValue.h>
  28. namespace Web::CSS {
  29. PropertyID property_id_from_camel_case_string(StringView string)
  30. {
  31. )~~~");
  32. properties.for_each_member([&](auto& name, auto& value) {
  33. VERIFY(value.is_object());
  34. auto member_generator = generator.fork();
  35. member_generator.set("name", name);
  36. member_generator.set("name:titlecase", title_casify(name));
  37. member_generator.set("name:camelcase", camel_casify(name));
  38. member_generator.append(R"~~~(
  39. if (string.equals_ignoring_case("@name:camelcase@"sv))
  40. return PropertyID::@name:titlecase@;
  41. )~~~");
  42. });
  43. generator.append(R"~~~(
  44. return PropertyID::Invalid;
  45. }
  46. PropertyID property_id_from_string(StringView string)
  47. {
  48. )~~~");
  49. properties.for_each_member([&](auto& name, auto& value) {
  50. VERIFY(value.is_object());
  51. auto member_generator = generator.fork();
  52. member_generator.set("name", name);
  53. member_generator.set("name:titlecase", title_casify(name));
  54. member_generator.append(R"~~~(
  55. if (string.equals_ignoring_case("@name@"))
  56. return PropertyID::@name:titlecase@;
  57. )~~~");
  58. });
  59. generator.append(R"~~~(
  60. return PropertyID::Invalid;
  61. }
  62. const char* string_from_property_id(PropertyID property_id) {
  63. switch (property_id) {
  64. )~~~");
  65. properties.for_each_member([&](auto& name, auto& value) {
  66. VERIFY(value.is_object());
  67. auto member_generator = generator.fork();
  68. member_generator.set("name", name);
  69. member_generator.set("name:titlecase", title_casify(name));
  70. member_generator.append(R"~~~(
  71. case PropertyID::@name:titlecase@:
  72. return "@name@";
  73. )~~~");
  74. });
  75. generator.append(R"~~~(
  76. default:
  77. return "(invalid CSS::PropertyID)";
  78. }
  79. }
  80. bool is_inherited_property(PropertyID property_id)
  81. {
  82. switch (property_id) {
  83. )~~~");
  84. properties.for_each_member([&](auto& name, auto& value) {
  85. VERIFY(value.is_object());
  86. bool inherited = false;
  87. if (value.as_object().has("inherited")) {
  88. auto& inherited_value = value.as_object().get("inherited");
  89. VERIFY(inherited_value.is_bool());
  90. inherited = inherited_value.as_bool();
  91. }
  92. if (inherited) {
  93. auto member_generator = generator.fork();
  94. member_generator.set("name:titlecase", title_casify(name));
  95. member_generator.append(R"~~~(
  96. case PropertyID::@name:titlecase@:
  97. return true;
  98. )~~~");
  99. }
  100. });
  101. generator.append(R"~~~(
  102. default:
  103. return false;
  104. }
  105. }
  106. bool property_affects_layout(PropertyID property_id)
  107. {
  108. switch (property_id) {
  109. )~~~");
  110. properties.for_each_member([&](auto& name, auto& value) {
  111. VERIFY(value.is_object());
  112. bool affects_layout = true;
  113. if (value.as_object().has("affects-layout"))
  114. affects_layout = value.as_object().get("affects-layout").to_bool();
  115. if (affects_layout) {
  116. auto member_generator = generator.fork();
  117. member_generator.set("name:titlecase", title_casify(name));
  118. member_generator.append(R"~~~(
  119. case PropertyID::@name:titlecase@:
  120. )~~~");
  121. }
  122. });
  123. generator.append(R"~~~(
  124. return true;
  125. default:
  126. return false;
  127. }
  128. }
  129. bool property_affects_stacking_context(PropertyID property_id)
  130. {
  131. switch (property_id) {
  132. )~~~");
  133. properties.for_each_member([&](auto& name, auto& value) {
  134. VERIFY(value.is_object());
  135. bool affects_stacking_context = false;
  136. if (value.as_object().has("affects-stacking-context"))
  137. affects_stacking_context = value.as_object().get("affects-stacking-context").to_bool();
  138. if (affects_stacking_context) {
  139. auto member_generator = generator.fork();
  140. member_generator.set("name:titlecase", title_casify(name));
  141. member_generator.append(R"~~~(
  142. case PropertyID::@name:titlecase@:
  143. )~~~");
  144. }
  145. });
  146. generator.append(R"~~~(
  147. return true;
  148. default:
  149. return false;
  150. }
  151. }
  152. NonnullRefPtr<StyleValue> property_initial_value(PropertyID property_id)
  153. {
  154. static Array<RefPtr<StyleValue>, to_underlying(last_property_id) + 1> initial_values;
  155. static bool initialized = false;
  156. if (!initialized) {
  157. initialized = true;
  158. ParsingContext parsing_context;
  159. )~~~");
  160. // NOTE: Parsing a shorthand property requires that its longhands are already available here.
  161. // So, we do this in two passes: First longhands, then shorthands.
  162. // Probably we should build a dependency graph and then handle them in order, but this
  163. // works for now! :^)
  164. auto output_initial_value_code = [&](auto& name, auto& object) {
  165. if (!object.has("initial")) {
  166. dbgln("No initial value specified for property '{}'", name);
  167. VERIFY_NOT_REACHED();
  168. }
  169. auto& initial_value = object.get("initial");
  170. VERIFY(initial_value.is_string());
  171. auto initial_value_string = initial_value.as_string();
  172. auto member_generator = generator.fork();
  173. member_generator.set("name:titlecase", title_casify(name));
  174. member_generator.set("initial_value_string", initial_value_string);
  175. member_generator.append(R"~~~(
  176. {
  177. auto parsed_value = Parser(parsing_context, "@initial_value_string@").parse_as_css_value(PropertyID::@name:titlecase@);
  178. VERIFY(!parsed_value.is_null());
  179. initial_values[to_underlying(PropertyID::@name:titlecase@)] = parsed_value.release_nonnull();
  180. }
  181. )~~~");
  182. };
  183. properties.for_each_member([&](auto& name, auto& value) {
  184. VERIFY(value.is_object());
  185. if (value.as_object().has("longhands"))
  186. return;
  187. output_initial_value_code(name, value.as_object());
  188. });
  189. properties.for_each_member([&](auto& name, auto& value) {
  190. VERIFY(value.is_object());
  191. if (!value.as_object().has("longhands"))
  192. return;
  193. output_initial_value_code(name, value.as_object());
  194. });
  195. generator.append(R"~~~(
  196. }
  197. return *initial_values[to_underlying(property_id)];
  198. }
  199. bool property_has_quirk(PropertyID property_id, Quirk quirk)
  200. {
  201. switch (property_id) {
  202. )~~~");
  203. properties.for_each_member([&](auto& name, auto& value) {
  204. VERIFY(value.is_object());
  205. if (value.as_object().has("quirks")) {
  206. auto& quirks_value = value.as_object().get("quirks");
  207. VERIFY(quirks_value.is_array());
  208. auto& quirks = quirks_value.as_array();
  209. if (!quirks.is_empty()) {
  210. auto property_generator = generator.fork();
  211. property_generator.set("name:titlecase", title_casify(name));
  212. property_generator.append(R"~~~(
  213. case PropertyID::@name:titlecase@: {
  214. switch (quirk) {
  215. )~~~");
  216. for (auto& quirk : quirks.values()) {
  217. VERIFY(quirk.is_string());
  218. auto quirk_generator = property_generator.fork();
  219. quirk_generator.set("quirk:titlecase", title_casify(quirk.as_string()));
  220. quirk_generator.append(R"~~~(
  221. case Quirk::@quirk:titlecase@:
  222. return true;
  223. )~~~");
  224. }
  225. property_generator.append(R"~~~(
  226. default:
  227. return false;
  228. }
  229. }
  230. )~~~");
  231. }
  232. }
  233. });
  234. generator.append(R"~~~(
  235. default:
  236. return false;
  237. }
  238. }
  239. bool property_accepts_value(PropertyID property_id, StyleValue& style_value)
  240. {
  241. if (style_value.is_builtin())
  242. return true;
  243. switch (property_id) {
  244. )~~~");
  245. properties.for_each_member([&](auto& name, auto& value) {
  246. VERIFY(value.is_object());
  247. auto& object = value.as_object();
  248. bool has_valid_types = object.has("valid-types");
  249. auto has_valid_identifiers = object.has("valid-identifiers");
  250. if (has_valid_types || has_valid_identifiers) {
  251. auto property_generator = generator.fork();
  252. property_generator.set("name:titlecase", title_casify(name));
  253. property_generator.append(R"~~~(
  254. case PropertyID::@name:titlecase@: {
  255. )~~~");
  256. auto output_numeric_value_check = [](SourceGenerator& generator, StringView type_check_function, StringView value_getter, Span<StringView> resolved_type_names, StringView min_value, StringView max_value) {
  257. auto test_generator = generator.fork();
  258. test_generator.set("type_check_function", type_check_function);
  259. test_generator.set("value_getter", value_getter);
  260. test_generator.append(R"~~~(
  261. if ((style_value.@type_check_function@())~~~");
  262. if (!min_value.is_empty() && min_value != "-∞") {
  263. test_generator.set("minvalue", min_value);
  264. test_generator.append(" && (style_value.@value_getter@ >= @minvalue@)");
  265. }
  266. if (!max_value.is_empty() && max_value != "∞") {
  267. test_generator.set("maxvalue", max_value);
  268. test_generator.append(" && (style_value.@value_getter@ <= @maxvalue@)");
  269. }
  270. test_generator.append(")");
  271. if (!resolved_type_names.is_empty()) {
  272. test_generator.append(R"~~~(
  273. || (style_value.is_calculated() && ()~~~");
  274. bool first = true;
  275. for (auto& type_name : resolved_type_names) {
  276. test_generator.set("resolved_type_name", type_name);
  277. if (!first)
  278. test_generator.append(" || ");
  279. test_generator.append("style_value.as_calculated().resolved_type() == CalculatedStyleValue::ResolvedType::@resolved_type_name@");
  280. first = false;
  281. }
  282. test_generator.append("))");
  283. }
  284. test_generator.append(R"~~~() {
  285. return true;
  286. }
  287. )~~~");
  288. };
  289. if (has_valid_types) {
  290. auto valid_types_value = object.get("valid-types");
  291. VERIFY(valid_types_value.is_array());
  292. auto valid_types = valid_types_value.as_array();
  293. if (!valid_types.is_empty()) {
  294. for (auto& type : valid_types.values()) {
  295. VERIFY(type.is_string());
  296. auto type_parts = type.as_string().split_view(' ');
  297. auto type_name = type_parts.first();
  298. auto type_args = type_parts.size() > 1 ? type_parts[1] : ""sv;
  299. StringView min_value;
  300. StringView max_value;
  301. if (!type_args.is_empty()) {
  302. VERIFY(type_args.starts_with('[') && type_args.ends_with(']'));
  303. auto comma_index = type_args.find(',').value();
  304. min_value = type_args.substring_view(1, comma_index - 1);
  305. max_value = type_args.substring_view(comma_index + 1, type_args.length() - comma_index - 2);
  306. }
  307. if (type_name == "angle") {
  308. output_numeric_value_check(property_generator, "is_angle", "as_angle().angle().to_degrees()", Array { "Angle"sv }, min_value, max_value);
  309. } else if (type_name == "color") {
  310. property_generator.append(R"~~~(
  311. if (style_value.has_color())
  312. return true;
  313. )~~~");
  314. } else if (type_name == "frequency") {
  315. output_numeric_value_check(property_generator, "is_frequency", "as_frequency().frequency().to_hertz()", Array { "Frequency"sv }, min_value, max_value);
  316. } else if (type_name == "image") {
  317. property_generator.append(R"~~~(
  318. if (style_value.is_image())
  319. return true;
  320. )~~~");
  321. } else if (type_name == "integer") {
  322. output_numeric_value_check(property_generator, "has_integer", "to_integer()", Array { "Integer"sv }, min_value, max_value);
  323. } else if (type_name == "length") {
  324. output_numeric_value_check(property_generator, "has_length", "to_length().raw_value()", Array { "Length"sv }, min_value, max_value);
  325. } else if (type_name == "number") {
  326. output_numeric_value_check(property_generator, "has_number", "to_number()", Array { "Integer"sv, "Number"sv }, min_value, max_value);
  327. } else if (type_name == "percentage") {
  328. output_numeric_value_check(property_generator, "is_percentage", "as_percentage().percentage().value()", Array { "Percentage"sv }, min_value, max_value);
  329. } else if (type_name == "resolution") {
  330. output_numeric_value_check(property_generator, "is_resolution", "as_resolution().resolution().to_dots_per_pixel()", Array<StringView, 0> {}, min_value, max_value);
  331. } else if (type_name == "string") {
  332. property_generator.append(R"~~~(
  333. if (style_value.is_string())
  334. return true;
  335. )~~~");
  336. } else if (type_name == "time") {
  337. output_numeric_value_check(property_generator, "is_time", "as_time().time().to_seconds()", Array { "Time"sv }, min_value, max_value);
  338. } else if (type_name == "url") {
  339. // FIXME: Handle urls!
  340. } else {
  341. warnln("Unrecognized valid-type name: '{}'", type_name);
  342. VERIFY_NOT_REACHED();
  343. }
  344. }
  345. }
  346. }
  347. if (has_valid_identifiers) {
  348. auto valid_identifiers_value = object.get("valid-identifiers");
  349. VERIFY(valid_identifiers_value.is_array());
  350. auto valid_identifiers = valid_identifiers_value.as_array();
  351. if (!valid_identifiers.is_empty()) {
  352. property_generator.append(R"~~~(
  353. switch (style_value.to_identifier()) {
  354. )~~~");
  355. for (auto& identifier : valid_identifiers.values()) {
  356. VERIFY(identifier.is_string());
  357. auto identifier_generator = generator.fork();
  358. identifier_generator.set("identifier:titlecase", title_casify(identifier.as_string()));
  359. identifier_generator.append(R"~~~(
  360. case ValueID::@identifier:titlecase@:
  361. )~~~");
  362. }
  363. property_generator.append(R"~~~(
  364. return true;
  365. default:
  366. break;
  367. }
  368. )~~~");
  369. }
  370. }
  371. generator.append(R"~~~(
  372. return false;
  373. }
  374. )~~~");
  375. }
  376. });
  377. generator.append(R"~~~(
  378. default:
  379. return true;
  380. }
  381. }
  382. size_t property_maximum_value_count(PropertyID property_id)
  383. {
  384. switch (property_id) {
  385. )~~~");
  386. properties.for_each_member([&](auto& name, auto& value) {
  387. VERIFY(value.is_object());
  388. if (value.as_object().has("max-values")) {
  389. auto max_values = value.as_object().get("max-values");
  390. VERIFY(max_values.is_number() && !max_values.is_double());
  391. auto property_generator = generator.fork();
  392. property_generator.set("name:titlecase", title_casify(name));
  393. property_generator.set("max_values", max_values.to_string());
  394. property_generator.append(R"~~~(
  395. case PropertyID::@name:titlecase@:
  396. return @max_values@;
  397. )~~~");
  398. }
  399. });
  400. generator.append(R"~~~(
  401. default:
  402. return 1;
  403. }
  404. }
  405. } // namespace Web::CSS
  406. )~~~");
  407. outln("{}", generator.as_string_view());
  408. return 0;
  409. }