GenerateCSSMathFunctions.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. /*
  2. * Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "GeneratorUtil.h"
  7. #include <AK/SourceGenerator.h>
  8. #include <AK/StringBuilder.h>
  9. #include <LibCore/ArgsParser.h>
  10. #include <LibMain/Main.h>
  11. ErrorOr<void> generate_header_file(JsonObject& functions_data, Core::File& file);
  12. ErrorOr<void> generate_implementation_file(JsonObject& functions_data, Core::File& file);
  13. ErrorOr<int> serenity_main(Main::Arguments arguments)
  14. {
  15. StringView generated_header_path;
  16. StringView generated_implementation_path;
  17. StringView identifiers_json_path;
  18. Core::ArgsParser args_parser;
  19. args_parser.add_option(generated_header_path, "Path to the MathFunctions header file to generate", "generated-header-path", 'h', "generated-header-path");
  20. args_parser.add_option(generated_implementation_path, "Path to the MathFunctions implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
  21. args_parser.add_option(identifiers_json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path");
  22. args_parser.parse(arguments);
  23. auto json = TRY(read_entire_file_as_json(identifiers_json_path));
  24. VERIFY(json.is_object());
  25. auto math_functions_data = json.as_object();
  26. auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write));
  27. auto generated_implementation_file = TRY(Core::File::open(generated_implementation_path, Core::File::OpenMode::Write));
  28. TRY(generate_header_file(math_functions_data, *generated_header_file));
  29. TRY(generate_implementation_file(math_functions_data, *generated_implementation_file));
  30. return 0;
  31. }
  32. ErrorOr<void> generate_header_file(JsonObject& functions_data, Core::File& file)
  33. {
  34. StringBuilder builder;
  35. SourceGenerator generator { builder };
  36. generator.append(R"~~~(
  37. // This file is generated by GenerateCSSMathFunctions.cpp
  38. #pragma once
  39. namespace Web::CSS {
  40. enum class MathFunction {
  41. )~~~");
  42. functions_data.for_each_member([&](auto& name, auto&) {
  43. auto member_generator = generator.fork();
  44. member_generator.set("name:titlecase", title_casify(name));
  45. member_generator.appendln(" @name:titlecase@,"sv);
  46. });
  47. generator.append(R"~~~(
  48. };
  49. }
  50. )~~~");
  51. TRY(file.write_until_depleted(generator.as_string_view().bytes()));
  52. return {};
  53. }
  54. String generate_calculation_type_check(StringView calculation_variable_name, StringView parameter_types)
  55. {
  56. StringBuilder builder;
  57. auto allowed_types = parameter_types.split_view('|');
  58. bool first_type_check = true;
  59. for (auto const& allowed_type_name : allowed_types) {
  60. if (!first_type_check)
  61. builder.append(" || "sv);
  62. first_type_check = false;
  63. if (allowed_type_name == "<angle>"sv) {
  64. builder.appendff("{}.{}", calculation_variable_name, "matches_angle()"sv);
  65. } else if (allowed_type_name == "<dimension>"sv) {
  66. builder.appendff("{}.{}", calculation_variable_name, "matches_dimension()"sv);
  67. } else if (allowed_type_name == "<flex>"sv) {
  68. builder.appendff("{}.{}", calculation_variable_name, "matches_flex()"sv);
  69. } else if (allowed_type_name == "<frequency>"sv) {
  70. builder.appendff("{}.{}", calculation_variable_name, "matches_frequency()"sv);
  71. } else if (allowed_type_name == "<length>"sv) {
  72. builder.appendff("{}.{}", calculation_variable_name, "matches_length()"sv);
  73. } else if (allowed_type_name == "<number>"sv) {
  74. builder.appendff("{}.{}", calculation_variable_name, "matches_number()"sv);
  75. } else if (allowed_type_name == "<percentage>"sv) {
  76. builder.appendff("{}.{}", calculation_variable_name, "matches_percentage()"sv);
  77. } else if (allowed_type_name == "<resolution>"sv) {
  78. builder.appendff("{}.{}", calculation_variable_name, "matches_resolution()"sv);
  79. } else if (allowed_type_name == "<time>"sv) {
  80. builder.appendff("{}.{}", calculation_variable_name, "matches_time()"sv);
  81. } else {
  82. dbgln("I don't know what '{}' is!", allowed_type_name);
  83. VERIFY_NOT_REACHED();
  84. }
  85. }
  86. return MUST(builder.to_string());
  87. }
  88. ErrorOr<void> generate_implementation_file(JsonObject& functions_data, Core::File& file)
  89. {
  90. StringBuilder builder;
  91. SourceGenerator generator { builder };
  92. generator.append(R"~~~(
  93. // This file is generated by GenerateCSSMathFunctions.cpp
  94. #include <AK/Debug.h>
  95. #include <LibWeb/CSS/MathFunctions.h>
  96. #include <LibWeb/CSS/Parser/Parser.h>
  97. #include <LibWeb/CSS/Enums.h>
  98. #include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
  99. namespace Web::CSS::Parser {
  100. static Optional<RoundingStrategy> parse_rounding_strategy(Vector<ComponentValue> const& tokens)
  101. {
  102. auto stream = TokenStream { tokens };
  103. stream.skip_whitespace();
  104. if (!stream.has_next_token())
  105. return {};
  106. auto& ident = stream.next_token();
  107. if (!ident.is(Token::Type::Ident))
  108. return {};
  109. stream.skip_whitespace();
  110. if (stream.has_next_token())
  111. return {};
  112. auto maybe_identifier = value_id_from_string(ident.token().ident());
  113. if (!maybe_identifier.has_value())
  114. return {};
  115. return value_id_to_rounding_strategy(maybe_identifier.value());
  116. }
  117. OwnPtr<CalculationNode> Parser::parse_math_function(PropertyID property_id, Function const& function)
  118. {
  119. TokenStream stream { function.values() };
  120. auto arguments = parse_a_comma_separated_list_of_component_values(stream);
  121. )~~~");
  122. functions_data.for_each_member([&](auto& name, JsonValue const& value) -> void {
  123. auto& function_data = value.as_object();
  124. auto& parameters = function_data.get_array("parameters"sv).value();
  125. auto function_generator = generator.fork();
  126. function_generator.set("name:lowercase", name);
  127. function_generator.set("name:titlecase", title_casify(name));
  128. function_generator.appendln(" if (function.name().equals_ignoring_ascii_case(\"@name:lowercase@\"sv)) {");
  129. if (function_data.get_bool("is-variadic"sv).value_or(false)) {
  130. // Variadic function
  131. function_generator.append(R"~~~(
  132. CSSNumericType determined_argument_type;
  133. Vector<NonnullOwnPtr<CalculationNode>> parsed_arguments;
  134. parsed_arguments.ensure_capacity(arguments.size());
  135. for (auto& argument : arguments) {
  136. auto calculation_node = parse_a_calculation(argument);
  137. if (!calculation_node) {
  138. dbgln_if(CSS_PARSER_DEBUG, "@name:lowercase@() argument #{} is not a valid calculation", parsed_arguments.size());
  139. return nullptr;
  140. }
  141. auto maybe_argument_type = calculation_node->determine_type(m_context.current_property_id());
  142. if (!maybe_argument_type.has_value()) {
  143. dbgln_if(CSS_PARSER_DEBUG, "@name:lowercase@() argument #{} couldn't determine its type", parsed_arguments.size());
  144. return nullptr;
  145. }
  146. auto argument_type = maybe_argument_type.release_value();
  147. )~~~");
  148. // Generate some type checks
  149. VERIFY(parameters.size() == 1);
  150. auto& parameter_data = parameters[0].as_object();
  151. auto parameter_type_string = parameter_data.get_deprecated_string("type"sv).value();
  152. function_generator.set("type_check", generate_calculation_type_check("argument_type"sv, parameter_type_string));
  153. function_generator.append(R"~~~(
  154. if (!(@type_check@)) {
  155. dbgln_if(CSS_PARSER_DEBUG, "@name:lowercase@() argument #{} type ({}) is not an accepted type", parsed_arguments.size(), MUST(argument_type.dump()));
  156. return nullptr;
  157. }
  158. if (parsed_arguments.is_empty()) {
  159. determined_argument_type = move(argument_type);
  160. } else {
  161. if (determined_argument_type != argument_type) {
  162. dbgln_if(CSS_PARSER_DEBUG, "@name:lowercase@() argument #{} type ({}) doesn't match type of previous arguments ({})", parsed_arguments.size(), MUST(argument_type.dump()), MUST(determined_argument_type.dump()));
  163. return nullptr;
  164. }
  165. }
  166. parsed_arguments.append(calculation_node.release_nonnull());
  167. }
  168. return @name:titlecase@CalculationNode::create(move(parsed_arguments));
  169. }
  170. )~~~");
  171. } else {
  172. // Function with specified parameters.
  173. size_t min_argument_count = 0;
  174. size_t max_argument_count = parameters.size();
  175. parameters.for_each([&](JsonValue const& parameter_value) {
  176. auto& parameter = parameter_value.as_object();
  177. if (parameter.get_bool("required"sv) == true)
  178. min_argument_count++;
  179. });
  180. function_generator.set("min_argument_count", MUST(String::number(min_argument_count)));
  181. function_generator.set("max_argument_count", MUST(String::number(max_argument_count)));
  182. function_generator.append(R"~~~(
  183. if (arguments.size() < @min_argument_count@ || arguments.size() > @max_argument_count@) {
  184. dbgln_if(CSS_PARSER_DEBUG, "@name:lowercase@() has wrong number of arguments {}, expected between @min_argument_count@ and @max_argument_count@ inclusive", arguments.size());
  185. return nullptr;
  186. }
  187. size_t argument_index = 0;
  188. [[maybe_unused]] CSSNumericType previous_argument_type;
  189. )~~~");
  190. size_t parameter_index = 0;
  191. StringView previous_parameter_type_string;
  192. parameters.for_each([&](JsonValue const& parameter_value) {
  193. auto& parameter = parameter_value.as_object();
  194. auto parameter_type_string = parameter.get_deprecated_string("type"sv).value();
  195. auto parameter_required = parameter.get_bool("required"sv).value();
  196. auto parameter_generator = function_generator.fork();
  197. parameter_generator.set("parameter_name", parameter.get_deprecated_string("name"sv).value());
  198. parameter_generator.set("parameter_index", MUST(String::number(parameter_index)));
  199. bool parameter_is_calculation;
  200. if (parameter_type_string == "<rounding-strategy>") {
  201. parameter_is_calculation = false;
  202. parameter_generator.set("parameter_type", "RoundingStrategy"_string);
  203. parameter_generator.set("parse_function", "parse_rounding_strategy(arguments[argument_index])"_string);
  204. parameter_generator.set("check_function", ".has_value()"_string);
  205. parameter_generator.set("release_function", ".release_value()"_string);
  206. if (auto default_value = parameter.get_deprecated_string("default"sv); default_value.has_value()) {
  207. parameter_generator.set("parameter_default", MUST(String::formatted(" = RoundingStrategy::{}", title_casify(default_value.value()))));
  208. } else {
  209. parameter_generator.set("parameter_default", ""_string);
  210. }
  211. } else {
  212. // NOTE: This assumes everything not handled above is a calculation node of some kind.
  213. parameter_is_calculation = true;
  214. parameter_generator.set("parameter_type", "OwnPtr<CalculationNode>"_string);
  215. parameter_generator.set("parse_function", "parse_a_calculation(arguments[argument_index])"_string);
  216. parameter_generator.set("check_function", " != nullptr"_string);
  217. parameter_generator.set("release_function", ".release_nonnull()"_string);
  218. // NOTE: We have exactly one default value in the data right now, and it's a `<calc-constant>`,
  219. // so that's all we handle.
  220. if (auto default_value = parameter.get_deprecated_string("default"sv); default_value.has_value()) {
  221. parameter_generator.set("parameter_default", MUST(String::formatted(" = ConstantCalculationNode::create(CalculationNode::constant_type_from_string(\"{}\"sv).value())", default_value.value())));
  222. } else {
  223. parameter_generator.set("parameter_default", ""_string);
  224. }
  225. }
  226. parameter_generator.append(R"~~~(
  227. @parameter_type@ parameter_@parameter_index@@parameter_default@;
  228. )~~~");
  229. if (parameter_required) {
  230. parameter_generator.append(R"~~~(
  231. if (argument_index >= arguments.size()) {
  232. dbgln_if(CSS_PARSER_DEBUG, "@name:lowercase@() missing required argument '@parameter_name@'");
  233. return nullptr;
  234. } else {
  235. )~~~");
  236. } else {
  237. parameter_generator.append(R"~~~(
  238. if (argument_index < arguments.size()) {
  239. )~~~");
  240. }
  241. parameter_generator.append(R"~~~(
  242. auto maybe_parsed_argument_@parameter_index@ = @parse_function@;
  243. if (maybe_parsed_argument_@parameter_index@@check_function@) {
  244. parameter_@parameter_index@ = maybe_parsed_argument_@parameter_index@@release_function@;
  245. argument_index++;
  246. )~~~");
  247. if (parameter_required) {
  248. parameter_generator.append(R"~~~(
  249. } else {
  250. dbgln_if(CSS_PARSER_DEBUG, "@name:lowercase@() required argument '@parameter_name@' failed to parse");
  251. return nullptr;
  252. )~~~");
  253. }
  254. parameter_generator.append(R"~~~(
  255. }
  256. }
  257. )~~~");
  258. if (parameter_is_calculation) {
  259. auto parameter_type_variable = MUST(String::formatted("argument_type_{}", parameter_index));
  260. parameter_generator.set("type_check", generate_calculation_type_check(parameter_type_variable, parameter_type_string));
  261. parameter_generator.append(R"~~~(
  262. auto maybe_argument_type_@parameter_index@ = parameter_@parameter_index@->determine_type(property_id);
  263. if (!maybe_argument_type_@parameter_index@.has_value()) {
  264. dbgln_if(CSS_PARSER_DEBUG, "@name:lowercase@() argument '@parameter_name@' couldn't determine its type");
  265. return nullptr;
  266. }
  267. auto argument_type_@parameter_index@ = maybe_argument_type_@parameter_index@.release_value();
  268. if (!(@type_check@)) {
  269. dbgln_if(CSS_PARSER_DEBUG, "@name:lowercase@() argument '@parameter_name@' type ({}) is not an accepted type", MUST(argument_type_@parameter_index@.dump()));
  270. return nullptr;
  271. }
  272. )~~~");
  273. // NOTE: In all current cases, the parameters that take the same types must resolve to the same CSSNumericType.
  274. // This is a bit of a hack, but serves our needs for now.
  275. if (previous_parameter_type_string == parameter_type_string) {
  276. parameter_generator.append(R"~~~(
  277. if (argument_type_@parameter_index@ != previous_argument_type) {
  278. dbgln_if(CSS_PARSER_DEBUG, "@name:lowercase@() argument '@parameter_name@' type ({}) doesn't match type of previous arguments ({})", MUST(argument_type_@parameter_index@.dump()), MUST(previous_argument_type.dump()));
  279. return nullptr;
  280. }
  281. )~~~");
  282. }
  283. parameter_generator.append(R"~~~(
  284. previous_argument_type = argument_type_@parameter_index@;
  285. )~~~");
  286. }
  287. parameter_index++;
  288. previous_parameter_type_string = parameter_type_string;
  289. });
  290. // Generate the call to the constructor
  291. function_generator.append(" return @name:titlecase@CalculationNode::create("sv);
  292. parameter_index = 0;
  293. parameters.for_each([&](JsonValue const& parameter_value) {
  294. auto& parameter = parameter_value.as_object();
  295. auto parameter_type_string = parameter.get_deprecated_string("type"sv).value();
  296. auto parameter_generator = function_generator.fork();
  297. parameter_generator.set("parameter_index"sv, MUST(String::number(parameter_index)));
  298. if (parameter_type_string == "<rounding-strategy>"sv) {
  299. parameter_generator.set("release_value"sv, ""_string);
  300. } else {
  301. // NOTE: This assumes everything not handled above is a calculation node of some kind.
  302. parameter_generator.set("release_value"sv, ".release_nonnull()"_string);
  303. }
  304. if (parameter_index == 0) {
  305. parameter_generator.append("parameter_@parameter_index@@release_value@"sv);
  306. } else {
  307. parameter_generator.append(", parameter_@parameter_index@@release_value@"sv);
  308. }
  309. parameter_index++;
  310. });
  311. function_generator.append(R"~~~();
  312. }
  313. )~~~");
  314. }
  315. });
  316. generator.append(R"~~~(
  317. return nullptr;
  318. }
  319. }
  320. )~~~");
  321. TRY(file.write_until_depleted(generator.as_string_view().bytes()));
  322. return {};
  323. }