GenerateCSSMathFunctions.cpp 17 KB

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