Generate_CSS_PropertyID_cpp.cpp 14 KB

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