• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&currency_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, &currency_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(&regions);
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