1 // Copyright 2018 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifndef V8_INTL_SUPPORT
6 #error Internationalization is expected to be enabled.
7 #endif // V8_INTL_SUPPORT
8
9 #include "src/objects/js-number-format.h"
10
11 #include <set>
12 #include <string>
13
14 #include "src/execution/isolate.h"
15 #include "src/objects/intl-objects.h"
16 #include "src/objects/js-number-format-inl.h"
17 #include "src/objects/objects-inl.h"
18 #include "unicode/currunit.h"
19 #include "unicode/decimfmt.h"
20 #include "unicode/locid.h"
21 #include "unicode/numberformatter.h"
22 #include "unicode/numfmt.h"
23 #include "unicode/numsys.h"
24 #include "unicode/ucurr.h"
25 #include "unicode/uloc.h"
26 #include "unicode/unumberformatter.h"
27 #include "unicode/uvernum.h" // for U_ICU_VERSION_MAJOR_NUM
28
29 namespace v8 {
30 namespace internal {
31
32 namespace {
33
34 // [[Style]] is one of the values "decimal", "percent", "currency",
35 // or "unit" identifying the style of the number format.
36 enum class Style { DECIMAL, PERCENT, CURRENCY, UNIT };
37
38 // [[CurrencyDisplay]] is one of the values "code", "symbol", "name",
39 // or "narrowSymbol" identifying the display of the currency number format.
40 enum class CurrencyDisplay {
41 CODE,
42 SYMBOL,
43 NAME,
44 NARROW_SYMBOL,
45 };
46
47 // [[CurrencySign]] is one of the String values "standard" or "accounting",
48 // specifying whether to render negative numbers in accounting format, often
49 // signified by parenthesis. It is only used when [[Style]] has the value
50 // "currency" and when [[SignDisplay]] is not "never".
51 enum class CurrencySign {
52 STANDARD,
53 ACCOUNTING,
54 };
55
56 // [[UnitDisplay]] is one of the String values "short", "narrow", or "long",
57 // specifying whether to display the unit as a symbol, narrow symbol, or
58 // localized long name if formatting with the "unit" style. It is
59 // only used when [[Style]] has the value "unit".
60 enum class UnitDisplay {
61 SHORT,
62 NARROW,
63 LONG,
64 };
65
66 // [[Notation]] is one of the String values "standard", "scientific",
67 // "engineering", or "compact", specifying whether the number should be
68 // displayed without scaling, scaled to the units place with the power of ten
69 // in scientific notation, scaled to the nearest thousand with the power of
70 // ten in scientific notation, or scaled to the nearest locale-dependent
71 // compact decimal notation power of ten with the corresponding compact
72 // decimal notation affix.
73
74 enum class Notation {
75 STANDARD,
76 SCIENTIFIC,
77 ENGINEERING,
78 COMPACT,
79 };
80
81 // [[CompactDisplay]] is one of the String values "short" or "long",
82 // specifying whether to display compact notation affixes in short form ("5K")
83 // or long form ("5 thousand") if formatting with the "compact" notation. It
84 // is only used when [[Notation]] has the value "compact".
85 enum class CompactDisplay {
86 SHORT,
87 LONG,
88 };
89
90 // [[SignDisplay]] is one of the String values "auto", "always", "never", or
91 // "exceptZero", specifying whether to show the sign on negative numbers
92 // only, positive and negative numbers including zero, neither positive nor
93 // negative numbers, or positive and negative numbers but not zero.
94 enum class SignDisplay {
95 AUTO,
96 ALWAYS,
97 NEVER,
98 EXCEPT_ZERO,
99 };
100
ToUNumberUnitWidth(CurrencyDisplay currency_display)101 UNumberUnitWidth ToUNumberUnitWidth(CurrencyDisplay currency_display) {
102 switch (currency_display) {
103 case CurrencyDisplay::SYMBOL:
104 return UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
105 case CurrencyDisplay::CODE:
106 return UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE;
107 case CurrencyDisplay::NAME:
108 return UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
109 case CurrencyDisplay::NARROW_SYMBOL:
110 return UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
111 }
112 }
113
ToUNumberUnitWidth(UnitDisplay unit_display)114 UNumberUnitWidth ToUNumberUnitWidth(UnitDisplay unit_display) {
115 switch (unit_display) {
116 case UnitDisplay::SHORT:
117 return UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
118 case UnitDisplay::LONG:
119 return UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
120 case UnitDisplay::NARROW:
121 return UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
122 }
123 }
124
ToUNumberSignDisplay(SignDisplay sign_display,CurrencySign currency_sign)125 UNumberSignDisplay ToUNumberSignDisplay(SignDisplay sign_display,
126 CurrencySign currency_sign) {
127 switch (sign_display) {
128 case SignDisplay::AUTO:
129 if (currency_sign == CurrencySign::ACCOUNTING) {
130 return UNumberSignDisplay::UNUM_SIGN_ACCOUNTING;
131 }
132 DCHECK(currency_sign == CurrencySign::STANDARD);
133 return UNumberSignDisplay::UNUM_SIGN_AUTO;
134 case SignDisplay::NEVER:
135 return UNumberSignDisplay::UNUM_SIGN_NEVER;
136 case SignDisplay::ALWAYS:
137 if (currency_sign == CurrencySign::ACCOUNTING) {
138 return UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS;
139 }
140 DCHECK(currency_sign == CurrencySign::STANDARD);
141 return UNumberSignDisplay::UNUM_SIGN_ALWAYS;
142 case SignDisplay::EXCEPT_ZERO:
143 if (currency_sign == CurrencySign::ACCOUNTING) {
144 return UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO;
145 }
146 DCHECK(currency_sign == CurrencySign::STANDARD);
147 return UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO;
148 }
149 }
150
ToICUNotation(Notation notation,CompactDisplay compact_display)151 icu::number::Notation ToICUNotation(Notation notation,
152 CompactDisplay compact_display) {
153 switch (notation) {
154 case Notation::STANDARD:
155 return icu::number::Notation::simple();
156 case Notation::SCIENTIFIC:
157 return icu::number::Notation::scientific();
158 case Notation::ENGINEERING:
159 return icu::number::Notation::engineering();
160 // 29. If notation is "compact", then
161 case Notation::COMPACT:
162 // 29. a. Set numberFormat.[[CompactDisplay]] to compactDisplay.
163 if (compact_display == CompactDisplay::SHORT) {
164 return icu::number::Notation::compactShort();
165 }
166 DCHECK(compact_display == CompactDisplay::LONG);
167 return icu::number::Notation::compactLong();
168 }
169 }
170
CreateUnitMap()171 std::map<const std::string, icu::MeasureUnit> CreateUnitMap() {
172 UErrorCode status = U_ZERO_ERROR;
173 int32_t total = icu::MeasureUnit::getAvailable(nullptr, 0, status);
174 CHECK(U_FAILURE(status));
175 status = U_ZERO_ERROR;
176 // See the list in ecma402 #sec-issanctionedsimpleunitidentifier
177 std::set<std::string> sanctioned(
178 {"acre", "bit", "byte",
179 "celsius", "centimeter", "day",
180 "degree", "fahrenheit", "fluid-ounce",
181 "foot", "gallon", "gigabit",
182 "gigabyte", "gram", "hectare",
183 "hour", "inch", "kilobit",
184 "kilobyte", "kilogram", "kilometer",
185 "liter", "megabit", "megabyte",
186 "meter", "mile", "mile-scandinavian",
187 "millimeter", "milliliter", "millisecond",
188 "minute", "month", "ounce",
189 "percent", "petabyte", "pound",
190 "second", "stone", "terabit",
191 "terabyte", "week", "yard",
192 "year"});
193 std::vector<icu::MeasureUnit> units(total);
194 total = icu::MeasureUnit::getAvailable(units.data(), total, status);
195 CHECK(U_SUCCESS(status));
196 std::map<const std::string, icu::MeasureUnit> map;
197 for (auto it = units.begin(); it != units.end(); ++it) {
198 // Need to skip none/percent
199 if (sanctioned.count(it->getSubtype()) > 0 &&
200 strcmp("none", it->getType()) != 0) {
201 map[it->getSubtype()] = *it;
202 }
203 }
204 return map;
205 }
206
207 class UnitFactory {
208 public:
UnitFactory()209 UnitFactory() : map_(CreateUnitMap()) {}
210 virtual ~UnitFactory() = default;
211
212 // ecma402 #sec-issanctionedsimpleunitidentifier
create(const std::string & unitIdentifier)213 icu::MeasureUnit create(const std::string& unitIdentifier) {
214 // 1. If unitIdentifier is in the following list, return true.
215 auto found = map_.find(unitIdentifier);
216 if (found != map_.end()) {
217 return found->second;
218 }
219 // 2. Return false.
220 return icu::MeasureUnit();
221 }
222
223 private:
224 std::map<const std::string, icu::MeasureUnit> map_;
225 };
226
227 // ecma402 #sec-issanctionedsimpleunitidentifier
IsSanctionedUnitIdentifier(const std::string & unit)228 icu::MeasureUnit IsSanctionedUnitIdentifier(const std::string& unit) {
229 static base::LazyInstance<UnitFactory>::type factory =
230 LAZY_INSTANCE_INITIALIZER;
231 return factory.Pointer()->create(unit);
232 }
233
234 // ecma402 #sec-iswellformedunitidentifier
IsWellFormedUnitIdentifier(Isolate * isolate,const std::string & unit)235 Maybe<std::pair<icu::MeasureUnit, icu::MeasureUnit>> IsWellFormedUnitIdentifier(
236 Isolate* isolate, const std::string& unit) {
237 icu::MeasureUnit result = IsSanctionedUnitIdentifier(unit);
238 icu::MeasureUnit none = icu::MeasureUnit();
239 // 1. If the result of IsSanctionedUnitIdentifier(unitIdentifier) is true,
240 // then
241 if (result != none) {
242 // a. Return true.
243 std::pair<icu::MeasureUnit, icu::MeasureUnit> pair(result, none);
244 return Just(pair);
245 }
246 // 2. If the substring "-per-" does not occur exactly once in unitIdentifier,
247 // then
248 size_t first_per = unit.find("-per-");
249 if (first_per == std::string::npos ||
250 unit.find("-per-", first_per + 5) != std::string::npos) {
251 // a. Return false.
252 return Nothing<std::pair<icu::MeasureUnit, icu::MeasureUnit>>();
253 }
254 // 3. Let numerator be the substring of unitIdentifier from the beginning to
255 // just before "-per-".
256 std::string numerator = unit.substr(0, first_per);
257
258 // 4. If the result of IsSanctionedUnitIdentifier(numerator) is false, then
259 result = IsSanctionedUnitIdentifier(numerator);
260 if (result == none) {
261 // a. Return false.
262 return Nothing<std::pair<icu::MeasureUnit, icu::MeasureUnit>>();
263 }
264 // 5. Let denominator be the substring of unitIdentifier from just after
265 // "-per-" to the end.
266 std::string denominator = unit.substr(first_per + 5);
267
268 // 6. If the result of IsSanctionedUnitIdentifier(denominator) is false, then
269 icu::MeasureUnit den_result = IsSanctionedUnitIdentifier(denominator);
270 if (den_result == none) {
271 // a. Return false.
272 return Nothing<std::pair<icu::MeasureUnit, icu::MeasureUnit>>();
273 }
274 // 7. Return true.
275 std::pair<icu::MeasureUnit, icu::MeasureUnit> pair(result, den_result);
276 return Just(pair);
277 }
278
279 // ecma-402/#sec-currencydigits
280 // The currency is expected to an all upper case string value.
CurrencyDigits(const icu::UnicodeString & currency)281 int CurrencyDigits(const icu::UnicodeString& currency) {
282 UErrorCode status = U_ZERO_ERROR;
283 uint32_t fraction_digits = ucurr_getDefaultFractionDigits(
284 reinterpret_cast<const UChar*>(currency.getBuffer()), &status);
285 // For missing currency codes, default to the most common, 2
286 return U_SUCCESS(status) ? fraction_digits : 2;
287 }
288
IsAToZ(char ch)289 bool IsAToZ(char ch) {
290 return base::IsInRange(AsciiAlphaToLower(ch), 'a', 'z');
291 }
292
293 // ecma402/#sec-iswellformedcurrencycode
IsWellFormedCurrencyCode(const std::string & currency)294 bool IsWellFormedCurrencyCode(const std::string& currency) {
295 // Verifies that the input is a well-formed ISO 4217 currency code.
296 // ecma402/#sec-currency-codes
297 // 2. If the number of elements in normalized is not 3, return false.
298 if (currency.length() != 3) return false;
299 // 1. Let normalized be the result of mapping currency to upper case as
300 // described in 6.1.
301 //
302 // 3. If normalized contains any character that is not in
303 // the range "A" to "Z" (U+0041 to U+005A), return false.
304 //
305 // 4. Return true.
306 // Don't uppercase to test. It could convert invalid code into a valid one.
307 // For example \u00DFP (Eszett+P) becomes SSP.
308 return (IsAToZ(currency[0]) && IsAToZ(currency[1]) && IsAToZ(currency[2]));
309 }
310
311 // Return the style as a String.
StyleAsString(Isolate * isolate,Style style)312 Handle<String> StyleAsString(Isolate* isolate, Style style) {
313 switch (style) {
314 case Style::PERCENT:
315 return ReadOnlyRoots(isolate).percent_string_handle();
316 case Style::CURRENCY:
317 return ReadOnlyRoots(isolate).currency_string_handle();
318 case Style::UNIT:
319 return ReadOnlyRoots(isolate).unit_string_handle();
320 case Style::DECIMAL:
321 return ReadOnlyRoots(isolate).decimal_string_handle();
322 }
323 UNREACHABLE();
324 }
325
326 // Parse the 'currencyDisplay' from the skeleton.
CurrencyDisplayString(Isolate * isolate,const icu::UnicodeString & skeleton)327 Handle<String> CurrencyDisplayString(Isolate* isolate,
328 const icu::UnicodeString& skeleton) {
329 // Ex: skeleton as
330 // "currency/TWD .00 rounding-mode-half-up unit-width-iso-code"
331 if (skeleton.indexOf("unit-width-iso-code") >= 0) {
332 return ReadOnlyRoots(isolate).code_string_handle();
333 }
334 // Ex: skeleton as
335 // "currency/TWD .00 rounding-mode-half-up unit-width-full-name;"
336 if (skeleton.indexOf("unit-width-full-name") >= 0) {
337 return ReadOnlyRoots(isolate).name_string_handle();
338 }
339 // Ex: skeleton as
340 // "currency/TWD .00 rounding-mode-half-up unit-width-narrow;
341 if (skeleton.indexOf("unit-width-narrow") >= 0) {
342 return ReadOnlyRoots(isolate).narrowSymbol_string_handle();
343 }
344 // Ex: skeleton as "currency/TWD .00 rounding-mode-half-up"
345 return ReadOnlyRoots(isolate).symbol_string_handle();
346 }
347
348 // Return true if there are no "group-off" in the skeleton.
UseGroupingFromSkeleton(const icu::UnicodeString & skeleton)349 bool UseGroupingFromSkeleton(const icu::UnicodeString& skeleton) {
350 return skeleton.indexOf("group-off") == -1;
351 }
352
353 // Parse currency code from skeleton. For example, skeleton as
354 // "currency/TWD .00 rounding-mode-half-up unit-width-full-name;"
CurrencyFromSkeleton(const icu::UnicodeString & skeleton)355 const icu::UnicodeString CurrencyFromSkeleton(
356 const icu::UnicodeString& skeleton) {
357 const char currency[] = "currency/";
358 int32_t index = skeleton.indexOf(currency);
359 if (index < 0) return "";
360 index += static_cast<int32_t>(std::strlen(currency));
361 return skeleton.tempSubString(index, 3);
362 }
363
NumberingSystemFromSkeleton(const icu::UnicodeString & skeleton)364 const icu::UnicodeString NumberingSystemFromSkeleton(
365 const icu::UnicodeString& skeleton) {
366 const char numbering_system[] = "numbering-system/";
367 int32_t index = skeleton.indexOf(numbering_system);
368 if (index < 0) return "latn";
369 index += static_cast<int32_t>(std::strlen(numbering_system));
370 const icu::UnicodeString res = skeleton.tempSubString(index);
371 index = res.indexOf(" ");
372 if (index < 0) return res;
373 return res.tempSubString(0, index);
374 }
375
376 // Return CurrencySign as string based on skeleton.
CurrencySignString(Isolate * isolate,const icu::UnicodeString & skeleton)377 Handle<String> CurrencySignString(Isolate* isolate,
378 const icu::UnicodeString& skeleton) {
379 // Ex: skeleton as
380 // "currency/TWD .00 rounding-mode-half-up sign-accounting-always" OR
381 // "currency/TWD .00 rounding-mode-half-up sign-accounting-except-zero"
382 if (skeleton.indexOf("sign-accounting") >= 0) {
383 return ReadOnlyRoots(isolate).accounting_string_handle();
384 }
385 return ReadOnlyRoots(isolate).standard_string_handle();
386 }
387
388 // Return UnitDisplay as string based on skeleton.
UnitDisplayString(Isolate * isolate,const icu::UnicodeString & skeleton)389 Handle<String> UnitDisplayString(Isolate* isolate,
390 const icu::UnicodeString& skeleton) {
391 // Ex: skeleton as
392 // "unit/length-meter .### rounding-mode-half-up unit-width-full-name"
393 if (skeleton.indexOf("unit-width-full-name") >= 0) {
394 return ReadOnlyRoots(isolate).long_string_handle();
395 }
396 // Ex: skeleton as
397 // "unit/length-meter .### rounding-mode-half-up unit-width-narrow".
398 if (skeleton.indexOf("unit-width-narrow") >= 0) {
399 return ReadOnlyRoots(isolate).narrow_string_handle();
400 }
401 // Ex: skeleton as
402 // "unit/length-foot .### rounding-mode-half-up"
403 return ReadOnlyRoots(isolate).short_string_handle();
404 }
405
406 // Parse Notation from skeleton.
NotationFromSkeleton(const icu::UnicodeString & skeleton)407 Notation NotationFromSkeleton(const icu::UnicodeString& skeleton) {
408 // Ex: skeleton as
409 // "scientific .### rounding-mode-half-up"
410 if (skeleton.indexOf("scientific") >= 0) {
411 return Notation::SCIENTIFIC;
412 }
413 // Ex: skeleton as
414 // "engineering .### rounding-mode-half-up"
415 if (skeleton.indexOf("engineering") >= 0) {
416 return Notation::ENGINEERING;
417 }
418 // Ex: skeleton as
419 // "compact-short .### rounding-mode-half-up" or
420 // "compact-long .### rounding-mode-half-up
421 if (skeleton.indexOf("compact-") >= 0) {
422 return Notation::COMPACT;
423 }
424 // Ex: skeleton as
425 // "unit/length-foot .### rounding-mode-half-up"
426 return Notation::STANDARD;
427 }
428
NotationAsString(Isolate * isolate,Notation notation)429 Handle<String> NotationAsString(Isolate* isolate, Notation notation) {
430 switch (notation) {
431 case Notation::SCIENTIFIC:
432 return ReadOnlyRoots(isolate).scientific_string_handle();
433 case Notation::ENGINEERING:
434 return ReadOnlyRoots(isolate).engineering_string_handle();
435 case Notation::COMPACT:
436 return ReadOnlyRoots(isolate).compact_string_handle();
437 case Notation::STANDARD:
438 return ReadOnlyRoots(isolate).standard_string_handle();
439 }
440 UNREACHABLE();
441 }
442
443 // Return CompactString as string based on skeleton.
CompactDisplayString(Isolate * isolate,const icu::UnicodeString & skeleton)444 Handle<String> CompactDisplayString(Isolate* isolate,
445 const icu::UnicodeString& skeleton) {
446 // Ex: skeleton as
447 // "compact-long .### rounding-mode-half-up"
448 if (skeleton.indexOf("compact-long") >= 0) {
449 return ReadOnlyRoots(isolate).long_string_handle();
450 }
451 // Ex: skeleton as
452 // "compact-short .### rounding-mode-half-up"
453 DCHECK_GE(skeleton.indexOf("compact-short"), 0);
454 return ReadOnlyRoots(isolate).short_string_handle();
455 }
456
457 // Return SignDisplay as string based on skeleton.
SignDisplayString(Isolate * isolate,const icu::UnicodeString & skeleton)458 Handle<String> SignDisplayString(Isolate* isolate,
459 const icu::UnicodeString& skeleton) {
460 // Ex: skeleton as
461 // "currency/TWD .00 rounding-mode-half-up sign-never"
462 if (skeleton.indexOf("sign-never") >= 0) {
463 return ReadOnlyRoots(isolate).never_string_handle();
464 }
465 // Ex: skeleton as
466 // ".### rounding-mode-half-up sign-always" or
467 // "currency/TWD .00 rounding-mode-half-up sign-accounting-always"
468 if (skeleton.indexOf("sign-always") >= 0 ||
469 skeleton.indexOf("sign-accounting-always") >= 0) {
470 return ReadOnlyRoots(isolate).always_string_handle();
471 }
472 // Ex: skeleton as
473 // "currency/TWD .00 rounding-mode-half-up sign-accounting-except-zero" or
474 // "currency/TWD .00 rounding-mode-half-up sign-except-zero"
475 if (skeleton.indexOf("sign-accounting-except-zero") >= 0 ||
476 skeleton.indexOf("sign-except-zero") >= 0) {
477 return ReadOnlyRoots(isolate).exceptZero_string_handle();
478 }
479 return ReadOnlyRoots(isolate).auto_string_handle();
480 }
481
482 } // anonymous namespace
483
484 // Return the minimum integer digits by counting the number of '0' after
485 // "integer-width/*" in the skeleton.
486 // Ex: Return 15 for skeleton as
487 // “currency/TWD .00 rounding-mode-half-up integer-width/*000000000000000”
488 // 1
489 // 123456789012345
490 // Return default value as 1 if there are no "integer-width/*".
MinimumIntegerDigitsFromSkeleton(const icu::UnicodeString & skeleton)491 int32_t JSNumberFormat::MinimumIntegerDigitsFromSkeleton(
492 const icu::UnicodeString& skeleton) {
493 // count the number of 0 after "integer-width/*"
494 icu::UnicodeString search("integer-width/*");
495 int32_t index = skeleton.indexOf(search);
496 if (index < 0) return 1; // return 1 if cannot find it.
497 index += search.length();
498 int32_t matched = 0;
499 while (index < skeleton.length() && skeleton[index] == '0') {
500 matched++;
501 index++;
502 }
503 CHECK_GT(matched, 0);
504 return matched;
505 }
506
507 // Return true if there are fraction digits, false if not.
508 // The minimum fraction digits is the number of '0' after '.' in the skeleton
509 // The maximum fraction digits is the number of '#' after the above '0's plus
510 // the minimum fraction digits.
511 // For example, as skeleton “.000#### rounding-mode-half-up”
512 // 123
513 // 4567
514 // Set The minimum as 3 and maximum as 7.
FractionDigitsFromSkeleton(const icu::UnicodeString & skeleton,int32_t * minimum,int32_t * maximum)515 bool JSNumberFormat::FractionDigitsFromSkeleton(
516 const icu::UnicodeString& skeleton, int32_t* minimum, int32_t* maximum) {
517 icu::UnicodeString search(".");
518 int32_t index = skeleton.indexOf(search);
519 if (index < 0) return false;
520 *minimum = 0;
521 index++; // skip the '.'
522 while (index < skeleton.length() && skeleton[index] == '0') {
523 (*minimum)++;
524 index++;
525 }
526 *maximum = *minimum;
527 while (index < skeleton.length() && skeleton[index] == '#') {
528 (*maximum)++;
529 index++;
530 }
531 return true;
532 }
533
534 // Return true if there are significant digits, false if not.
535 // The minimum significant digits is the number of '@' in the skeleton
536 // The maximum significant digits is the number of '#' after these '@'s plus
537 // the minimum significant digits.
538 // Ex: Skeleton as "@@@@@####### rounding-mode-half-up"
539 // 12345
540 // 6789012
541 // Set The minimum as 5 and maximum as 12.
SignificantDigitsFromSkeleton(const icu::UnicodeString & skeleton,int32_t * minimum,int32_t * maximum)542 bool JSNumberFormat::SignificantDigitsFromSkeleton(
543 const icu::UnicodeString& skeleton, int32_t* minimum, int32_t* maximum) {
544 icu::UnicodeString search("@");
545 int32_t index = skeleton.indexOf(search);
546 if (index < 0) return false;
547 *minimum = 1;
548 index++; // skip the first '@'
549 while (index < skeleton.length() && skeleton[index] == '@') {
550 (*minimum)++;
551 index++;
552 }
553 *maximum = *minimum;
554 while (index < skeleton.length() && skeleton[index] == '#') {
555 (*maximum)++;
556 index++;
557 }
558 return true;
559 }
560
561 namespace {
562
563 // Ex: percent .### rounding-mode-half-up
564 // Special case for "percent"
565 // Ex: "unit/milliliter-per-acre .### rounding-mode-half-up"
566 // should return "milliliter-per-acre".
567 // Ex: "unit/year .### rounding-mode-half-up" should return
568 // "year".
UnitFromSkeleton(const icu::UnicodeString & skeleton)569 std::string UnitFromSkeleton(const icu::UnicodeString& skeleton) {
570 std::string str;
571 str = skeleton.toUTF8String<std::string>(str);
572 std::string search("unit/");
573 size_t begin = str.find(search);
574 if (begin == str.npos) {
575 // Special case for "percent".
576 if (str.find("percent") != str.npos) {
577 return "percent";
578 }
579 return "";
580 }
581 // Ex:
582 // "unit/acre .### rounding-mode-half-up"
583 // b
584 // Ex:
585 // "unit/milliliter-per-acre .### rounding-mode-half-up"
586 // b
587 begin += search.size();
588 if (begin == str.npos) {
589 return "";
590 }
591 // Find the end of the subtype.
592 size_t end = str.find(" ", begin);
593 // Ex:
594 // "unit/acre .### rounding-mode-half-up"
595 // b e
596 // Ex:
597 // "unit/milliliter-per-acre .### rounding-mode-half-up"
598 // b e
599 if (end == str.npos) {
600 end = str.size();
601 }
602 return str.substr(begin, end - begin);
603 }
604
StyleFromSkeleton(const icu::UnicodeString & skeleton)605 Style StyleFromSkeleton(const icu::UnicodeString& skeleton) {
606 if (skeleton.indexOf("currency/") >= 0) {
607 return Style::CURRENCY;
608 }
609 if (skeleton.indexOf("percent") >= 0) {
610 // percent precision-integer rounding-mode-half-up scale/100
611 if (skeleton.indexOf("scale/100") >= 0) {
612 return Style::PERCENT;
613 } else {
614 return Style::UNIT;
615 }
616 }
617 // Before ICU68: "measure-unit/", since ICU68 "unit/"
618 if (skeleton.indexOf("unit/") >= 0) {
619 return Style::UNIT;
620 }
621 return Style::DECIMAL;
622 }
623
624 } // anonymous namespace
625
626 icu::number::LocalizedNumberFormatter
SetDigitOptionsToFormatter(const icu::number::LocalizedNumberFormatter & icu_number_formatter,const Intl::NumberFormatDigitOptions & digit_options)627 JSNumberFormat::SetDigitOptionsToFormatter(
628 const icu::number::LocalizedNumberFormatter& icu_number_formatter,
629 const Intl::NumberFormatDigitOptions& digit_options) {
630 icu::number::LocalizedNumberFormatter result = icu_number_formatter;
631 if (digit_options.minimum_integer_digits > 1) {
632 result = result.integerWidth(icu::number::IntegerWidth::zeroFillTo(
633 digit_options.minimum_integer_digits));
634 }
635
636 // Value -1 of minimum_significant_digits represent the roundingtype is
637 // "compact-rounding".
638 if (digit_options.minimum_significant_digits < 0) {
639 return result;
640 }
641 icu::number::Precision precision =
642 (digit_options.minimum_significant_digits > 0)
643 ? icu::number::Precision::minMaxSignificantDigits(
644 digit_options.minimum_significant_digits,
645 digit_options.maximum_significant_digits)
646 : icu::number::Precision::minMaxFraction(
647 digit_options.minimum_fraction_digits,
648 digit_options.maximum_fraction_digits);
649
650 return result.precision(precision);
651 }
652
653 // static
654 // ecma402 #sec-intl.numberformat.prototype.resolvedoptions
ResolvedOptions(Isolate * isolate,Handle<JSNumberFormat> number_format)655 Handle<JSObject> JSNumberFormat::ResolvedOptions(
656 Isolate* isolate, Handle<JSNumberFormat> number_format) {
657 Factory* factory = isolate->factory();
658
659 UErrorCode status = U_ZERO_ERROR;
660 icu::number::LocalizedNumberFormatter* icu_number_formatter =
661 number_format->icu_number_formatter().raw();
662 icu::UnicodeString skeleton = icu_number_formatter->toSkeleton(status);
663 CHECK(U_SUCCESS(status));
664
665 // 4. Let options be ! ObjectCreate(%ObjectPrototype%).
666 Handle<JSObject> options = factory->NewJSObject(isolate->object_function());
667
668 Handle<String> locale = Handle<String>(number_format->locale(), isolate);
669 const icu::UnicodeString numberingSystem_ustr =
670 NumberingSystemFromSkeleton(skeleton);
671 // 5. For each row of Table 4, except the header row, in table order, do
672 // Table 4: Resolved Options of NumberFormat Instances
673 // Internal Slot Property
674 // [[Locale]] "locale"
675 // [[NumberingSystem]] "numberingSystem"
676 // [[Style]] "style"
677 // [[Currency]] "currency"
678 // [[CurrencyDisplay]] "currencyDisplay"
679 // [[MinimumIntegerDigits]] "minimumIntegerDigits"
680 // [[MinimumFractionDigits]] "minimumFractionDigits"
681 // [[MaximumFractionDigits]] "maximumFractionDigits"
682 // [[MinimumSignificantDigits]] "minimumSignificantDigits"
683 // [[MaximumSignificantDigits]] "maximumSignificantDigits"
684 // [[UseGrouping]] "useGrouping"
685 CHECK(JSReceiver::CreateDataProperty(isolate, options,
686 factory->locale_string(), locale,
687 Just(kDontThrow))
688 .FromJust());
689 Handle<String> numberingSystem_string;
690 CHECK(Intl::ToString(isolate, numberingSystem_ustr)
691 .ToHandle(&numberingSystem_string));
692 CHECK(JSReceiver::CreateDataProperty(isolate, options,
693 factory->numberingSystem_string(),
694 numberingSystem_string, Just(kDontThrow))
695 .FromJust());
696 Style style = StyleFromSkeleton(skeleton);
697 CHECK(JSReceiver::CreateDataProperty(
698 isolate, options, factory->style_string(),
699 StyleAsString(isolate, style), Just(kDontThrow))
700 .FromJust());
701 const icu::UnicodeString currency_ustr = CurrencyFromSkeleton(skeleton);
702 if (!currency_ustr.isEmpty()) {
703 Handle<String> currency_string;
704 CHECK(Intl::ToString(isolate, currency_ustr).ToHandle(¤cy_string));
705 CHECK(JSReceiver::CreateDataProperty(isolate, options,
706 factory->currency_string(),
707 currency_string, Just(kDontThrow))
708 .FromJust());
709
710 CHECK(JSReceiver::CreateDataProperty(
711 isolate, options, factory->currencyDisplay_string(),
712 CurrencyDisplayString(isolate, skeleton), Just(kDontThrow))
713 .FromJust());
714 CHECK(JSReceiver::CreateDataProperty(
715 isolate, options, factory->currencySign_string(),
716 CurrencySignString(isolate, skeleton), Just(kDontThrow))
717 .FromJust());
718 }
719
720 if (style == Style::UNIT) {
721 std::string unit = UnitFromSkeleton(skeleton);
722 if (!unit.empty()) {
723 CHECK(JSReceiver::CreateDataProperty(
724 isolate, options, factory->unit_string(),
725 isolate->factory()->NewStringFromAsciiChecked(unit.c_str()),
726 Just(kDontThrow))
727 .FromJust());
728 }
729 CHECK(JSReceiver::CreateDataProperty(
730 isolate, options, factory->unitDisplay_string(),
731 UnitDisplayString(isolate, skeleton), Just(kDontThrow))
732 .FromJust());
733 }
734
735 CHECK(
736 JSReceiver::CreateDataProperty(
737 isolate, options, factory->minimumIntegerDigits_string(),
738 factory->NewNumberFromInt(MinimumIntegerDigitsFromSkeleton(skeleton)),
739 Just(kDontThrow))
740 .FromJust());
741
742 int32_t minimum = 0, maximum = 0;
743 if (SignificantDigitsFromSkeleton(skeleton, &minimum, &maximum)) {
744 CHECK(JSReceiver::CreateDataProperty(
745 isolate, options, factory->minimumSignificantDigits_string(),
746 factory->NewNumberFromInt(minimum), Just(kDontThrow))
747 .FromJust());
748 CHECK(JSReceiver::CreateDataProperty(
749 isolate, options, factory->maximumSignificantDigits_string(),
750 factory->NewNumberFromInt(maximum), Just(kDontThrow))
751 .FromJust());
752 } else {
753 FractionDigitsFromSkeleton(skeleton, &minimum, &maximum);
754 CHECK(JSReceiver::CreateDataProperty(
755 isolate, options, factory->minimumFractionDigits_string(),
756 factory->NewNumberFromInt(minimum), Just(kDontThrow))
757 .FromJust());
758 CHECK(JSReceiver::CreateDataProperty(
759 isolate, options, factory->maximumFractionDigits_string(),
760 factory->NewNumberFromInt(maximum), Just(kDontThrow))
761 .FromJust());
762 }
763
764 CHECK(JSReceiver::CreateDataProperty(
765 isolate, options, factory->useGrouping_string(),
766 factory->ToBoolean(UseGroupingFromSkeleton(skeleton)),
767 Just(kDontThrow))
768 .FromJust());
769 Notation notation = NotationFromSkeleton(skeleton);
770 CHECK(JSReceiver::CreateDataProperty(
771 isolate, options, factory->notation_string(),
772 NotationAsString(isolate, notation), Just(kDontThrow))
773 .FromJust());
774 // Only output compactDisplay when notation is compact.
775 if (notation == Notation::COMPACT) {
776 CHECK(JSReceiver::CreateDataProperty(
777 isolate, options, factory->compactDisplay_string(),
778 CompactDisplayString(isolate, skeleton), Just(kDontThrow))
779 .FromJust());
780 }
781 CHECK(JSReceiver::CreateDataProperty(
782 isolate, options, factory->signDisplay_string(),
783 SignDisplayString(isolate, skeleton), Just(kDontThrow))
784 .FromJust());
785 return options;
786 }
787
788 // ecma402/#sec-unwrapnumberformat
UnwrapNumberFormat(Isolate * isolate,Handle<JSReceiver> format_holder)789 MaybeHandle<JSNumberFormat> JSNumberFormat::UnwrapNumberFormat(
790 Isolate* isolate, Handle<JSReceiver> format_holder) {
791 // old code copy from NumberFormat::Unwrap that has no spec comment and
792 // compiled but fail unit tests.
793 Handle<Context> native_context =
794 Handle<Context>(isolate->context().native_context(), isolate);
795 Handle<JSFunction> constructor = Handle<JSFunction>(
796 JSFunction::cast(native_context->intl_number_format_function()), isolate);
797 Handle<Object> object;
798 ASSIGN_RETURN_ON_EXCEPTION(
799 isolate, object,
800 Intl::LegacyUnwrapReceiver(isolate, format_holder, constructor,
801 format_holder->IsJSNumberFormat()),
802 JSNumberFormat);
803 // 4. If ... or nf does not have an [[InitializedNumberFormat]] internal slot,
804 // then
805 if (!object->IsJSNumberFormat()) {
806 // a. Throw a TypeError exception.
807 THROW_NEW_ERROR(isolate,
808 NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
809 isolate->factory()->NewStringFromAsciiChecked(
810 "UnwrapNumberFormat")),
811 JSNumberFormat);
812 }
813 // 5. Return nf.
814 return Handle<JSNumberFormat>::cast(object);
815 }
816
817 // static
New(Isolate * isolate,Handle<Map> map,Handle<Object> locales,Handle<Object> options_obj,const char * service)818 MaybeHandle<JSNumberFormat> JSNumberFormat::New(Isolate* isolate,
819 Handle<Map> map,
820 Handle<Object> locales,
821 Handle<Object> options_obj,
822 const char* service) {
823 Factory* factory = isolate->factory();
824
825 // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
826 Maybe<std::vector<std::string>> maybe_requested_locales =
827 Intl::CanonicalizeLocaleList(isolate, locales);
828 MAYBE_RETURN(maybe_requested_locales, Handle<JSNumberFormat>());
829 std::vector<std::string> requested_locales =
830 maybe_requested_locales.FromJust();
831
832 // 2. If options is undefined, then
833 if (options_obj->IsUndefined(isolate)) {
834 // 2. a. Let options be ObjectCreate(null).
835 options_obj = isolate->factory()->NewJSObjectWithNullProto();
836 } else {
837 // 3. Else
838 // 3. a. Let options be ? ToObject(options).
839 ASSIGN_RETURN_ON_EXCEPTION(isolate, options_obj,
840 Object::ToObject(isolate, options_obj, service),
841 JSNumberFormat);
842 }
843
844 // At this point, options_obj can either be a JSObject or a JSProxy only.
845 Handle<JSReceiver> options = Handle<JSReceiver>::cast(options_obj);
846
847 // 4. Let opt be a new Record.
848 // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", «
849 // "lookup", "best fit" », "best fit").
850 // 6. Set opt.[[localeMatcher]] to matcher.
851 Maybe<Intl::MatcherOption> maybe_locale_matcher =
852 Intl::GetLocaleMatcher(isolate, options, service);
853 MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSNumberFormat>());
854 Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
855
856 std::unique_ptr<char[]> numbering_system_str = nullptr;
857 // 7. Let _numberingSystem_ be ? GetOption(_options_, `"numberingSystem"`,
858 // `"string"`, *undefined*, *undefined*).
859 Maybe<bool> maybe_numberingSystem = Intl::GetNumberingSystem(
860 isolate, options, service, &numbering_system_str);
861 // 8. If _numberingSystem_ is not *undefined*, then
862 // a. If _numberingSystem_ does not match the
863 // `(3*8alphanum) *("-" (3*8alphanum))` sequence, throw a *RangeError*
864 // exception.
865 MAYBE_RETURN(maybe_numberingSystem, MaybeHandle<JSNumberFormat>());
866
867 // 7. Let localeData be %NumberFormat%.[[LocaleData]].
868 // 8. Let r be ResolveLocale(%NumberFormat%.[[AvailableLocales]],
869 // requestedLocales, opt, %NumberFormat%.[[RelevantExtensionKeys]],
870 // localeData).
871 std::set<std::string> relevant_extension_keys{"nu"};
872 Maybe<Intl::ResolvedLocale> maybe_resolve_locale =
873 Intl::ResolveLocale(isolate, JSNumberFormat::GetAvailableLocales(),
874 requested_locales, matcher, relevant_extension_keys);
875 if (maybe_resolve_locale.IsNothing()) {
876 THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
877 JSNumberFormat);
878 }
879 Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();
880
881 icu::Locale icu_locale = r.icu_locale;
882 UErrorCode status = U_ZERO_ERROR;
883 if (numbering_system_str != nullptr) {
884 auto nu_extension_it = r.extensions.find("nu");
885 if (nu_extension_it != r.extensions.end() &&
886 nu_extension_it->second != numbering_system_str.get()) {
887 icu_locale.setUnicodeKeywordValue("nu", nullptr, status);
888 CHECK(U_SUCCESS(status));
889 }
890 }
891
892 // 9. Set numberFormat.[[Locale]] to r.[[locale]].
893 Maybe<std::string> maybe_locale_str = Intl::ToLanguageTag(icu_locale);
894 MAYBE_RETURN(maybe_locale_str, MaybeHandle<JSNumberFormat>());
895 Handle<String> locale_str = isolate->factory()->NewStringFromAsciiChecked(
896 maybe_locale_str.FromJust().c_str());
897
898 if (numbering_system_str != nullptr &&
899 Intl::IsValidNumberingSystem(numbering_system_str.get())) {
900 icu_locale.setUnicodeKeywordValue("nu", numbering_system_str.get(), status);
901 CHECK(U_SUCCESS(status));
902 }
903
904 std::string numbering_system = Intl::GetNumberingSystem(icu_locale);
905
906 // 11. Let dataLocale be r.[[dataLocale]].
907
908 icu::number::LocalizedNumberFormatter icu_number_formatter =
909 icu::number::NumberFormatter::withLocale(icu_locale)
910 .roundingMode(UNUM_ROUND_HALFUP);
911
912 // For 'latn' numbering system, skip the adoptSymbols which would cause
913 // 10.1%-13.7% of regression of JSTests/Intl-NewIntlNumberFormat
914 // See crbug/1052751 so we skip calling adoptSymbols and depending on the
915 // default instead.
916 if (!numbering_system.empty() && numbering_system != "latn") {
917 icu_number_formatter = icu_number_formatter.adoptSymbols(
918 icu::NumberingSystem::createInstanceByName(numbering_system.c_str(),
919 status));
920 CHECK(U_SUCCESS(status));
921 }
922
923 // 3. Let style be ? GetOption(options, "style", "string", « "decimal",
924 // "percent", "currency", "unit" », "decimal").
925
926 Maybe<Style> maybe_style = Intl::GetStringOption<Style>(
927 isolate, options, "style", service,
928 {"decimal", "percent", "currency", "unit"},
929 {Style::DECIMAL, Style::PERCENT, Style::CURRENCY, Style::UNIT},
930 Style::DECIMAL);
931 MAYBE_RETURN(maybe_style, MaybeHandle<JSNumberFormat>());
932 Style style = maybe_style.FromJust();
933
934 // 4. Set intlObj.[[Style]] to style.
935
936 // 5. Let currency be ? GetOption(options, "currency", "string", undefined,
937 // undefined).
938 std::unique_ptr<char[]> currency_cstr;
939 const std::vector<const char*> empty_values = {};
940 Maybe<bool> found_currency = Intl::GetStringOption(
941 isolate, options, "currency", empty_values, service, ¤cy_cstr);
942 MAYBE_RETURN(found_currency, MaybeHandle<JSNumberFormat>());
943
944 std::string currency;
945 // 6. If currency is not undefined, then
946 if (found_currency.FromJust()) {
947 DCHECK_NOT_NULL(currency_cstr.get());
948 currency = currency_cstr.get();
949 // 6. a. If the result of IsWellFormedCurrencyCode(currency) is false,
950 // throw a RangeError exception.
951 if (!IsWellFormedCurrencyCode(currency)) {
952 THROW_NEW_ERROR(
953 isolate,
954 NewRangeError(MessageTemplate::kInvalid,
955 factory->NewStringFromStaticChars("currency code"),
956 factory->NewStringFromAsciiChecked(currency.c_str())),
957 JSNumberFormat);
958 }
959 } else {
960 // 7. If style is "currency" and currency is undefined, throw a TypeError
961 // exception.
962 if (style == Style::CURRENCY) {
963 THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kCurrencyCode),
964 JSNumberFormat);
965 }
966 }
967 // 8. Let currencyDisplay be ? GetOption(options, "currencyDisplay",
968 // "string", « "code", "symbol", "name", "narrowSymbol" », "symbol").
969 Maybe<CurrencyDisplay> maybe_currency_display =
970 Intl::GetStringOption<CurrencyDisplay>(
971 isolate, options, "currencyDisplay", service,
972 {"code", "symbol", "name", "narrowSymbol"},
973 {CurrencyDisplay::CODE, CurrencyDisplay::SYMBOL,
974 CurrencyDisplay::NAME, CurrencyDisplay::NARROW_SYMBOL},
975 CurrencyDisplay::SYMBOL);
976 MAYBE_RETURN(maybe_currency_display, MaybeHandle<JSNumberFormat>());
977 CurrencyDisplay currency_display = maybe_currency_display.FromJust();
978
979 CurrencySign currency_sign = CurrencySign::STANDARD;
980 // 9. Let currencySign be ? GetOption(options, "currencySign", "string", «
981 // "standard", "accounting" », "standard").
982 Maybe<CurrencySign> maybe_currency_sign = Intl::GetStringOption<CurrencySign>(
983 isolate, options, "currencySign", service, {"standard", "accounting"},
984 {CurrencySign::STANDARD, CurrencySign::ACCOUNTING},
985 CurrencySign::STANDARD);
986 MAYBE_RETURN(maybe_currency_sign, MaybeHandle<JSNumberFormat>());
987 currency_sign = maybe_currency_sign.FromJust();
988
989 // 10. Let unit be ? GetOption(options, "unit", "string", undefined,
990 // undefined).
991 std::unique_ptr<char[]> unit_cstr;
992 Maybe<bool> found_unit = Intl::GetStringOption(
993 isolate, options, "unit", empty_values, service, &unit_cstr);
994 MAYBE_RETURN(found_unit, MaybeHandle<JSNumberFormat>());
995
996 std::pair<icu::MeasureUnit, icu::MeasureUnit> unit_pair;
997 // 11. If unit is not undefined, then
998 if (found_unit.FromJust()) {
999 DCHECK_NOT_NULL(unit_cstr.get());
1000 std::string unit = unit_cstr.get();
1001 // 11.a If the result of IsWellFormedUnitIdentifier(unit) is false, throw a
1002 // RangeError exception.
1003 Maybe<std::pair<icu::MeasureUnit, icu::MeasureUnit>> maybe_wellformed_unit =
1004 IsWellFormedUnitIdentifier(isolate, unit);
1005 if (maybe_wellformed_unit.IsNothing()) {
1006 THROW_NEW_ERROR(
1007 isolate,
1008 NewRangeError(MessageTemplate::kInvalidUnit,
1009 factory->NewStringFromAsciiChecked(service),
1010 factory->NewStringFromAsciiChecked(unit.c_str())),
1011 JSNumberFormat);
1012 }
1013 unit_pair = maybe_wellformed_unit.FromJust();
1014 } else {
1015 // 12. If style is "unit" and unit is undefined, throw a TypeError
1016 // exception.
1017 if (style == Style::UNIT) {
1018 THROW_NEW_ERROR(isolate,
1019 NewTypeError(MessageTemplate::kInvalidUnit,
1020 factory->NewStringFromAsciiChecked(service),
1021 factory->empty_string()),
1022 JSNumberFormat);
1023 }
1024 }
1025
1026 // 13. Let unitDisplay be ? GetOption(options, "unitDisplay", "string", «
1027 // "short", "narrow", "long" », "short").
1028 Maybe<UnitDisplay> maybe_unit_display = Intl::GetStringOption<UnitDisplay>(
1029 isolate, options, "unitDisplay", service, {"short", "narrow", "long"},
1030 {UnitDisplay::SHORT, UnitDisplay::NARROW, UnitDisplay::LONG},
1031 UnitDisplay::SHORT);
1032 MAYBE_RETURN(maybe_unit_display, MaybeHandle<JSNumberFormat>());
1033 UnitDisplay unit_display = maybe_unit_display.FromJust();
1034
1035 // 14. If style is "currency", then
1036 icu::UnicodeString currency_ustr;
1037 if (style == Style::CURRENCY) {
1038 // 14.a. If currency is undefined, throw a TypeError exception.
1039 if (!found_currency.FromJust()) {
1040 THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kCurrencyCode),
1041 JSNumberFormat);
1042 }
1043 // 14.a. Let currency be the result of converting currency to upper case as
1044 // specified in 6.1
1045 std::transform(currency.begin(), currency.end(), currency.begin(), toupper);
1046 currency_ustr = currency.c_str();
1047
1048 // 14.b. Set numberFormat.[[Currency]] to currency.
1049 if (!currency_ustr.isEmpty()) {
1050 Handle<String> currency_string;
1051 ASSIGN_RETURN_ON_EXCEPTION(isolate, currency_string,
1052 Intl::ToString(isolate, currency_ustr),
1053 JSNumberFormat);
1054
1055 icu_number_formatter = icu_number_formatter.unit(
1056 icu::CurrencyUnit(currency_ustr.getBuffer(), status));
1057 CHECK(U_SUCCESS(status));
1058 // 14.c Set intlObj.[[CurrencyDisplay]] to currencyDisplay.
1059 // The default unitWidth is SHORT in ICU and that mapped from
1060 // Symbol so we can skip the setting for optimization.
1061 if (currency_display != CurrencyDisplay::SYMBOL) {
1062 icu_number_formatter = icu_number_formatter.unitWidth(
1063 ToUNumberUnitWidth(currency_display));
1064 }
1065 CHECK(U_SUCCESS(status));
1066 }
1067 }
1068
1069 // 15. If style is "unit", then
1070 if (style == Style::UNIT) {
1071 // Track newer style "unit".
1072 isolate->CountUsage(v8::Isolate::UseCounterFeature::kNumberFormatStyleUnit);
1073
1074 icu::MeasureUnit none = icu::MeasureUnit();
1075 // 13.b Set intlObj.[[Unit]] to unit.
1076 if (unit_pair.first != none) {
1077 icu_number_formatter = icu_number_formatter.unit(unit_pair.first);
1078 }
1079 if (unit_pair.second != none) {
1080 icu_number_formatter = icu_number_formatter.perUnit(unit_pair.second);
1081 }
1082
1083 // The default unitWidth is SHORT in ICU and that mapped from
1084 // Symbol so we can skip the setting for optimization.
1085 if (unit_display != UnitDisplay::SHORT) {
1086 icu_number_formatter =
1087 icu_number_formatter.unitWidth(ToUNumberUnitWidth(unit_display));
1088 }
1089 }
1090
1091 if (style == Style::PERCENT) {
1092 icu_number_formatter =
1093 icu_number_formatter.unit(icu::MeasureUnit::getPercent())
1094 .scale(icu::number::Scale::powerOfTen(2));
1095 }
1096
1097 // 23. If style is "currency", then
1098 int mnfd_default, mxfd_default;
1099 if (style == Style::CURRENCY) {
1100 // b. Let cDigits be CurrencyDigits(currency).
1101 int c_digits = CurrencyDigits(currency_ustr);
1102 // c. Let mnfdDefault be cDigits.
1103 // d. Let mxfdDefault be cDigits.
1104 mnfd_default = c_digits;
1105 mxfd_default = c_digits;
1106 // 24. Else,
1107 } else {
1108 // a. Let mnfdDefault be 0.
1109 mnfd_default = 0;
1110 // b. If style is "percent", then
1111 if (style == Style::PERCENT) {
1112 // i. Let mxfdDefault be 0.
1113 mxfd_default = 0;
1114 } else {
1115 // c. Else,
1116 // i. Let mxfdDefault be 3.
1117 mxfd_default = 3;
1118 }
1119 }
1120
1121 Notation notation = Notation::STANDARD;
1122 // 25. Let notation be ? GetOption(options, "notation", "string", «
1123 // "standard", "scientific", "engineering", "compact" », "standard").
1124 Maybe<Notation> maybe_notation = Intl::GetStringOption<Notation>(
1125 isolate, options, "notation", service,
1126 {"standard", "scientific", "engineering", "compact"},
1127 {Notation::STANDARD, Notation::SCIENTIFIC, Notation::ENGINEERING,
1128 Notation::COMPACT},
1129 Notation::STANDARD);
1130 MAYBE_RETURN(maybe_notation, MaybeHandle<JSNumberFormat>());
1131 notation = maybe_notation.FromJust();
1132
1133 // 27. Perform ? SetNumberFormatDigitOptions(numberFormat, options,
1134 // mnfdDefault, mxfdDefault).
1135 Maybe<Intl::NumberFormatDigitOptions> maybe_digit_options =
1136 Intl::SetNumberFormatDigitOptions(isolate, options, mnfd_default,
1137 mxfd_default,
1138 notation == Notation::COMPACT);
1139 MAYBE_RETURN(maybe_digit_options, Handle<JSNumberFormat>());
1140 Intl::NumberFormatDigitOptions digit_options = maybe_digit_options.FromJust();
1141 icu_number_formatter = JSNumberFormat::SetDigitOptionsToFormatter(
1142 icu_number_formatter, digit_options);
1143
1144 // 28. Let compactDisplay be ? GetOption(options, "compactDisplay",
1145 // "string", « "short", "long" », "short").
1146 Maybe<CompactDisplay> maybe_compact_display =
1147 Intl::GetStringOption<CompactDisplay>(
1148 isolate, options, "compactDisplay", service, {"short", "long"},
1149 {CompactDisplay::SHORT, CompactDisplay::LONG}, CompactDisplay::SHORT);
1150 MAYBE_RETURN(maybe_compact_display, MaybeHandle<JSNumberFormat>());
1151 CompactDisplay compact_display = maybe_compact_display.FromJust();
1152
1153 // 26. Set numberFormat.[[Notation]] to notation.
1154 // The default notation in ICU is Simple, which mapped from STANDARD
1155 // so we can skip setting it.
1156 if (notation != Notation::STANDARD) {
1157 icu_number_formatter =
1158 icu_number_formatter.notation(ToICUNotation(notation, compact_display));
1159 }
1160 // 30. Let useGrouping be ? GetOption(options, "useGrouping", "boolean",
1161 // undefined, true).
1162 bool use_grouping = true;
1163 Maybe<bool> found_use_grouping = Intl::GetBoolOption(
1164 isolate, options, "useGrouping", service, &use_grouping);
1165 MAYBE_RETURN(found_use_grouping, MaybeHandle<JSNumberFormat>());
1166 // 31. Set numberFormat.[[UseGrouping]] to useGrouping.
1167 if (!use_grouping) {
1168 icu_number_formatter = icu_number_formatter.grouping(
1169 UNumberGroupingStrategy::UNUM_GROUPING_OFF);
1170 }
1171
1172 // 32. Let signDisplay be ? GetOption(options, "signDisplay", "string", «
1173 // "auto", "never", "always", "exceptZero" », "auto").
1174 Maybe<SignDisplay> maybe_sign_display = Intl::GetStringOption<SignDisplay>(
1175 isolate, options, "signDisplay", service,
1176 {"auto", "never", "always", "exceptZero"},
1177 {SignDisplay::AUTO, SignDisplay::NEVER, SignDisplay::ALWAYS,
1178 SignDisplay::EXCEPT_ZERO},
1179 SignDisplay::AUTO);
1180 MAYBE_RETURN(maybe_sign_display, MaybeHandle<JSNumberFormat>());
1181 SignDisplay sign_display = maybe_sign_display.FromJust();
1182
1183 // 33. Set numberFormat.[[SignDisplay]] to signDisplay.
1184 // The default sign in ICU is UNUM_SIGN_AUTO which is mapped from
1185 // SignDisplay::AUTO and CurrencySign::STANDARD so we can skip setting
1186 // under that values for optimization.
1187 if (sign_display != SignDisplay::AUTO ||
1188 currency_sign != CurrencySign::STANDARD) {
1189 icu_number_formatter = icu_number_formatter.sign(
1190 ToUNumberSignDisplay(sign_display, currency_sign));
1191 }
1192
1193 // 25. Let dataLocaleData be localeData.[[<dataLocale>]].
1194 //
1195 // 26. Let patterns be dataLocaleData.[[patterns]].
1196 //
1197 // 27. Assert: patterns is a record (see 11.3.3).
1198 //
1199 // 28. Let stylePatterns be patterns.[[<style>]].
1200 //
1201 // 29. Set numberFormat.[[PositivePattern]] to
1202 // stylePatterns.[[positivePattern]].
1203 //
1204 // 30. Set numberFormat.[[NegativePattern]] to
1205 // stylePatterns.[[negativePattern]].
1206 //
1207 Handle<Managed<icu::number::LocalizedNumberFormatter>>
1208 managed_number_formatter =
1209 Managed<icu::number::LocalizedNumberFormatter>::FromRawPtr(
1210 isolate, 0,
1211 new icu::number::LocalizedNumberFormatter(icu_number_formatter));
1212
1213 // Now all properties are ready, so we can allocate the result object.
1214 Handle<JSNumberFormat> number_format = Handle<JSNumberFormat>::cast(
1215 isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
1216 DisallowHeapAllocation no_gc;
1217 number_format->set_locale(*locale_str);
1218
1219 number_format->set_icu_number_formatter(*managed_number_formatter);
1220 number_format->set_bound_format(*factory->undefined_value());
1221
1222 // 31. Return numberFormat.
1223 return number_format;
1224 }
1225
1226 namespace {
IcuFormatNumber(Isolate * isolate,const icu::number::LocalizedNumberFormatter & number_format,Handle<Object> numeric_obj,icu::number::FormattedNumber * formatted)1227 Maybe<bool> IcuFormatNumber(
1228 Isolate* isolate,
1229 const icu::number::LocalizedNumberFormatter& number_format,
1230 Handle<Object> numeric_obj, icu::number::FormattedNumber* formatted) {
1231 // If it is BigInt, handle it differently.
1232 UErrorCode status = U_ZERO_ERROR;
1233 if (numeric_obj->IsBigInt()) {
1234 Handle<BigInt> big_int = Handle<BigInt>::cast(numeric_obj);
1235 Handle<String> big_int_string;
1236 ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, big_int_string,
1237 BigInt::ToString(isolate, big_int),
1238 Nothing<bool>());
1239 *formatted = number_format.formatDecimal(
1240 {big_int_string->ToCString().get(), big_int_string->length()}, status);
1241 } else {
1242 double number = numeric_obj->IsNaN()
1243 ? std::numeric_limits<double>::quiet_NaN()
1244 : numeric_obj->Number();
1245 *formatted = number_format.formatDouble(number, status);
1246 }
1247 if (U_FAILURE(status)) {
1248 // This happen because of icu data trimming trim out "unit".
1249 // See https://bugs.chromium.org/p/v8/issues/detail?id=8641
1250 THROW_NEW_ERROR_RETURN_VALUE(
1251 isolate, NewTypeError(MessageTemplate::kIcuError), Nothing<bool>());
1252 }
1253 return Just(true);
1254 }
1255
1256 } // namespace
1257
FormatNumeric(Isolate * isolate,const icu::number::LocalizedNumberFormatter & number_format,Handle<Object> numeric_obj)1258 MaybeHandle<String> JSNumberFormat::FormatNumeric(
1259 Isolate* isolate,
1260 const icu::number::LocalizedNumberFormatter& number_format,
1261 Handle<Object> numeric_obj) {
1262 DCHECK(numeric_obj->IsNumeric());
1263
1264 icu::number::FormattedNumber formatted;
1265 Maybe<bool> maybe_format =
1266 IcuFormatNumber(isolate, number_format, numeric_obj, &formatted);
1267 MAYBE_RETURN(maybe_format, Handle<String>());
1268 UErrorCode status = U_ZERO_ERROR;
1269 icu::UnicodeString result = formatted.toString(status);
1270 if (U_FAILURE(status)) {
1271 THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), String);
1272 }
1273 return Intl::ToString(isolate, result);
1274 }
1275
1276 namespace {
1277
cmp_NumberFormatSpan(const NumberFormatSpan & a,const NumberFormatSpan & b)1278 bool cmp_NumberFormatSpan(const NumberFormatSpan& a,
1279 const NumberFormatSpan& b) {
1280 // Regions that start earlier should be encountered earlier.
1281 if (a.begin_pos < b.begin_pos) return true;
1282 if (a.begin_pos > b.begin_pos) return false;
1283 // For regions that start in the same place, regions that last longer should
1284 // be encountered earlier.
1285 if (a.end_pos < b.end_pos) return false;
1286 if (a.end_pos > b.end_pos) return true;
1287 // For regions that are exactly the same, one of them must be the "literal"
1288 // backdrop we added, which has a field_id of -1, so consider higher field_ids
1289 // to be later.
1290 return a.field_id < b.field_id;
1291 }
1292
1293 } // namespace
1294
1295 // Flattens a list of possibly-overlapping "regions" to a list of
1296 // non-overlapping "parts". At least one of the input regions must span the
1297 // entire space of possible indexes. The regions parameter will sorted in-place
1298 // according to some criteria; this is done for performance to avoid copying the
1299 // input.
FlattenRegionsToParts(std::vector<NumberFormatSpan> * regions)1300 std::vector<NumberFormatSpan> FlattenRegionsToParts(
1301 std::vector<NumberFormatSpan>* regions) {
1302 // The intention of this algorithm is that it's used to translate ICU "fields"
1303 // to JavaScript "parts" of a formatted string. Each ICU field and JavaScript
1304 // part has an integer field_id, which corresponds to something like "grouping
1305 // separator", "fraction", or "percent sign", and has a begin and end
1306 // position. Here's a diagram of:
1307
1308 // var nf = new Intl.NumberFormat(['de'], {style:'currency',currency:'EUR'});
1309 // nf.formatToParts(123456.78);
1310
1311 // : 6
1312 // input regions: 0000000211 7
1313 // ('-' means -1): ------------
1314 // formatted string: "123.456,78 €"
1315 // output parts: 0006000211-7
1316
1317 // To illustrate the requirements of this algorithm, here's a contrived and
1318 // convoluted example of inputs and expected outputs:
1319
1320 // : 4
1321 // : 22 33 3
1322 // : 11111 22
1323 // input regions: 0000000 111
1324 // : ------------
1325 // formatted string: "abcdefghijkl"
1326 // output parts: 0221340--231
1327 // (The characters in the formatted string are irrelevant to this function.)
1328
1329 // We arrange the overlapping input regions like a mountain range where
1330 // smaller regions are "on top" of larger regions, and we output a birds-eye
1331 // view of the mountains, so that smaller regions take priority over larger
1332 // regions.
1333 std::sort(regions->begin(), regions->end(), cmp_NumberFormatSpan);
1334 std::vector<size_t> overlapping_region_index_stack;
1335 // At least one item in regions must be a region spanning the entire string.
1336 // Due to the sorting above, the first item in the vector will be one of them.
1337 overlapping_region_index_stack.push_back(0);
1338 NumberFormatSpan top_region = regions->at(0);
1339 size_t region_iterator = 1;
1340 int32_t entire_size = top_region.end_pos;
1341
1342 std::vector<NumberFormatSpan> out_parts;
1343
1344 // The "climber" is a cursor that advances from left to right climbing "up"
1345 // and "down" the mountains. Whenever the climber moves to the right, that
1346 // represents an item of output.
1347 int32_t climber = 0;
1348 while (climber < entire_size) {
1349 int32_t next_region_begin_pos;
1350 if (region_iterator < regions->size()) {
1351 next_region_begin_pos = regions->at(region_iterator).begin_pos;
1352 } else {
1353 // finish off the rest of the input by proceeding to the end.
1354 next_region_begin_pos = entire_size;
1355 }
1356
1357 if (climber < next_region_begin_pos) {
1358 while (top_region.end_pos < next_region_begin_pos) {
1359 if (climber < top_region.end_pos) {
1360 // step down
1361 out_parts.push_back(NumberFormatSpan(top_region.field_id, climber,
1362 top_region.end_pos));
1363 climber = top_region.end_pos;
1364 } else {
1365 // drop down
1366 }
1367 overlapping_region_index_stack.pop_back();
1368 top_region = regions->at(overlapping_region_index_stack.back());
1369 }
1370 if (climber < next_region_begin_pos) {
1371 // cross a plateau/mesa/valley
1372 out_parts.push_back(NumberFormatSpan(top_region.field_id, climber,
1373 next_region_begin_pos));
1374 climber = next_region_begin_pos;
1375 }
1376 }
1377 if (region_iterator < regions->size()) {
1378 overlapping_region_index_stack.push_back(region_iterator++);
1379 top_region = regions->at(overlapping_region_index_stack.back());
1380 }
1381 }
1382 return out_parts;
1383 }
1384
1385 namespace {
ConstructParts(Isolate * isolate,icu::number::FormattedNumber * formatted,Handle<JSArray> result,int start_index,Handle<Object> numeric_obj,bool style_is_unit)1386 Maybe<int> ConstructParts(Isolate* isolate,
1387 icu::number::FormattedNumber* formatted,
1388 Handle<JSArray> result, int start_index,
1389 Handle<Object> numeric_obj, bool style_is_unit) {
1390 UErrorCode status = U_ZERO_ERROR;
1391 icu::UnicodeString formatted_text = formatted->toString(status);
1392 if (U_FAILURE(status)) {
1393 THROW_NEW_ERROR_RETURN_VALUE(
1394 isolate, NewTypeError(MessageTemplate::kIcuError), Nothing<int>());
1395 }
1396 DCHECK(numeric_obj->IsNumeric());
1397 int32_t length = formatted_text.length();
1398 int index = start_index;
1399 if (length == 0) return Just(index);
1400
1401 std::vector<NumberFormatSpan> regions;
1402 // Add a "literal" backdrop for the entire string. This will be used if no
1403 // other region covers some part of the formatted string. It's possible
1404 // there's another field with exactly the same begin and end as this backdrop,
1405 // in which case the backdrop's field_id of -1 will give it lower priority.
1406 regions.push_back(NumberFormatSpan(-1, 0, formatted_text.length()));
1407
1408 {
1409 icu::ConstrainedFieldPosition cfp;
1410 cfp.constrainCategory(UFIELD_CATEGORY_NUMBER);
1411 while (formatted->nextPosition(cfp, status)) {
1412 regions.push_back(
1413 NumberFormatSpan(cfp.getField(), cfp.getStart(), cfp.getLimit()));
1414 }
1415 }
1416
1417 std::vector<NumberFormatSpan> parts = FlattenRegionsToParts(®ions);
1418
1419 for (auto it = parts.begin(); it < parts.end(); it++) {
1420 NumberFormatSpan part = *it;
1421 Handle<String> field_type_string = isolate->factory()->literal_string();
1422 if (part.field_id != -1) {
1423 if (style_is_unit && static_cast<UNumberFormatFields>(part.field_id) ==
1424 UNUM_PERCENT_FIELD) {
1425 // Special case when style is unit.
1426 field_type_string = isolate->factory()->unit_string();
1427 } else {
1428 field_type_string =
1429 Intl::NumberFieldToType(isolate, numeric_obj, part.field_id);
1430 }
1431 }
1432 Handle<String> substring;
1433 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1434 isolate, substring,
1435 Intl::ToString(isolate, formatted_text, part.begin_pos, part.end_pos),
1436 Nothing<int>());
1437 Intl::AddElement(isolate, result, index, field_type_string, substring);
1438 ++index;
1439 }
1440 JSObject::ValidateElements(*result);
1441 return Just(index);
1442 }
1443
1444 } // namespace
1445
FormatToParts(Isolate * isolate,Handle<JSNumberFormat> number_format,Handle<Object> numeric_obj)1446 MaybeHandle<JSArray> JSNumberFormat::FormatToParts(
1447 Isolate* isolate, Handle<JSNumberFormat> number_format,
1448 Handle<Object> numeric_obj) {
1449 CHECK(numeric_obj->IsNumeric());
1450 Factory* factory = isolate->factory();
1451 icu::number::LocalizedNumberFormatter* fmt =
1452 number_format->icu_number_formatter().raw();
1453 CHECK_NOT_NULL(fmt);
1454
1455 icu::number::FormattedNumber formatted;
1456 Maybe<bool> maybe_format =
1457 IcuFormatNumber(isolate, *fmt, numeric_obj, &formatted);
1458 MAYBE_RETURN(maybe_format, Handle<JSArray>());
1459 UErrorCode status = U_ZERO_ERROR;
1460
1461 bool style_is_unit =
1462 Style::UNIT == StyleFromSkeleton(fmt->toSkeleton(status));
1463 CHECK(U_SUCCESS(status));
1464
1465 Handle<JSArray> result = factory->NewJSArray(0);
1466 Maybe<int> maybe_format_to_parts = ConstructParts(
1467 isolate, &formatted, result, 0, numeric_obj, style_is_unit);
1468 MAYBE_RETURN(maybe_format_to_parts, Handle<JSArray>());
1469
1470 return result;
1471 }
1472
1473 namespace {
1474
1475 struct CheckNumberElements {
keyv8::internal::__anon164e43c60611::CheckNumberElements1476 static const char* key() { return "NumberElements"; }
pathv8::internal::__anon164e43c60611::CheckNumberElements1477 static const char* path() { return nullptr; }
1478 };
1479
1480 } // namespace
1481
GetAvailableLocales()1482 const std::set<std::string>& JSNumberFormat::GetAvailableLocales() {
1483 static base::LazyInstance<Intl::AvailableLocales<CheckNumberElements>>::type
1484 available_locales = LAZY_INSTANCE_INITIALIZER;
1485 return available_locales.Pointer()->Get();
1486 }
1487
1488 } // namespace internal
1489 } // namespace v8
1490