NumberFormatConstructor.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /*
  2. * Copyright (c) 2021, Tim Flynn <trflynn89@pm.me>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibJS/Runtime/AbstractOperations.h>
  7. #include <LibJS/Runtime/Array.h>
  8. #include <LibJS/Runtime/GlobalObject.h>
  9. #include <LibJS/Runtime/Intl/AbstractOperations.h>
  10. #include <LibJS/Runtime/Intl/NumberFormat.h>
  11. #include <LibJS/Runtime/Intl/NumberFormatConstructor.h>
  12. #include <LibUnicode/CurrencyCode.h>
  13. #include <LibUnicode/Locale.h>
  14. namespace JS::Intl {
  15. static Vector<StringView> const& number_format_relevant_extension_keys()
  16. {
  17. // 15.3.3 Internal slots, https://tc39.es/ecma402/#sec-intl.numberformat-internal-slots
  18. // The value of the [[RelevantExtensionKeys]] internal slot is « "nu" ».
  19. static Vector<StringView> relevant_extension_keys { "nu"sv };
  20. return relevant_extension_keys;
  21. }
  22. // 15.1.1 SetNumberFormatDigitOptions ( intlObj, options, mnfdDefault, mxfdDefault, notation ), https://tc39.es/ecma402/#sec-setnfdigitoptions
  23. static void set_number_format_digit_options(GlobalObject& global_object, NumberFormat& intl_object, Object const& options, int default_min_fraction_digits, int default_max_fraction_digits, NumberFormat::Notation notation)
  24. {
  25. auto& vm = global_object.vm();
  26. // 1. Assert: Type(intlObj) is Object.
  27. // 2. Assert: Type(options) is Object.
  28. // 3. Assert: Type(mnfdDefault) is Number.
  29. // 4. Assert: Type(mxfdDefault) is Number.
  30. // 5. Let mnid be ? GetNumberOption(options, "minimumIntegerDigits,", 1, 21, 1).
  31. auto min_integer_digits = get_number_option(global_object, options, vm.names.minimumIntegerDigits, 1, 21, 1);
  32. if (vm.exception())
  33. return;
  34. // 6. Let mnfd be ? Get(options, "minimumFractionDigits").
  35. auto min_fraction_digits = options.get(vm.names.minimumFractionDigits);
  36. if (vm.exception())
  37. return;
  38. // 7. Let mxfd be ? Get(options, "maximumFractionDigits").
  39. auto max_fraction_digits = options.get(vm.names.maximumFractionDigits);
  40. if (vm.exception())
  41. return;
  42. // 8. Let mnsd be ? Get(options, "minimumSignificantDigits").
  43. auto min_significant_digits = options.get(vm.names.minimumSignificantDigits);
  44. if (vm.exception())
  45. return;
  46. // 9. Let mxsd be ? Get(options, "maximumSignificantDigits").
  47. auto max_significant_digits = options.get(vm.names.maximumSignificantDigits);
  48. if (vm.exception())
  49. return;
  50. // 10. Set intlObj.[[MinimumIntegerDigits]] to mnid.
  51. intl_object.set_min_integer_digits(*min_integer_digits);
  52. // 11. If mnsd is not undefined or mxsd is not undefined, then
  53. if (!min_significant_digits.is_undefined() || !max_significant_digits.is_undefined()) {
  54. // a. Set intlObj.[[RoundingType]] to significantDigits.
  55. intl_object.set_rounding_type(NumberFormat::RoundingType::SignificantDigits);
  56. // b. Let mnsd be ? DefaultNumberOption(mnsd, 1, 21, 1).
  57. auto min_digits = default_number_option(global_object, min_significant_digits, 1, 21, 1);
  58. if (vm.exception())
  59. return;
  60. // c. Let mxsd be ? DefaultNumberOption(mxsd, mnsd, 21, 21).
  61. auto max_digits = default_number_option(global_object, max_significant_digits, *min_digits, 21, 21);
  62. if (vm.exception())
  63. return;
  64. // d. Set intlObj.[[MinimumSignificantDigits]] to mnsd.
  65. intl_object.set_min_significant_digits(*min_digits);
  66. // e. Set intlObj.[[MaximumSignificantDigits]] to mxsd.
  67. intl_object.set_max_significant_digits(*max_digits);
  68. }
  69. // 12. Else if mnfd is not undefined or mxfd is not undefined, then
  70. else if (!min_fraction_digits.is_undefined() || !max_fraction_digits.is_undefined()) {
  71. // a. Set intlObj.[[RoundingType]] to fractionDigits.
  72. intl_object.set_rounding_type(NumberFormat::RoundingType::FractionDigits);
  73. // b. Let mnfd be ? DefaultNumberOption(mnfd, 0, 20, undefined).
  74. auto min_digits = default_number_option(global_object, min_fraction_digits, 0, 20, {});
  75. if (vm.exception())
  76. return;
  77. // c. Let mxfd be ? DefaultNumberOption(mxfd, 0, 20, undefined).
  78. auto max_digits = default_number_option(global_object, max_fraction_digits, 0, 20, {});
  79. if (vm.exception())
  80. return;
  81. // d. If mnfd is undefined, set mnfd to min(mnfdDefault, mxfd).
  82. if (!min_digits.has_value()) {
  83. min_digits = min(default_min_fraction_digits, *max_digits);
  84. }
  85. // e. Else if mxfd is undefined, set mxfd to max(mxfdDefault, mnfd).
  86. else if (!max_digits.has_value()) {
  87. max_digits = max(default_max_fraction_digits, *min_digits);
  88. }
  89. // f. Else if mnfd is greater than mxfd, throw a RangeError exception.
  90. else if (*min_digits > *max_digits) {
  91. vm.throw_exception<RangeError>(global_object, ErrorType::IntlMinimumExceedsMaximum, *min_digits, *max_digits);
  92. return;
  93. }
  94. // g. Set intlObj.[[MinimumFractionDigits]] to mnfd.
  95. intl_object.set_min_fraction_digits(*min_digits);
  96. // h. Set intlObj.[[MaximumFractionDigits]] to mxfd.
  97. intl_object.set_max_fraction_digits(*max_digits);
  98. }
  99. // 13. Else if notation is "compact", then
  100. else if (notation == NumberFormat::Notation::Compact) {
  101. // a. Set intlObj.[[RoundingType]] to compactRounding.
  102. intl_object.set_rounding_type(NumberFormat::RoundingType::CompactRounding);
  103. }
  104. // 14. Else,
  105. else {
  106. // a. Set intlObj.[[RoundingType]] to fractionDigits.
  107. intl_object.set_rounding_type(NumberFormat::RoundingType::FractionDigits);
  108. // b. Set intlObj.[[MinimumFractionDigits]] to mnfdDefault.
  109. intl_object.set_min_fraction_digits(default_min_fraction_digits);
  110. // c. Set intlObj.[[MaximumFractionDigits]] to mxfdDefault.
  111. intl_object.set_max_fraction_digits(default_max_fraction_digits);
  112. }
  113. }
  114. // 15.1.3 CurrencyDigits ( currency ), https://tc39.es/ecma402/#sec-currencydigits
  115. static int currency_digits(StringView currency)
  116. {
  117. // 1. If the ISO 4217 currency and funds code list contains currency as an alphabetic code, return the minor
  118. // unit value corresponding to the currency from the list; otherwise, return 2.
  119. if (auto currency_code = Unicode::get_currency_code(currency); currency_code.has_value())
  120. return currency_code->minor_unit.value_or(2);
  121. return 2;
  122. }
  123. // 15.1.13 SetNumberFormatUnitOptions ( intlObj, options ), https://tc39.es/ecma402/#sec-setnumberformatunitoptions
  124. static void set_number_format_unit_options(GlobalObject& global_object, NumberFormat& intl_object, Object const& options)
  125. {
  126. auto& vm = global_object.vm();
  127. // 1. Assert: Type(intlObj) is Object.
  128. // 2. Assert: Type(options) is Object.
  129. // 3. Let style be ? GetOption(options, "style", "string", « "decimal", "percent", "currency", "unit" », "decimal").
  130. auto style = get_option(global_object, options, vm.names.style, Value::Type::String, { "decimal"sv, "percent"sv, "currency"sv, "unit"sv }, "decimal"sv);
  131. if (vm.exception())
  132. return;
  133. // 4. Set intlObj.[[Style]] to style.
  134. intl_object.set_style(style.as_string().string());
  135. // 5. Let currency be ? GetOption(options, "currency", "string", undefined, undefined).
  136. auto currency = get_option(global_object, options, vm.names.currency, Value::Type::String, {}, Empty {});
  137. if (vm.exception())
  138. return;
  139. // 6. If currency is undefined, then
  140. if (currency.is_undefined()) {
  141. // a. If style is "currency", throw a TypeError exception.
  142. if (intl_object.style() == NumberFormat::Style::Currency) {
  143. vm.throw_exception<TypeError>(global_object, ErrorType::IntlOptionUndefined, "currency"sv, "style"sv, style);
  144. return;
  145. }
  146. }
  147. // 7. Else,
  148. // a. If the result of IsWellFormedCurrencyCode(currency) is false, throw a RangeError exception.
  149. else if (!is_well_formed_currency_code(currency.as_string().string())) {
  150. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, currency, "currency"sv);
  151. return;
  152. }
  153. // 8. Let currencyDisplay be ? GetOption(options, "currencyDisplay", "string", « "code", "symbol", "narrowSymbol", "name" », "symbol").
  154. auto currency_display = get_option(global_object, options, vm.names.currencyDisplay, Value::Type::String, { "code"sv, "symbol"sv, "narrowSymbol"sv, "name"sv }, "symbol"sv);
  155. if (vm.exception())
  156. return;
  157. // 9. Let currencySign be ? GetOption(options, "currencySign", "string", « "standard", "accounting" », "standard").
  158. auto currency_sign = get_option(global_object, options, vm.names.currencySign, Value::Type::String, { "standard"sv, "accounting"sv }, "standard"sv);
  159. if (vm.exception())
  160. return;
  161. // 10. Let unit be ? GetOption(options, "unit", "string", undefined, undefined).
  162. auto unit = get_option(global_object, options, vm.names.unit, Value::Type::String, {}, Empty {});
  163. if (vm.exception())
  164. return;
  165. // 11. If unit is undefined, then
  166. if (unit.is_undefined()) {
  167. // a. If style is "unit", throw a TypeError exception.
  168. if (intl_object.style() == NumberFormat::Style::Unit) {
  169. vm.throw_exception<TypeError>(global_object, ErrorType::IntlOptionUndefined, "unit"sv, "style"sv, style);
  170. return;
  171. }
  172. }
  173. // 12. Else,
  174. // a. If the result of IsWellFormedUnitIdentifier(unit) is false, throw a RangeError exception.
  175. else if (!is_well_formed_unit_identifier(unit.as_string().string())) {
  176. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, unit, "unit"sv);
  177. return;
  178. }
  179. // 13. Let unitDisplay be ? GetOption(options, "unitDisplay", "string", « "short", "narrow", "long" », "short").
  180. auto unit_display = get_option(global_object, options, vm.names.unitDisplay, Value::Type::String, { "short"sv, "narrow"sv, "long"sv }, "short"sv);
  181. if (vm.exception())
  182. return;
  183. // 14. If style is "currency", then
  184. if (intl_object.style() == NumberFormat::Style::Currency) {
  185. // a. Let currency be the result of converting currency to upper case as specified in 6.1.
  186. // b. Set intlObj.[[Currency]] to currency.
  187. intl_object.set_currency(currency.as_string().string().to_uppercase());
  188. // c. Set intlObj.[[CurrencyDisplay]] to currencyDisplay.
  189. intl_object.set_currency_display(currency_display.as_string().string());
  190. // d. Set intlObj.[[CurrencySign]] to currencySign.
  191. intl_object.set_currency_sign(currency_sign.as_string().string());
  192. }
  193. // 15. If style is "unit", then
  194. if (intl_object.style() == NumberFormat::Style::Unit) {
  195. // a. Set intlObj.[[Unit]] to unit.
  196. intl_object.set_unit(unit.as_string().string());
  197. // b. Set intlObj.[[UnitDisplay]] to unitDisplay.
  198. intl_object.set_unit_display(unit_display.as_string().string());
  199. }
  200. }
  201. // 15.1.2 InitializeNumberFormat ( numberFormat, locales, options ), https://tc39.es/ecma402/#sec-initializenumberformat
  202. static NumberFormat* initialize_number_format(GlobalObject& global_object, NumberFormat& number_format, Value locales_value, Value options_value)
  203. {
  204. auto& vm = global_object.vm();
  205. // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
  206. auto requested_locales = canonicalize_locale_list(global_object, locales_value);
  207. if (vm.exception())
  208. return {};
  209. // 2. Set options to ? CoerceOptionsToObject(options).
  210. auto* options = coerce_options_to_object(global_object, options_value);
  211. if (vm.exception())
  212. return {};
  213. // 3. Let opt be a new Record.
  214. LocaleOptions opt {};
  215. // 4. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
  216. auto matcher = get_option(global_object, *options, vm.names.localeMatcher, Value::Type::String, { "lookup"sv, "best fit"sv }, "best fit"sv);
  217. if (vm.exception())
  218. return {};
  219. // 5. Set opt.[[localeMatcher]] to matcher.
  220. opt.locale_matcher = matcher;
  221. // 6. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
  222. auto numbering_system = get_option(global_object, *options, vm.names.numberingSystem, Value::Type::String, {}, Empty {});
  223. if (vm.exception())
  224. return {};
  225. // 7. If numberingSystem is not undefined, then
  226. if (!numbering_system.is_undefined()) {
  227. // a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
  228. if (!Unicode::is_type_identifier(numbering_system.as_string().string())) {
  229. vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, numbering_system, "numberingSystem"sv);
  230. return {};
  231. }
  232. // 8. Set opt.[[nu]] to numberingSystem.
  233. opt.nu = numbering_system.as_string().string();
  234. }
  235. // 9. Let localeData be %NumberFormat%.[[LocaleData]].
  236. // 10. Let r be ResolveLocale(%NumberFormat%.[[AvailableLocales]], requestedLocales, opt, %NumberFormat%.[[RelevantExtensionKeys]], localeData).
  237. auto result = resolve_locale(requested_locales, opt, number_format_relevant_extension_keys());
  238. // 11. Set numberFormat.[[Locale]] to r.[[locale]].
  239. number_format.set_locale(move(result.locale));
  240. // 12. Set numberFormat.[[DataLocale]] to r.[[dataLocale]].
  241. number_format.set_data_locale(move(result.data_locale));
  242. // 13. Set numberFormat.[[NumberingSystem]] to r.[[nu]].
  243. number_format.set_numbering_system(result.nu.release_value());
  244. // 14. Perform ? SetNumberFormatUnitOptions(numberFormat, options).
  245. set_number_format_unit_options(global_object, number_format, *options);
  246. if (vm.exception())
  247. return {};
  248. // 15. Let style be numberFormat.[[Style]].
  249. auto style = number_format.style();
  250. int default_min_fraction_digits = 0;
  251. int default_max_fraction_digits = 0;
  252. // 16. If style is "currency", then
  253. if (style == NumberFormat::Style::Currency) {
  254. // a. Let currency be numberFormat.[[Currency]].
  255. auto const& currency = number_format.currency();
  256. // b. Let cDigits be CurrencyDigits(currency).
  257. int digits = currency_digits(currency);
  258. // c. Let mnfdDefault be cDigits.
  259. default_min_fraction_digits = digits;
  260. // d. Let mxfdDefault be cDigits.
  261. default_max_fraction_digits = digits;
  262. }
  263. // 17. Else,
  264. else {
  265. // a. Let mnfdDefault be 0.
  266. default_min_fraction_digits = 0;
  267. // b. If style is "percent", then
  268. // i. Let mxfdDefault be 0.
  269. // c. Else,
  270. // i. Let mxfdDefault be 3.
  271. default_max_fraction_digits = style == NumberFormat::Style::Percent ? 0 : 3;
  272. }
  273. // 18. Let notation be ? GetOption(options, "notation", "string", « "standard", "scientific", "engineering", "compact" », "standard").
  274. auto notation = get_option(global_object, *options, vm.names.notation, Value::Type::String, { "standard"sv, "scientific"sv, "engineering"sv, "compact"sv }, "standard"sv);
  275. if (vm.exception())
  276. return {};
  277. // 19. Set numberFormat.[[Notation]] to notation.
  278. number_format.set_notation(notation.as_string().string());
  279. // 20. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation).
  280. set_number_format_digit_options(global_object, number_format, *options, default_min_fraction_digits, default_max_fraction_digits, number_format.notation());
  281. if (vm.exception())
  282. return {};
  283. // 21. Let compactDisplay be ? GetOption(options, "compactDisplay", "string", « "short", "long" », "short").
  284. auto compact_display = get_option(global_object, *options, vm.names.compactDisplay, Value::Type::String, { "short"sv, "long"sv }, "short"sv);
  285. if (vm.exception())
  286. return {};
  287. // 22. If notation is "compact", then
  288. if (number_format.notation() == NumberFormat::Notation::Compact) {
  289. // a. Set numberFormat.[[CompactDisplay]] to compactDisplay.
  290. number_format.set_compact_display(compact_display.as_string().string());
  291. }
  292. // 23. Let useGrouping be ? GetOption(options, "useGrouping", "boolean", undefined, true).
  293. auto use_grouping = get_option(global_object, *options, vm.names.useGrouping, Value::Type::Boolean, {}, true);
  294. if (vm.exception())
  295. return {};
  296. // 24. Set numberFormat.[[UseGrouping]] to useGrouping.
  297. number_format.set_use_grouping(use_grouping.as_bool());
  298. // 25. Let signDisplay be ? GetOption(options, "signDisplay", "string", « "auto", "never", "always", "exceptZero" », "auto").
  299. auto sign_display = get_option(global_object, *options, vm.names.signDisplay, Value::Type::String, { "auto"sv, "never"sv, "always"sv, "exceptZero"sv }, "auto"sv);
  300. if (vm.exception())
  301. return {};
  302. // 26. Set numberFormat.[[SignDisplay]] to signDisplay.
  303. number_format.set_sign_display(sign_display.as_string().string());
  304. // 27. Return numberFormat.
  305. return &number_format;
  306. }
  307. // 15.2 The Intl.NumberFormat Constructor, https://tc39.es/ecma402/#sec-intl-numberformat-constructor
  308. NumberFormatConstructor::NumberFormatConstructor(GlobalObject& global_object)
  309. : NativeFunction(vm().names.NumberFormat.as_string(), *global_object.function_prototype())
  310. {
  311. }
  312. void NumberFormatConstructor::initialize(GlobalObject& global_object)
  313. {
  314. NativeFunction::initialize(global_object);
  315. auto& vm = this->vm();
  316. // 15.3.1 Intl.NumberFormat.prototype, https://tc39.es/ecma402/#sec-intl.numberformat.prototype
  317. define_direct_property(vm.names.prototype, global_object.intl_number_format_prototype(), 0);
  318. u8 attr = Attribute::Writable | Attribute::Configurable;
  319. define_native_function(vm.names.supportedLocalesOf, supported_locales_of, 1, attr);
  320. define_direct_property(vm.names.length, Value(0), Attribute::Configurable);
  321. }
  322. // 15.2.1 Intl.NumberFormat ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sec-intl.numberformat
  323. Value NumberFormatConstructor::call()
  324. {
  325. // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
  326. return construct(*this);
  327. }
  328. // 15.2.1 Intl.NumberFormat ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sec-intl.numberformat
  329. Value NumberFormatConstructor::construct(FunctionObject& new_target)
  330. {
  331. auto& vm = this->vm();
  332. auto& global_object = this->global_object();
  333. auto locales = vm.argument(0);
  334. auto options = vm.argument(1);
  335. // 2. Let numberFormat be ? OrdinaryCreateFromConstructor(newTarget, "%NumberFormat.prototype%", « [[InitializedNumberFormat]], [[Locale]], [[DataLocale]], [[NumberingSystem]], [[Style]], [[Unit]], [[UnitDisplay]], [[Currency]], [[CurrencyDisplay]], [[CurrencySign]], [[MinimumIntegerDigits]], [[MinimumFractionDigits]], [[MaximumFractionDigits]], [[MinimumSignificantDigits]], [[MaximumSignificantDigits]], [[RoundingType]], [[Notation]], [[CompactDisplay]], [[UseGrouping]], [[SignDisplay]], [[BoundFormat]] »).
  336. auto* number_format = ordinary_create_from_constructor<NumberFormat>(global_object, new_target, &GlobalObject::intl_number_format_prototype);
  337. if (vm.exception())
  338. return {};
  339. // 3. Perform ? InitializeNumberFormat(numberFormat, locales, options).
  340. initialize_number_format(global_object, *number_format, locales, options);
  341. if (vm.exception())
  342. return {};
  343. // 4. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then
  344. // a. Let this be the this value.
  345. // b. Return ? ChainNumberFormat(numberFormat, NewTarget, this).
  346. // 5. Return numberFormat.
  347. return number_format;
  348. }
  349. // 15.3.2 Intl.NumberFormat.supportedLocalesOf ( locales [ , options ] ), https://tc39.es/ecma402/#sec-intl.numberformat.supportedlocalesof
  350. JS_DEFINE_NATIVE_FUNCTION(NumberFormatConstructor::supported_locales_of)
  351. {
  352. auto locales = vm.argument(0);
  353. auto options = vm.argument(1);
  354. // 1. Let availableLocales be %NumberFormat%.[[AvailableLocales]].
  355. // 2. Let requestedLocales be ? CanonicalizeLocaleList(locales).
  356. auto requested_locales = canonicalize_locale_list(global_object, locales);
  357. if (vm.exception())
  358. return {};
  359. // 3. Return ? SupportedLocales(availableLocales, requestedLocales, options).
  360. return supported_locales(global_object, requested_locales, options);
  361. }
  362. }