GenerateUnicodePluralRules.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /*
  2. * Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "GeneratorUtil.h"
  7. #include <AK/Format.h>
  8. #include <AK/JsonObject.h>
  9. #include <AK/JsonParser.h>
  10. #include <AK/JsonValue.h>
  11. #include <AK/LexicalPath.h>
  12. #include <AK/SourceGenerator.h>
  13. #include <AK/String.h>
  14. #include <AK/StringBuilder.h>
  15. #include <AK/Variant.h>
  16. #include <LibCore/ArgsParser.h>
  17. #include <LibCore/File.h>
  18. #include <LibCore/Stream.h>
  19. #include <LibUnicode/PluralRules.h>
  20. using StringIndexType = u16;
  21. static String format_identifier(StringView owner, String identifier)
  22. {
  23. identifier = identifier.replace("-"sv, "_"sv, ReplaceMode::All);
  24. if (all_of(identifier, is_ascii_digit))
  25. return String::formatted("{}_{}", owner[0], identifier);
  26. if (is_ascii_lower_alpha(identifier[0]))
  27. return String::formatted("{:c}{}", to_ascii_uppercase(identifier[0]), identifier.substring_view(1));
  28. return identifier;
  29. }
  30. struct Relation {
  31. using Range = Array<u32, 2>;
  32. using Comparator = Variant<u32, Range>;
  33. enum class Type {
  34. Equality,
  35. Inequality,
  36. };
  37. String const& modulus_variable_name() const
  38. {
  39. VERIFY(modulus.has_value());
  40. if (!cached_modulus_variable_name.has_value())
  41. cached_modulus_variable_name = String::formatted("mod_{}_{}", symbol, *modulus);
  42. return *cached_modulus_variable_name;
  43. }
  44. String const& exponential_variable_name() const
  45. {
  46. if (!cached_exponential_variable_name.has_value())
  47. cached_exponential_variable_name = String::formatted("exp_{}", symbol);
  48. return *cached_exponential_variable_name;
  49. }
  50. void generate_relation(SourceGenerator& generator) const
  51. {
  52. auto append_variable_name = [&]() {
  53. if (modulus.has_value())
  54. generator.append(modulus_variable_name());
  55. else if (symbol == 'e' || symbol == 'c')
  56. generator.append(exponential_variable_name());
  57. else
  58. generator.append(String::formatted("ops.{}", Unicode::PluralOperands::symbol_to_variable_name(symbol)));
  59. };
  60. auto append_value = [&](u32 value) {
  61. append_variable_name();
  62. generator.append(" == "sv);
  63. generator.append(String::number(value));
  64. };
  65. auto append_range = [&](auto const& range) {
  66. // This check avoids generating "0 <= unsigned_value", which is always true.
  67. if (range[0] != 0 || Unicode::PluralOperands::symbol_requires_floating_point_modulus(symbol)) {
  68. generator.append(String::formatted("{} <= ", range[0]));
  69. append_variable_name();
  70. generator.append(" && "sv);
  71. }
  72. append_variable_name();
  73. generator.append(String::formatted(" <= {}", range[1]));
  74. };
  75. if (type == Type::Inequality)
  76. generator.append("!"sv);
  77. generator.append("("sv);
  78. bool first = true;
  79. for (auto const& comparator : comparators) {
  80. generator.append(first ? "("sv : " || ("sv);
  81. comparator.visit(
  82. [&](u32 value) { append_value(value); },
  83. [&](Range const& range) { append_range(range); });
  84. generator.append(")"sv);
  85. first = false;
  86. }
  87. generator.append(")"sv);
  88. }
  89. void generate_precomputed_variables(SourceGenerator& generator, HashTable<String>& generated_variables) const
  90. {
  91. // FIXME: How do we handle the exponential symbols? They seem unused by ECMA-402.
  92. if (symbol == 'e' || symbol == 'c') {
  93. if (auto variable = exponential_variable_name(); !generated_variables.contains(variable)) {
  94. generated_variables.set(variable);
  95. generator.set("variable"sv, move(variable));
  96. generator.append(R"~~~(
  97. auto @variable@ = 0;)~~~");
  98. }
  99. }
  100. if (!modulus.has_value())
  101. return;
  102. auto variable = modulus_variable_name();
  103. if (generated_variables.contains(variable))
  104. return;
  105. generated_variables.set(variable);
  106. generator.set("variable"sv, move(variable));
  107. generator.set("operand"sv, Unicode::PluralOperands::symbol_to_variable_name(symbol));
  108. generator.set("modulus"sv, String::number(*modulus));
  109. if (Unicode::PluralOperands::symbol_requires_floating_point_modulus(symbol)) {
  110. generator.append(R"~~~(
  111. auto @variable@ = fmod(ops.@operand@, @modulus@);)~~~");
  112. } else {
  113. generator.append(R"~~~(
  114. auto @variable@ = ops.@operand@ % @modulus@;)~~~");
  115. }
  116. }
  117. Type type;
  118. char symbol { 0 };
  119. Optional<u32> modulus;
  120. Vector<Comparator> comparators;
  121. private:
  122. mutable Optional<String> cached_modulus_variable_name;
  123. mutable Optional<String> cached_exponential_variable_name;
  124. };
  125. struct Condition {
  126. void generate_condition(SourceGenerator& generator) const
  127. {
  128. for (size_t i = 0; i < relations.size(); ++i) {
  129. if (i > 0)
  130. generator.append(" || "sv);
  131. auto const& conjunctions = relations[i];
  132. if (conjunctions.size() > 1)
  133. generator.append("("sv);
  134. for (size_t j = 0; j < conjunctions.size(); ++j) {
  135. if (j > 0)
  136. generator.append(" && "sv);
  137. conjunctions[j].generate_relation(generator);
  138. }
  139. if (conjunctions.size() > 1)
  140. generator.append(")"sv);
  141. }
  142. }
  143. void generate_precomputed_variables(SourceGenerator& generator, HashTable<String>& generated_variables) const
  144. {
  145. for (auto const& conjunctions : relations) {
  146. for (auto const& relation : conjunctions)
  147. relation.generate_precomputed_variables(generator, generated_variables);
  148. }
  149. }
  150. Vector<Vector<Relation>> relations;
  151. };
  152. struct Locale {
  153. static String generated_method_name(StringView form, StringView locale)
  154. {
  155. return String::formatted("{}_plurality_{}", form, format_identifier({}, locale));
  156. }
  157. HashMap<String, Condition>& rules_for_form(StringView form)
  158. {
  159. if (form == "cardinal")
  160. return cardinal_rules;
  161. if (form == "ordinal")
  162. return ordinal_rules;
  163. VERIFY_NOT_REACHED();
  164. }
  165. HashMap<String, Condition> cardinal_rules;
  166. HashMap<String, Condition> ordinal_rules;
  167. };
  168. struct UnicodeLocaleData {
  169. UniqueStringStorage<StringIndexType> unique_strings;
  170. HashMap<String, Locale> locales;
  171. };
  172. static Relation parse_relation(StringView relation)
  173. {
  174. static constexpr auto equality_operator = " = "sv;
  175. static constexpr auto inequality_operator = " != "sv;
  176. static constexpr auto modulus_operator = " % "sv;
  177. static constexpr auto range_operator = ".."sv;
  178. static constexpr auto set_operator = ',';
  179. Relation parsed;
  180. StringView lhs;
  181. StringView rhs;
  182. if (auto index = relation.find(equality_operator); index.has_value()) {
  183. parsed.type = Relation::Type::Equality;
  184. lhs = relation.substring_view(0, *index);
  185. rhs = relation.substring_view(*index + equality_operator.length());
  186. } else if (auto index = relation.find(inequality_operator); index.has_value()) {
  187. parsed.type = Relation::Type::Inequality;
  188. lhs = relation.substring_view(0, *index);
  189. rhs = relation.substring_view(*index + inequality_operator.length());
  190. } else {
  191. VERIFY_NOT_REACHED();
  192. }
  193. if (auto index = lhs.find(modulus_operator); index.has_value()) {
  194. auto symbol = lhs.substring_view(0, *index);
  195. VERIFY(symbol.length() == 1);
  196. auto modulus = lhs.substring_view(*index + modulus_operator.length()).to_uint();
  197. VERIFY(modulus.has_value());
  198. parsed.symbol = symbol[0];
  199. parsed.modulus = move(modulus);
  200. } else {
  201. VERIFY(lhs.length() == 1);
  202. parsed.symbol = lhs[0];
  203. }
  204. rhs.for_each_split_view(set_operator, false, [&](auto set) {
  205. if (auto index = set.find(range_operator); index.has_value()) {
  206. auto range_begin = set.substring_view(0, *index).to_uint();
  207. VERIFY(range_begin.has_value());
  208. auto range_end = set.substring_view(*index + range_operator.length()).to_uint();
  209. VERIFY(range_end.has_value());
  210. parsed.comparators.empend(Array { *range_begin, *range_end });
  211. } else {
  212. auto value = set.to_uint();
  213. VERIFY(value.has_value());
  214. parsed.comparators.empend(*value);
  215. }
  216. });
  217. return parsed;
  218. }
  219. // https://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax
  220. //
  221. // A very simplified view of a plural rule is:
  222. //
  223. // condition.* ([@integer|@decimal] sample)+
  224. //
  225. // The "sample" being series of integer or decimal values that fit the specified condition. The
  226. // condition may be one or more binary expressions, chained together with "and" or "or" operators.
  227. static void parse_condition(StringView category, StringView rule, HashMap<String, Condition>& rules)
  228. {
  229. static constexpr auto other_category = "other"sv;
  230. static constexpr auto disjunction_keyword = " or "sv;
  231. static constexpr auto conjunction_keyword = " and "sv;
  232. // We don't need the examples in the generated code, so we can drop them here.
  233. auto example_index = rule.find('@');
  234. VERIFY(example_index.has_value());
  235. auto condition = rule.substring_view(0, *example_index).trim_whitespace();
  236. // Our implementation does not generate rules for the "other" category. We simply return "other"
  237. // for values that do not match any rules. This will need to be revisited if this VERIFY fails.
  238. if (condition.is_empty()) {
  239. VERIFY(category == other_category);
  240. return;
  241. }
  242. auto& relation_list = rules.ensure(category);
  243. // The grammar for a condition (i.e. a chain of relations) is:
  244. //
  245. // condition = and_condition ('or' and_condition)*
  246. // and_condition = relation ('and' relation)*
  247. //
  248. // This affords some simplicity in that disjunctions are never embedded within a conjunction.
  249. condition.for_each_split_view(disjunction_keyword, false, [&](auto disjunction) {
  250. Vector<Relation> conjunctions;
  251. disjunction.for_each_split_view(conjunction_keyword, false, [&](auto relation) {
  252. conjunctions.append(parse_relation(relation));
  253. });
  254. relation_list.relations.append(move(conjunctions));
  255. });
  256. }
  257. static ErrorOr<void> parse_plural_rules(String core_supplemental_path, StringView file_name, UnicodeLocaleData& locale_data)
  258. {
  259. static constexpr auto form_prefix = "plurals-type-"sv;
  260. static constexpr auto rule_prefix = "pluralRule-count-"sv;
  261. LexicalPath plurals_path(move(core_supplemental_path));
  262. plurals_path = plurals_path.append(file_name);
  263. auto plurals = TRY(read_json_file(plurals_path.string()));
  264. auto const& supplemental_object = plurals.as_object().get("supplemental"sv);
  265. supplemental_object.as_object().for_each_member([&](auto const& key, auto const& plurals_object) {
  266. if (!key.starts_with(form_prefix))
  267. return;
  268. auto form = key.substring_view(form_prefix.length());
  269. plurals_object.as_object().for_each_member([&](auto const& loc, auto const& rules) {
  270. auto locale = locale_data.locales.get(loc);
  271. if (!locale.has_value())
  272. return;
  273. rules.as_object().for_each_member([&](auto const& key, auto const& condition) {
  274. VERIFY(key.starts_with(rule_prefix));
  275. auto category = key.substring_view(rule_prefix.length());
  276. parse_condition(category, condition.as_string(), locale->rules_for_form(form));
  277. });
  278. });
  279. });
  280. return {};
  281. }
  282. static ErrorOr<void> parse_all_locales(String core_path, String locale_names_path, UnicodeLocaleData& locale_data)
  283. {
  284. auto identity_iterator = TRY(path_to_dir_iterator(move(locale_names_path)));
  285. LexicalPath core_supplemental_path(move(core_path));
  286. core_supplemental_path = core_supplemental_path.append("supplemental"sv);
  287. VERIFY(Core::File::is_directory(core_supplemental_path.string()));
  288. auto remove_variants_from_path = [&](String path) -> ErrorOr<String> {
  289. auto parsed_locale = TRY(CanonicalLanguageID<StringIndexType>::parse(locale_data.unique_strings, LexicalPath::basename(path)));
  290. StringBuilder builder;
  291. builder.append(locale_data.unique_strings.get(parsed_locale.language));
  292. if (auto script = locale_data.unique_strings.get(parsed_locale.script); !script.is_empty())
  293. builder.appendff("-{}", script);
  294. if (auto region = locale_data.unique_strings.get(parsed_locale.region); !region.is_empty())
  295. builder.appendff("-{}", region);
  296. return builder.build();
  297. };
  298. while (identity_iterator.has_next()) {
  299. auto locale_path = TRY(next_path_from_dir_iterator(identity_iterator));
  300. auto language = TRY(remove_variants_from_path(locale_path));
  301. locale_data.locales.ensure(language);
  302. }
  303. TRY(parse_plural_rules(core_supplemental_path.string(), "plurals.json"sv, locale_data));
  304. TRY(parse_plural_rules(core_supplemental_path.string(), "ordinals.json"sv, locale_data));
  305. return {};
  306. }
  307. static ErrorOr<void> generate_unicode_locale_header(Core::Stream::BufferedFile& file, UnicodeLocaleData&)
  308. {
  309. StringBuilder builder;
  310. SourceGenerator generator { builder };
  311. generator.append(R"~~~(
  312. #include <AK/Types.h>
  313. #pragma once
  314. namespace Unicode {
  315. )~~~");
  316. generator.append(R"~~~(
  317. }
  318. )~~~");
  319. TRY(file.write(generator.as_string_view().bytes()));
  320. return {};
  321. }
  322. static ErrorOr<void> generate_unicode_locale_implementation(Core::Stream::BufferedFile& file, UnicodeLocaleData& locale_data)
  323. {
  324. StringBuilder builder;
  325. SourceGenerator generator { builder };
  326. auto locales = locale_data.locales.keys();
  327. quick_sort(locales);
  328. generator.append(R"~~~(
  329. #include <AK/Array.h>
  330. #include <LibUnicode/Locale.h>
  331. #include <LibUnicode/PluralRules.h>
  332. #include <LibUnicode/UnicodeLocale.h>
  333. #include <LibUnicode/UnicodePluralRules.h>
  334. #include <math.h>
  335. namespace Unicode {
  336. using PluralCategoryFunction = PluralCategory(*)(PluralOperands);
  337. static PluralCategory default_category(PluralOperands)
  338. {
  339. return PluralCategory::Other;
  340. }
  341. )~~~");
  342. auto append_rules = [&](auto form, auto const& locale, auto const& rules) {
  343. if (rules.is_empty())
  344. return;
  345. generator.set("method"sv, Locale::generated_method_name(form, locale));
  346. HashTable<String> generated_variables;
  347. generator.append(R"~~~(
  348. static PluralCategory @method@([[maybe_unused]] PluralOperands ops)
  349. {)~~~");
  350. for (auto [category, condition] : rules) {
  351. condition.generate_precomputed_variables(generator, generated_variables);
  352. generator.append(R"~~~(
  353. if ()~~~");
  354. generator.set("category"sv, format_identifier({}, category));
  355. condition.generate_condition(generator);
  356. generator.append(R"~~~()
  357. return PluralCategory::@category@;)~~~");
  358. }
  359. generator.append(R"~~~(
  360. return PluralCategory::Other;
  361. }
  362. )~~~");
  363. };
  364. auto append_lookup_table = [&](auto form) {
  365. generator.set("form"sv, form);
  366. generator.set("size"sv, String::number(locales.size()));
  367. generator.append(R"~~~(
  368. static constexpr Array<PluralCategoryFunction, @size@> s_@form@_functions { {)~~~");
  369. for (auto const& locale : locales) {
  370. auto& rules = locale_data.locales.find(locale)->value;
  371. if (rules.rules_for_form(form).is_empty()) {
  372. generator.append(R"~~~(
  373. default_category,)~~~");
  374. } else {
  375. generator.set("method"sv, Locale::generated_method_name(form, locale));
  376. generator.append(R"~~~(
  377. @method@,)~~~");
  378. }
  379. }
  380. generator.append(R"~~~(
  381. } };
  382. )~~~");
  383. };
  384. auto append_categories = [&](auto const& name, auto const& rules) {
  385. generator.set("name", name);
  386. generator.set("size", String::number(rules.size() + 1));
  387. generator.append(R"~~~(
  388. static constexpr Array<PluralCategory, @size@> @name@ { { PluralCategory::Other)~~~");
  389. for (auto [category, condition] : rules) {
  390. generator.set("category"sv, format_identifier({}, category));
  391. generator.append(", PluralCategory::@category@"sv);
  392. }
  393. generator.append("} };");
  394. };
  395. for (auto [locale, rules] : locale_data.locales) {
  396. append_rules("cardinal"sv, locale, rules.cardinal_rules);
  397. append_rules("ordinal"sv, locale, rules.ordinal_rules);
  398. }
  399. append_lookup_table("cardinal"sv);
  400. append_lookup_table("ordinal"sv);
  401. generate_mapping(generator, locales, "PluralCategory"sv, "s_cardinal_categories"sv, "s_cardinal_categories_{}", format_identifier,
  402. [&](auto const& name, auto const& locale) {
  403. auto& rules = locale_data.locales.find(locale)->value;
  404. append_categories(name, rules.rules_for_form("cardinal"sv));
  405. });
  406. generate_mapping(generator, locales, "PluralCategory"sv, "s_ordinal_categories"sv, "s_ordinal_categories_{}", format_identifier,
  407. [&](auto const& name, auto const& locale) {
  408. auto& rules = locale_data.locales.find(locale)->value;
  409. append_categories(name, rules.rules_for_form("ordinal"sv));
  410. });
  411. generator.append(R"~~~(
  412. PluralCategory determine_plural_category(StringView locale, PluralForm form, PluralOperands operands)
  413. {
  414. auto locale_value = locale_from_string(locale);
  415. if (!locale_value.has_value())
  416. return PluralCategory::Other;
  417. auto locale_index = to_underlying(*locale_value) - 1; // Subtract 1 because 0 == Locale::None.
  418. PluralCategoryFunction decider { nullptr };
  419. switch (form) {
  420. case PluralForm::Cardinal:
  421. decider = s_cardinal_functions[locale_index];
  422. break;
  423. case PluralForm::Ordinal:
  424. decider = s_ordinal_functions[locale_index];
  425. break;
  426. }
  427. return decider(move(operands));
  428. }
  429. Span<PluralCategory const> available_plural_categories(StringView locale, PluralForm form)
  430. {
  431. auto locale_value = locale_from_string(locale);
  432. if (!locale_value.has_value())
  433. return {};
  434. auto locale_index = to_underlying(*locale_value) - 1; // Subtract 1 because 0 == Locale::None.
  435. switch (form) {
  436. case PluralForm::Cardinal:
  437. return s_cardinal_categories[locale_index];
  438. case PluralForm::Ordinal:
  439. return s_ordinal_categories[locale_index];
  440. }
  441. VERIFY_NOT_REACHED();
  442. }
  443. }
  444. )~~~");
  445. TRY(file.write(generator.as_string_view().bytes()));
  446. return {};
  447. }
  448. ErrorOr<int> serenity_main(Main::Arguments arguments)
  449. {
  450. StringView generated_header_path;
  451. StringView generated_implementation_path;
  452. StringView core_path;
  453. StringView locale_names_path;
  454. Core::ArgsParser args_parser;
  455. args_parser.add_option(generated_header_path, "Path to the Unicode locale header file to generate", "generated-header-path", 'h', "generated-header-path");
  456. args_parser.add_option(generated_implementation_path, "Path to the Unicode locale implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
  457. args_parser.add_option(core_path, "Path to cldr-core directory", "core-path", 'r', "core-path");
  458. args_parser.add_option(locale_names_path, "Path to cldr-localenames directory", "locale-names-path", 'l', "locale-names-path");
  459. args_parser.parse(arguments);
  460. auto generated_header_file = TRY(open_file(generated_header_path, Core::Stream::OpenMode::Write));
  461. auto generated_implementation_file = TRY(open_file(generated_implementation_path, Core::Stream::OpenMode::Write));
  462. UnicodeLocaleData locale_data;
  463. TRY(parse_all_locales(core_path, locale_names_path, locale_data));
  464. TRY(generate_unicode_locale_header(*generated_header_file, locale_data));
  465. TRY(generate_unicode_locale_implementation(*generated_implementation_file, locale_data));
  466. return 0;
  467. }