GeneratePluralRulesData.cpp 24 KB

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