0da85cc03c284f3bf93161adba098c9ccfc90823
[WebKit-https.git] / Source / JavaScriptCore / runtime / IntlNumberFormat.cpp
1 /*
2  * Copyright (C) 2015 Andy VanWagoner (thetalecrafter@gmail.com)
3  * Copyright (C) 2016 Sukolsak Sakshuwong (sukolsak@gmail.com)
4  * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
19  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25  * THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29 #include "IntlNumberFormat.h"
30
31 #if ENABLE(INTL)
32
33 #include "Error.h"
34 #include "IntlNumberFormatConstructor.h"
35 #include "IntlObject.h"
36 #include "JSBoundFunction.h"
37 #include "JSCInlines.h"
38 #include "ObjectConstructor.h"
39
40 namespace JSC {
41
42 const ClassInfo IntlNumberFormat::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlNumberFormat) };
43
44 static const char* const relevantExtensionKeys[1] = { "nu" };
45
46 void IntlNumberFormat::UNumberFormatDeleter::operator()(UNumberFormat* numberFormat) const
47 {
48     if (numberFormat)
49         unum_close(numberFormat);
50 }
51
52 IntlNumberFormat* IntlNumberFormat::create(VM& vm, Structure* structure)
53 {
54     IntlNumberFormat* format = new (NotNull, allocateCell<IntlNumberFormat>(vm.heap)) IntlNumberFormat(vm, structure);
55     format->finishCreation(vm);
56     return format;
57 }
58
59 Structure* IntlNumberFormat::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
60 {
61     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
62 }
63
64 IntlNumberFormat::IntlNumberFormat(VM& vm, Structure* structure)
65     : JSDestructibleObject(vm, structure)
66 {
67 }
68
69 void IntlNumberFormat::finishCreation(VM& vm)
70 {
71     Base::finishCreation(vm);
72     ASSERT(inherits(vm, info()));
73 }
74
75 void IntlNumberFormat::destroy(JSCell* cell)
76 {
77     static_cast<IntlNumberFormat*>(cell)->IntlNumberFormat::~IntlNumberFormat();
78 }
79
80 void IntlNumberFormat::visitChildren(JSCell* cell, SlotVisitor& visitor)
81 {
82     IntlNumberFormat* thisObject = jsCast<IntlNumberFormat*>(cell);
83     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
84
85     Base::visitChildren(thisObject, visitor);
86
87     visitor.append(thisObject->m_boundFormat);
88 }
89
90 static Vector<String> localeData(const String& locale, size_t keyIndex)
91 {
92     // 9.1 Internal slots of Service Constructors & 11.2.3 Internal slots (ECMA-402 2.0)
93     ASSERT_UNUSED(keyIndex, !keyIndex); // The index of the extension key "nu" in relevantExtensionKeys is 0.
94     return numberingSystemsForLocale(locale);
95 }
96
97 static inline unsigned computeCurrencySortKey(const String& currency)
98 {
99     ASSERT(currency.length() == 3);
100     ASSERT(currency.isAllSpecialCharacters<isASCIIUpper>());
101     return (currency[0] << 16) + (currency[1] << 8) + currency[2];
102 }
103
104 static inline unsigned computeCurrencySortKey(const char* currency)
105 {
106     ASSERT(strlen(currency) == 3);
107     ASSERT(isAllSpecialCharacters<isASCIIUpper>(currency, 3));
108     return (currency[0] << 16) + (currency[1] << 8) + currency[2];
109 }
110
111 static unsigned extractCurrencySortKey(std::pair<const char*, unsigned>* currencyMinorUnit)
112 {
113     return computeCurrencySortKey(currencyMinorUnit->first);
114 }
115
116 static unsigned computeCurrencyDigits(const String& currency)
117 {
118     // 11.1.1 The abstract operation CurrencyDigits (currency)
119     // "If the ISO 4217 currency and funds code list contains currency as an alphabetic code,
120     // then return the minor unit value corresponding to the currency from the list; else return 2.
121     std::pair<const char*, unsigned> currencyMinorUnits[] = {
122         { "BHD", 3 },
123         { "BIF", 0 },
124         { "BYR", 0 },
125         { "CLF", 4 },
126         { "CLP", 0 },
127         { "DJF", 0 },
128         { "GNF", 0 },
129         { "IQD", 3 },
130         { "ISK", 0 },
131         { "JOD", 3 },
132         { "JPY", 0 },
133         { "KMF", 0 },
134         { "KRW", 0 },
135         { "KWD", 3 },
136         { "LYD", 3 },
137         { "OMR", 3 },
138         { "PYG", 0 },
139         { "RWF", 0 },
140         { "TND", 3 },
141         { "UGX", 0 },
142         { "UYI", 0 },
143         { "VND", 0 },
144         { "VUV", 0 },
145         { "XAF", 0 },
146         { "XOF", 0 },
147         { "XPF", 0 }
148     };
149     auto* currencyMinorUnit = tryBinarySearch<std::pair<const char*, unsigned>>(currencyMinorUnits, WTF_ARRAY_LENGTH(currencyMinorUnits), computeCurrencySortKey(currency), extractCurrencySortKey);
150     if (currencyMinorUnit)
151         return currencyMinorUnit->second;
152     return 2;
153 }
154
155 void IntlNumberFormat::initializeNumberFormat(ExecState& state, JSValue locales, JSValue optionsValue)
156 {
157     // 11.1.1 InitializeNumberFormat (numberFormat, locales, options) (ECMA-402 2.0)
158     VM& vm = state.vm();
159     auto scope = DECLARE_THROW_SCOPE(vm);
160
161     // 1. If numberFormat has an [[initializedIntlObject]] internal slot with value true, throw a TypeError exception.
162     // 2. Set numberFormat.[[initializedIntlObject]] to true.
163
164     // 3. Let requestedLocales be CanonicalizeLocaleList(locales).
165     auto requestedLocales = canonicalizeLocaleList(state, locales);
166     // 4. ReturnIfAbrupt(requestedLocales).
167     RETURN_IF_EXCEPTION(scope, void());
168
169     // 5. If options is undefined, then
170     JSObject* options;
171     if (optionsValue.isUndefined()) {
172         // a. Let options be ObjectCreate(%ObjectPrototype%).
173         options = constructEmptyObject(&state);
174     } else { // 6. Else
175         // a. Let options be ToObject(options).
176         options = optionsValue.toObject(&state);
177         // b. ReturnIfAbrupt(options).
178         RETURN_IF_EXCEPTION(scope, void());
179     }
180
181     // 7. Let opt be a new Record.
182     HashMap<String, String> opt;
183
184     // 8. Let matcher be GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
185     String matcher = intlStringOption(state, options, vm.propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit");
186     // 9. ReturnIfAbrupt(matcher).
187     RETURN_IF_EXCEPTION(scope, void());
188     // 10. Set opt.[[localeMatcher]] to matcher.
189     opt.add(ASCIILiteral("localeMatcher"), matcher);
190
191     // 11. Let localeData be %NumberFormat%.[[localeData]].
192     // 12. Let r be ResolveLocale(%NumberFormat%.[[availableLocales]], requestedLocales, opt, %NumberFormat%.[[relevantExtensionKeys]], localeData).
193     auto& availableLocales = state.jsCallee()->globalObject()->intlNumberFormatAvailableLocales();
194     auto result = resolveLocale(state, availableLocales, requestedLocales, opt, relevantExtensionKeys, WTF_ARRAY_LENGTH(relevantExtensionKeys), localeData);
195
196     // 13. Set numberFormat.[[locale]] to the value of r.[[locale]].
197     m_locale = result.get(ASCIILiteral("locale"));
198     if (m_locale.isEmpty()) {
199         throwTypeError(&state, scope, ASCIILiteral("failed to initialize NumberFormat due to invalid locale"));
200         return;
201     }
202
203     // 14. Set numberFormat.[[numberingSystem]] to the value of r.[[nu]].
204     m_numberingSystem = result.get(ASCIILiteral("nu"));
205
206     // 15. Let dataLocale be r.[[dataLocale]].
207
208     // 16. Let s be GetOption(options, "style", "string", « "decimal", "percent", "currency"», "decimal").
209     String styleString = intlStringOption(state, options, Identifier::fromString(&vm, "style"), { "decimal", "percent", "currency" }, "style must be either \"decimal\", \"percent\", or \"currency\"", "decimal");
210     // 17. ReturnIfAbrupt(s).
211     RETURN_IF_EXCEPTION(scope, void());
212     // 18. Set numberFormat.[[style]] to s.
213     if (styleString == "decimal")
214         m_style = Style::Decimal;
215     else if (styleString == "percent")
216         m_style = Style::Percent;
217     else if (styleString == "currency")
218         m_style = Style::Currency;
219     else
220         ASSERT_NOT_REACHED();
221
222     // 19. Let c be GetOption(options, "currency", "string", undefined, undefined).
223     String currency = intlStringOption(state, options, Identifier::fromString(&vm, "currency"), { }, nullptr, nullptr);
224     // 20. ReturnIfAbrupt(c).
225     RETURN_IF_EXCEPTION(scope, void());
226     // 21. If c is not undefined, then
227     if (!currency.isNull()) {
228         // a. If the result of IsWellFormedCurrencyCode(c), is false, then throw a RangeError exception.
229         if (currency.length() != 3 || !currency.isAllSpecialCharacters<isASCIIAlpha>()) {
230             throwException(&state, scope, createRangeError(&state, ASCIILiteral("currency is not a well-formed currency code")));
231             return;
232         }
233     }
234
235     unsigned currencyDigits = 0;
236     if (m_style == Style::Currency) {
237         // 22. If s is "currency" and c is undefined, throw a TypeError exception.
238         if (currency.isNull()) {
239             throwTypeError(&state, scope, ASCIILiteral("currency must be a string"));
240             return;
241         }
242
243         // 23. If s is "currency", then
244         // a. Let c be converting c to upper case as specified in 6.1.
245         currency = currency.convertToASCIIUppercase();
246         // b. Set numberFormat.[[currency]] to c.
247         m_currency = currency;
248         // c. Let cDigits be CurrencyDigits(c)
249         currencyDigits = computeCurrencyDigits(currency);
250     }
251
252     // 24. Let cd be GetOption(options, "currencyDisplay", "string", «"code", "symbol", "name"», "symbol").
253     String currencyDisplayString = intlStringOption(state, options, Identifier::fromString(&vm, "currencyDisplay"), { "code", "symbol", "name" }, "currencyDisplay must be either \"code\", \"symbol\", or \"name\"", "symbol");
254     // 25. ReturnIfAbrupt(cd).
255     RETURN_IF_EXCEPTION(scope, void());
256     // 26. If s is "currency", set numberFormat.[[currencyDisplay]] to cd.
257     if (m_style == Style::Currency) {
258         if (currencyDisplayString == "code")
259             m_currencyDisplay = CurrencyDisplay::Code;
260         else if (currencyDisplayString == "symbol")
261             m_currencyDisplay = CurrencyDisplay::Symbol;
262         else if (currencyDisplayString == "name")
263             m_currencyDisplay = CurrencyDisplay::Name;
264         else
265             ASSERT_NOT_REACHED();
266     }
267
268     // 27. Let mnid be GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1).
269     // 28. ReturnIfAbrupt(mnid).
270     // 29. Set numberFormat.[[minimumIntegerDigits]] to mnid.
271     unsigned minimumIntegerDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "minimumIntegerDigits"), 1, 21, 1);
272     RETURN_IF_EXCEPTION(scope, void());
273     m_minimumIntegerDigits = minimumIntegerDigits;
274
275     // 30. If s is "currency", let mnfdDefault be cDigits; else let mnfdDefault be 0.
276     unsigned minimumFractionDigitsDefault = (m_style == Style::Currency) ? currencyDigits : 0;
277
278     // 31. Let mnfd be GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault).
279     // 32. ReturnIfAbrupt(mnfd).
280     // 33. Set numberFormat.[[minimumFractionDigits]] to mnfd.
281     unsigned minimumFractionDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "minimumFractionDigits"), 0, 20, minimumFractionDigitsDefault);
282     RETURN_IF_EXCEPTION(scope, void());
283     m_minimumFractionDigits = minimumFractionDigits;
284
285     // 34. If s is "currency", let mxfdDefault be max(mnfd, cDigits);
286     unsigned maximumFractionDigitsDefault;
287     if (m_style == Style::Currency)
288         maximumFractionDigitsDefault = std::max(minimumFractionDigits, currencyDigits);
289     else if (m_style == Style::Percent) // else if s is "percent", let mxfdDefault be max(mnfd, 0);
290         maximumFractionDigitsDefault = minimumFractionDigits;
291     else // else let mxfdDefault be max(mnfd, 3).
292         maximumFractionDigitsDefault = std::max(minimumFractionDigits, 3u);
293
294     // 35. Let mxfd be GetNumberOption(options, "maximumFractionDigits", mnfd, 20, mxfdDefault).
295     // 36. ReturnIfAbrupt(mxfd).
296     // 37. Set numberFormat.[[maximumFractionDigits]] to mxfd.
297     unsigned maximumFractionDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "maximumFractionDigits"), minimumFractionDigits, 20, maximumFractionDigitsDefault);
298     RETURN_IF_EXCEPTION(scope, void());
299     m_maximumFractionDigits = maximumFractionDigits;
300
301     // 38. Let mnsd be Get(options, "minimumSignificantDigits").
302     JSValue minimumSignificantDigitsValue = options->get(&state, Identifier::fromString(&vm, "minimumSignificantDigits"));
303     // 39. ReturnIfAbrupt(mnsd).
304     RETURN_IF_EXCEPTION(scope, void());
305
306     // 40. Let mxsd be Get(options, "maximumSignificantDigits").
307     JSValue maximumSignificantDigitsValue = options->get(&state, Identifier::fromString(&vm, "maximumSignificantDigits"));
308     // 41. ReturnIfAbrupt(mxsd).
309     RETURN_IF_EXCEPTION(scope, void());
310
311     // 42. If mnsd is not undefined or mxsd is not undefined, then
312     if (!minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined()) {
313         // a. Let mnsd be GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1).
314         unsigned minimumSignificantDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "minimumSignificantDigits"), 1, 21, 1);
315         // b. ReturnIfAbrupt(mnsd).
316         RETURN_IF_EXCEPTION(scope, void());
317         // c. Let mxsd be GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21).
318         unsigned maximumSignificantDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "maximumSignificantDigits"), minimumSignificantDigits, 21, 21);
319         // d. ReturnIfAbrupt(mxsd).
320         RETURN_IF_EXCEPTION(scope, void());
321         // e. Set numberFormat.[[minimumSignificantDigits]] to mnsd.
322         m_minimumSignificantDigits = minimumSignificantDigits;
323         // f. Set numberFormat.[[maximumSignificantDigits]] to mxsd.
324         m_maximumSignificantDigits = maximumSignificantDigits;
325     }
326
327     // 43. Let g be GetOption(options, "useGrouping", "boolean", undefined, true).
328     bool usesFallback;
329     bool useGrouping = intlBooleanOption(state, options, Identifier::fromString(&vm, "useGrouping"), usesFallback);
330     if (usesFallback)
331         useGrouping = true;
332     // 44. ReturnIfAbrupt(g).
333     RETURN_IF_EXCEPTION(scope, void());
334     // 45. Set numberFormat.[[useGrouping]] to g.
335     m_useGrouping = useGrouping;
336
337     // Steps 46 - 51 are not necessary to our implementation.
338     // 46. Let dataLocaleData be Get(localeData, dataLocale).
339     // 47. Let patterns be Get(dataLocaleData, "patterns").
340     // 48. Assert: patterns is an object (see 11.2.3).
341     // 49. Let stylePatterns be Get(patterns, s).
342     // 50. Set numberFormat.[[positivePattern]] to Get(stylePatterns, "positivePattern").
343     // 51. Set numberFormat.[[negativePattern]] to Get(stylePatterns, "negativePattern").
344
345     // 52. Set numberFormat.[[boundFormat]] to undefined.
346     // 53. Set numberFormat.[[initializedNumberFormat]] to true.
347     m_initializedNumberFormat = true;
348
349     // 54. Return numberFormat.
350 }
351
352 void IntlNumberFormat::createNumberFormat(ExecState& state)
353 {
354     VM& vm = state.vm();
355     auto scope = DECLARE_CATCH_SCOPE(vm);
356
357     ASSERT(!m_numberFormat);
358
359     if (!m_initializedNumberFormat) {
360         initializeNumberFormat(state, jsUndefined(), jsUndefined());
361         scope.assertNoException();
362     }
363
364     UNumberFormatStyle style;
365     switch (m_style) {
366     case Style::Decimal:
367         style = UNUM_DECIMAL;
368         break;
369     case Style::Percent:
370         style = UNUM_PERCENT;
371         break;
372     case Style::Currency:
373         switch (m_currencyDisplay) {
374         case CurrencyDisplay::Code:
375             style = UNUM_CURRENCY_ISO;
376             break;
377         case CurrencyDisplay::Symbol:
378             style = UNUM_CURRENCY;
379             break;
380         case CurrencyDisplay::Name:
381             style = UNUM_CURRENCY_PLURAL;
382             break;
383         default:
384             ASSERT_NOT_REACHED();
385         }
386         break;
387     default:
388         ASSERT_NOT_REACHED();
389     }
390
391     UErrorCode status = U_ZERO_ERROR;
392     auto numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(style, nullptr, 0, m_locale.utf8().data(), nullptr, &status));
393     if (U_FAILURE(status))
394         return;
395
396     if (m_style == Style::Currency)
397         unum_setTextAttribute(numberFormat.get(), UNUM_CURRENCY_CODE, StringView(m_currency).upconvertedCharacters(), 3, &status);
398     if (!m_minimumSignificantDigits) {
399         unum_setAttribute(numberFormat.get(), UNUM_MIN_INTEGER_DIGITS, m_minimumIntegerDigits);
400         unum_setAttribute(numberFormat.get(), UNUM_MIN_FRACTION_DIGITS, m_minimumFractionDigits);
401         unum_setAttribute(numberFormat.get(), UNUM_MAX_FRACTION_DIGITS, m_maximumFractionDigits);
402     } else {
403         unum_setAttribute(numberFormat.get(), UNUM_SIGNIFICANT_DIGITS_USED, true);
404         unum_setAttribute(numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits);
405         unum_setAttribute(numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits);
406     }
407     unum_setAttribute(numberFormat.get(), UNUM_GROUPING_USED, m_useGrouping);
408     unum_setAttribute(numberFormat.get(), UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP);
409     if (U_FAILURE(status))
410         return;
411
412     m_numberFormat = WTFMove(numberFormat);
413 }
414
415 JSValue IntlNumberFormat::formatNumber(ExecState& state, double number)
416 {
417     VM& vm = state.vm();
418     auto scope = DECLARE_THROW_SCOPE(vm);
419
420     // 11.3.4 FormatNumber abstract operation (ECMA-402 2.0)
421     if (!m_numberFormat) {
422         createNumberFormat(state);
423         if (!m_numberFormat)
424             return throwException(&state, scope, createError(&state, ASCIILiteral("Failed to format a number.")));
425     }
426
427     // Map negative zero to positive zero.
428     if (!number)
429         number = 0.0;
430
431     UErrorCode status = U_ZERO_ERROR;
432     Vector<UChar, 32> buffer(32);
433     auto length = unum_formatDouble(m_numberFormat.get(), number, buffer.data(), buffer.size(), nullptr, &status);
434     if (status == U_BUFFER_OVERFLOW_ERROR) {
435         buffer.grow(length);
436         status = U_ZERO_ERROR;
437         unum_formatDouble(m_numberFormat.get(), number, buffer.data(), length, nullptr, &status);
438     }
439     if (U_FAILURE(status))
440         return throwException(&state, scope, createError(&state, ASCIILiteral("Failed to format a number.")));
441
442     return jsString(&state, String(buffer.data(), length));
443 }
444
445 const char* IntlNumberFormat::styleString(Style style)
446 {
447     switch (style) {
448     case Style::Decimal:
449         return "decimal";
450     case Style::Percent:
451         return "percent";
452     case Style::Currency:
453         return "currency";
454     }
455     ASSERT_NOT_REACHED();
456     return nullptr;
457 }
458
459 const char* IntlNumberFormat::currencyDisplayString(CurrencyDisplay currencyDisplay)
460 {
461     switch (currencyDisplay) {
462     case CurrencyDisplay::Code:
463         return "code";
464     case CurrencyDisplay::Symbol:
465         return "symbol";
466     case CurrencyDisplay::Name:
467         return "name";
468     }
469     ASSERT_NOT_REACHED();
470     return nullptr;
471 }
472
473 JSObject* IntlNumberFormat::resolvedOptions(ExecState& state)
474 {
475     VM& vm = state.vm();
476     auto scope = DECLARE_THROW_SCOPE(vm);
477
478     // 11.3.5 Intl.NumberFormat.prototype.resolvedOptions() (ECMA-402 2.0)
479     // The function returns a new object whose properties and attributes are set as if
480     // constructed by an object literal assigning to each of the following properties the
481     // value of the corresponding internal slot of this NumberFormat object (see 11.4):
482     // locale, numberingSystem, style, currency, currencyDisplay, minimumIntegerDigits,
483     // minimumFractionDigits, maximumFractionDigits, minimumSignificantDigits,
484     // maximumSignificantDigits, and useGrouping. Properties whose corresponding internal
485     // slots are not present are not assigned.
486
487     if (!m_initializedNumberFormat) {
488         initializeNumberFormat(state, jsUndefined(), jsUndefined());
489         scope.assertNoException();
490     }
491
492     JSObject* options = constructEmptyObject(&state);
493     options->putDirect(vm, vm.propertyNames->locale, jsString(&state, m_locale));
494     options->putDirect(vm, Identifier::fromString(&vm, "numberingSystem"), jsString(&state, m_numberingSystem));
495     options->putDirect(vm, Identifier::fromString(&vm, "style"), jsNontrivialString(&state, ASCIILiteral(styleString(m_style))));
496     if (m_style == Style::Currency) {
497         options->putDirect(vm, Identifier::fromString(&vm, "currency"), jsNontrivialString(&state, m_currency));
498         options->putDirect(vm, Identifier::fromString(&vm, "currencyDisplay"), jsNontrivialString(&state, ASCIILiteral(currencyDisplayString(m_currencyDisplay))));
499     }
500     options->putDirect(vm, Identifier::fromString(&vm, "minimumIntegerDigits"), jsNumber(m_minimumIntegerDigits));
501     options->putDirect(vm, Identifier::fromString(&vm, "minimumFractionDigits"), jsNumber(m_minimumFractionDigits));
502     options->putDirect(vm, Identifier::fromString(&vm, "maximumFractionDigits"), jsNumber(m_maximumFractionDigits));
503     if (m_minimumSignificantDigits) {
504         ASSERT(m_maximumSignificantDigits);
505         options->putDirect(vm, Identifier::fromString(&vm, "minimumSignificantDigits"), jsNumber(m_minimumSignificantDigits));
506         options->putDirect(vm, Identifier::fromString(&vm, "maximumSignificantDigits"), jsNumber(m_maximumSignificantDigits));
507     }
508     options->putDirect(vm, Identifier::fromString(&vm, "useGrouping"), jsBoolean(m_useGrouping));
509     return options;
510 }
511
512 void IntlNumberFormat::setBoundFormat(VM& vm, JSBoundFunction* format)
513 {
514     m_boundFormat.set(vm, this, format);
515 }
516
517 } // namespace JSC
518
519 #endif // ENABLE(INTL)