Changeset 196434 in webkit


Ignore:
Timestamp:
Feb 11, 2016 1:24:52 PM (8 years ago)
Author:
Sukolsak Sakshuwong
Message:

[INTL] Implement Intl.NumberFormat.prototype.resolvedOptions ()
https://bugs.webkit.org/show_bug.cgi?id=147602

Reviewed by Darin Adler.

Source/JavaScriptCore:

This patch implements Intl.NumberFormat.prototype.resolvedOptions() according
to the ECMAScript 2015 Internationalization API spec (ECMA-402 2nd edition.)

  • runtime/IntlDateTimeFormat.cpp:

(JSC::localeData):

  • runtime/IntlNumberFormat.cpp:

(JSC::localeData):
(JSC::computeCurrencySortKey):
(JSC::extractCurrencySortKey):
(JSC::computeCurrencyDigits):
(JSC::IntlNumberFormat::initializeNumberFormat):
(JSC::IntlNumberFormat::styleString):
(JSC::IntlNumberFormat::currencyDisplayString):
(JSC::IntlNumberFormat::resolvedOptions):
(JSC::IntlNumberFormat::setBoundFormat):

  • runtime/IntlNumberFormat.h:
  • runtime/IntlNumberFormatConstructor.cpp:

(JSC::constructIntlNumberFormat):
(JSC::callIntlNumberFormat):

  • runtime/IntlNumberFormatPrototype.cpp:

(JSC::IntlNumberFormatPrototypeFuncResolvedOptions):

  • runtime/IntlObject.cpp:

(JSC::intlNumberOption):
(JSC::numberingSystemsForLocale):
(JSC::getNumberingSystemsForLocale): Deleted.

  • runtime/IntlObject.h:

LayoutTests:

  • js/intl-numberformat-expected.txt:
  • js/script-tests/intl-numberformat.js:

(testNumberFormat):

Location:
trunk
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r196431 r196434  
     12016-02-11  Sukolsak Sakshuwong  <sukolsak@gmail.com>
     2
     3        [INTL] Implement Intl.NumberFormat.prototype.resolvedOptions ()
     4        https://bugs.webkit.org/show_bug.cgi?id=147602
     5
     6        Reviewed by Darin Adler.
     7
     8        * js/intl-numberformat-expected.txt:
     9        * js/script-tests/intl-numberformat.js:
     10        (testNumberFormat):
     11
    1122016-02-11  Ryan Haddad  <ryanhaddad@apple.com>
    213
  • trunk/LayoutTests/js/intl-numberformat-expected.txt

    r189811 r196434  
    1313PASS class DerivedNumberFormat extends Intl.NumberFormat {};Object.getPrototypeOf(new DerivedNumberFormat) === DerivedNumberFormat.prototype is true
    1414PASS class DerivedNumberFormat extends Intl.NumberFormat {};Object.getPrototypeOf(Object.getPrototypeOf(new DerivedNumberFormat)) === Intl.NumberFormat.prototype is true
     15PASS testNumberFormat(Intl.NumberFormat('en'), [{locale: 'en'}]) is true
     16PASS testNumberFormat(Intl.NumberFormat('eN-uS'), [{locale: 'en-US'}]) is true
     17PASS testNumberFormat(Intl.NumberFormat(['en', 'de']), [{locale: 'en'}]) is true
     18PASS testNumberFormat(Intl.NumberFormat('de'), [{locale: 'de'}]) is true
     19PASS testNumberFormat(Intl.NumberFormat('zh-Hans-CN-u-nu-hanidec'), [{locale: 'zh-Hans-CN-u-nu-hanidec', numberingSystem: 'hanidec'}]) is true
     20PASS testNumberFormat(Intl.NumberFormat('ZH-hans-cn-U-Nu-Hanidec'), [{locale: 'zh-Hans-CN-u-nu-hanidec', numberingSystem: 'hanidec'}]) is true
     21PASS testNumberFormat(Intl.NumberFormat('en-u-nu-abcd'), [{locale: 'en'}]) is true
     22PASS testNumberFormat(Intl.NumberFormat('zh-Hans-CN-u-aa-aaaa-co-pinyin-nu-hanidec-bb-bbbb'), [{locale: 'zh-Hans-CN-u-nu-hanidec', numberingSystem: 'hanidec'}]) is true
     23PASS testNumberFormat(Intl.NumberFormat('en', {localeMatcher: 'lookup'}), [{locale: 'en'}]) is true
     24PASS testNumberFormat(Intl.NumberFormat('en', {localeMatcher: 'best fit'}), [{locale: 'en'}]) is true
     25PASS Intl.NumberFormat('en', {localeMatcher: 'LookUp'}) threw exception RangeError: localeMatcher must be either "lookup" or "best fit".
     26PASS Intl.NumberFormat('en', { get localeMatcher() { throw 42; } }) threw exception 42.
     27PASS Intl.NumberFormat('en', {localeMatcher: {toString() { throw 42; }}}) threw exception 42.
     28PASS testNumberFormat(Intl.NumberFormat('en', {style: 'decimal'}), [{locale: 'en', style: 'decimal'}]) is true
     29PASS Intl.NumberFormat('en', {style: 'currency'}) threw exception TypeError: currency must be a string.
     30PASS testNumberFormat(Intl.NumberFormat('en', {style: 'percent'}), [{locale: 'en', style: 'percent', maximumFractionDigits: 0}]) is true
     31PASS Intl.NumberFormat('en', {style: 'Decimal'}) threw exception RangeError: style must be either "decimal", "percent", or "currency".
     32PASS Intl.NumberFormat('en', { get style() { throw 42; } }) threw exception 42.
     33PASS testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'USD'}), [{locale: 'en', style: 'currency', currency: 'USD', currencyDisplay: 'symbol', minimumFractionDigits: 2, maximumFractionDigits: 2}]) is true
     34PASS testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'UsD'}), [{locale: 'en', style: 'currency', currency: 'USD', currencyDisplay: 'symbol', minimumFractionDigits: 2, maximumFractionDigits: 2}]) is true
     35PASS testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'CLF'}), [{locale: 'en', style: 'currency', currency: 'CLF', currencyDisplay: 'symbol', minimumFractionDigits: 4, maximumFractionDigits: 4}]) is true
     36PASS testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'cLf'}), [{locale: 'en', style: 'currency', currency: 'CLF', currencyDisplay: 'symbol', minimumFractionDigits: 4, maximumFractionDigits: 4}]) is true
     37PASS testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'XXX'}), [{locale: 'en', style: 'currency', currency: 'XXX', currencyDisplay: 'symbol', minimumFractionDigits: 2, maximumFractionDigits: 2}]) is true
     38PASS Intl.NumberFormat('en', {style: 'currency', currency: 'US$'}) threw exception RangeError: currency is not a well-formed currency code.
     39PASS Intl.NumberFormat('en', {style: 'currency', currency: 'US'}) threw exception RangeError: currency is not a well-formed currency code.
     40PASS Intl.NumberFormat('en', {style: 'currency', currency: 'US Dollar'}) threw exception RangeError: currency is not a well-formed currency code.
     41PASS Intl.NumberFormat('en', {style: 'currency', get currency() { throw 42; }}) threw exception 42.
     42PASS testNumberFormat(Intl.NumberFormat('en', {style: 'decimal', currency: 'USD'}), [{locale: 'en', style: 'decimal'}]) is true
     43PASS testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'USD', currencyDisplay: 'code'}), [{locale: 'en', style: 'currency', currency: 'USD', currencyDisplay: 'code', minimumFractionDigits: 2, maximumFractionDigits: 2}]) is true
     44PASS testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'USD', currencyDisplay: 'symbol'}), [{locale: 'en', style: 'currency', currency: 'USD', currencyDisplay: 'symbol', minimumFractionDigits: 2, maximumFractionDigits: 2}]) is true
     45PASS testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'USD', currencyDisplay: 'name'}), [{locale: 'en', style: 'currency', currency: 'USD', currencyDisplay: 'name', minimumFractionDigits: 2, maximumFractionDigits: 2}]) is true
     46PASS Intl.NumberFormat('en', {style: 'currency', currency: 'USD', currencyDisplay: 'Code'}) threw exception RangeError: currencyDisplay must be either "code", "symbol", or "name".
     47PASS Intl.NumberFormat('en', {style: 'currency', currency: 'USD', get currencyDisplay() { throw 42; }}) threw exception 42.
     48PASS testNumberFormat(Intl.NumberFormat('en', {style: 'decimal', currencyDisplay: 'code'}), [{locale: 'en', style: 'decimal'}]) is true
     49PASS testNumberFormat(Intl.NumberFormat('en', {minimumIntegerDigits: 1}), [{locale: 'en', minimumIntegerDigits: 1}]) is true
     50PASS testNumberFormat(Intl.NumberFormat('en', {minimumIntegerDigits: '2'}), [{locale: 'en', minimumIntegerDigits: 2}]) is true
     51PASS testNumberFormat(Intl.NumberFormat('en', {minimumIntegerDigits: {valueOf() { return 3; }}}), [{locale: 'en', minimumIntegerDigits: 3}]) is true
     52PASS testNumberFormat(Intl.NumberFormat('en', {minimumIntegerDigits: 4.9}), [{locale: 'en', minimumIntegerDigits: 4}]) is true
     53PASS testNumberFormat(Intl.NumberFormat('en', {minimumIntegerDigits: 21}), [{locale: 'en', minimumIntegerDigits: 21}]) is true
     54PASS Intl.NumberFormat('en', {minimumIntegerDigits: 0}) threw exception RangeError: minimumIntegerDigits is out of range.
     55PASS Intl.NumberFormat('en', {minimumIntegerDigits: 22}) threw exception RangeError: minimumIntegerDigits is out of range.
     56PASS Intl.NumberFormat('en', {minimumIntegerDigits: 0.9}) threw exception RangeError: minimumIntegerDigits is out of range.
     57PASS Intl.NumberFormat('en', {minimumIntegerDigits: 21.1}) threw exception RangeError: minimumIntegerDigits is out of range.
     58PASS Intl.NumberFormat('en', {minimumIntegerDigits: NaN}) threw exception RangeError: minimumIntegerDigits is out of range.
     59PASS Intl.NumberFormat('en', {minimumIntegerDigits: Infinity}) threw exception RangeError: minimumIntegerDigits is out of range.
     60PASS Intl.NumberFormat('en', { get minimumIntegerDigits() { throw 42; } }) threw exception 42.
     61PASS Intl.NumberFormat('en', {minimumIntegerDigits: {valueOf() { throw 42; }}}) threw exception 42.
     62PASS testNumberFormat(Intl.NumberFormat('en', {minimumFractionDigits: 0}), [{locale: 'en', minimumFractionDigits: 0, maximumFractionDigits: 3}]) is true
     63PASS testNumberFormat(Intl.NumberFormat('en', {style: 'percent', minimumFractionDigits: 0}), [{locale: 'en', style: 'percent', minimumFractionDigits: 0, maximumFractionDigits: 0}]) is true
     64PASS testNumberFormat(Intl.NumberFormat('en', {minimumFractionDigits: 6}), [{locale: 'en', minimumFractionDigits: 6, maximumFractionDigits: 6}]) is true
     65PASS Intl.NumberFormat('en', {minimumFractionDigits: -1}) threw exception RangeError: minimumFractionDigits is out of range.
     66PASS Intl.NumberFormat('en', {minimumFractionDigits: 21}) threw exception RangeError: minimumFractionDigits is out of range.
     67PASS testNumberFormat(Intl.NumberFormat('en', {maximumFractionDigits: 6}), [{locale: 'en', maximumFractionDigits: 6}]) is true
     68PASS Intl.NumberFormat('en', {minimumFractionDigits: 7, maximumFractionDigits: 6}) threw exception RangeError: maximumFractionDigits is out of range.
     69PASS Intl.NumberFormat('en', {maximumFractionDigits: -1}) threw exception RangeError: maximumFractionDigits is out of range.
     70PASS Intl.NumberFormat('en', {maximumFractionDigits: 21}) threw exception RangeError: maximumFractionDigits is out of range.
     71PASS testNumberFormat(Intl.NumberFormat('en', {minimumSignificantDigits: 6}), [{locale: 'en', minimumSignificantDigits: 6, maximumSignificantDigits: 21}]) is true
     72PASS Intl.NumberFormat('en', {minimumSignificantDigits: 0}) threw exception RangeError: minimumSignificantDigits is out of range.
     73PASS Intl.NumberFormat('en', {minimumSignificantDigits: 22}) threw exception RangeError: minimumSignificantDigits is out of range.
     74PASS testNumberFormat(Intl.NumberFormat('en', {maximumSignificantDigits: 6}), [{locale: 'en', minimumSignificantDigits: 1, maximumSignificantDigits: 6}]) is true
     75PASS Intl.NumberFormat('en', {minimumSignificantDigits: 7, maximumSignificantDigits: 6}) threw exception RangeError: maximumSignificantDigits is out of range.
     76PASS Intl.NumberFormat('en', {maximumSignificantDigits: 0}) threw exception RangeError: maximumSignificantDigits is out of range.
     77PASS Intl.NumberFormat('en', {maximumSignificantDigits: 22}) threw exception RangeError: maximumSignificantDigits is out of range.
     78PASS testNumberFormat(Intl.NumberFormat('en', {useGrouping: true}), [{locale: 'en', useGrouping: true}]) is true
     79PASS testNumberFormat(Intl.NumberFormat('en', {useGrouping: false}), [{locale: 'en', useGrouping: false}]) is true
     80PASS testNumberFormat(Intl.NumberFormat('en', {useGrouping: 'false'}), [{locale: 'en', useGrouping: true}]) is true
     81PASS Intl.NumberFormat('en', { get useGrouping() { throw 42; } }) threw exception 42.
    1582PASS Intl.NumberFormat.length is 0
    1683PASS Object.getOwnPropertyDescriptor(Intl.NumberFormat, 'prototype').writable is false
     
    65132PASS Intl.NumberFormat.prototype.resolvedOptions() === Intl.NumberFormat.prototype.resolvedOptions() is false
    66133PASS Intl.NumberFormat.prototype.resolvedOptions.call(5) threw exception TypeError: Intl.NumberFormat.prototype.resolvedOptions called on value that's not an object initialized as a NumberFormat.
     134PASS var options = Intl.NumberFormat.prototype.resolvedOptions(); delete options['locale']; JSON.stringify(options) is '{"numberingSystem":"latn","style":"decimal","minimumIntegerDigits":1,"minimumFractionDigits":0,"maximumFractionDigits":3,"useGrouping":true}'
    67135PASS successfullyParsed is true
    68136
  • trunk/LayoutTests/js/script-tests/intl-numberformat.js

    r189811 r196434  
    1818shouldBeTrue(classPrefix + "Object.getPrototypeOf(new DerivedNumberFormat) === DerivedNumberFormat.prototype");
    1919shouldBeTrue(classPrefix + "Object.getPrototypeOf(Object.getPrototypeOf(new DerivedNumberFormat)) === Intl.NumberFormat.prototype");
     20
     21function testNumberFormat(numberFormat, possibleDifferences) {
     22    var possibleOptions = possibleDifferences.map(function(difference) {
     23        var defaultOptions = {
     24            locale: undefined,
     25            numberingSystem: "latn",
     26            style: "decimal",
     27            currency: undefined,
     28            currencyDisplay: undefined,
     29            minimumIntegerDigits: 1,
     30            minimumFractionDigits: 0,
     31            maximumFractionDigits: 3,
     32            minimumSignificantDigits: undefined,
     33            maximumSignificantDigits: undefined,
     34            useGrouping: true
     35        }
     36        Object.assign(defaultOptions, difference);
     37        return JSON.stringify(defaultOptions);
     38    });
     39    var actualOptions = JSON.stringify(numberFormat.resolvedOptions())
     40    return possibleOptions.includes(actualOptions);
     41}
     42
     43// Locale is processed correctly.
     44shouldBeTrue("testNumberFormat(Intl.NumberFormat('en'), [{locale: 'en'}])");
     45shouldBeTrue("testNumberFormat(Intl.NumberFormat('eN-uS'), [{locale: 'en-US'}])");
     46shouldBeTrue("testNumberFormat(Intl.NumberFormat(['en', 'de']), [{locale: 'en'}])");
     47shouldBeTrue("testNumberFormat(Intl.NumberFormat('de'), [{locale: 'de'}])");
     48
     49// The "nu" key is processed correctly.
     50shouldBeTrue("testNumberFormat(Intl.NumberFormat('zh-Hans-CN-u-nu-hanidec'), [{locale: 'zh-Hans-CN-u-nu-hanidec', numberingSystem: 'hanidec'}])");
     51shouldBeTrue("testNumberFormat(Intl.NumberFormat('ZH-hans-cn-U-Nu-Hanidec'), [{locale: 'zh-Hans-CN-u-nu-hanidec', numberingSystem: 'hanidec'}])");
     52shouldBeTrue("testNumberFormat(Intl.NumberFormat('en-u-nu-abcd'), [{locale: 'en'}])");
     53
     54// Ignores irrelevant extension keys.
     55shouldBeTrue("testNumberFormat(Intl.NumberFormat('zh-Hans-CN-u-aa-aaaa-co-pinyin-nu-hanidec-bb-bbbb'), [{locale: 'zh-Hans-CN-u-nu-hanidec', numberingSystem: 'hanidec'}])");
     56
     57// The option localeMatcher is processed correctly.
     58shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {localeMatcher: 'lookup'}), [{locale: 'en'}])");
     59shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {localeMatcher: 'best fit'}), [{locale: 'en'}])");
     60shouldThrow("Intl.NumberFormat('en', {localeMatcher: 'LookUp'})", '\'RangeError: localeMatcher must be either "lookup" or "best fit"\'');
     61shouldThrow("Intl.NumberFormat('en', { get localeMatcher() { throw 42; } })", "'42'");
     62shouldThrow("Intl.NumberFormat('en', {localeMatcher: {toString() { throw 42; }}})", "'42'");
     63
     64// The option style is processed correctly.
     65shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'decimal'}), [{locale: 'en', style: 'decimal'}])");
     66shouldThrow("Intl.NumberFormat('en', {style: 'currency'})", "'TypeError: currency must be a string'");
     67shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'percent'}), [{locale: 'en', style: 'percent', maximumFractionDigits: 0}])");
     68shouldThrow("Intl.NumberFormat('en', {style: 'Decimal'})", '\'RangeError: style must be either "decimal", "percent", or "currency"\'');
     69shouldThrow("Intl.NumberFormat('en', { get style() { throw 42; } })", "'42'");
     70
     71// The option currency is processed correctly.
     72shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'USD'}), [{locale: 'en', style: 'currency', currency: 'USD', currencyDisplay: 'symbol', minimumFractionDigits: 2, maximumFractionDigits: 2}])");
     73shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'UsD'}), [{locale: 'en', style: 'currency', currency: 'USD', currencyDisplay: 'symbol', minimumFractionDigits: 2, maximumFractionDigits: 2}])");
     74shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'CLF'}), [{locale: 'en', style: 'currency', currency: 'CLF', currencyDisplay: 'symbol', minimumFractionDigits: 4, maximumFractionDigits: 4}])");
     75shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'cLf'}), [{locale: 'en', style: 'currency', currency: 'CLF', currencyDisplay: 'symbol', minimumFractionDigits: 4, maximumFractionDigits: 4}])");
     76shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'XXX'}), [{locale: 'en', style: 'currency', currency: 'XXX', currencyDisplay: 'symbol', minimumFractionDigits: 2, maximumFractionDigits: 2}])");
     77shouldThrow("Intl.NumberFormat('en', {style: 'currency', currency: 'US$'})", "'RangeError: currency is not a well-formed currency code'");
     78shouldThrow("Intl.NumberFormat('en', {style: 'currency', currency: 'US'})", "'RangeError: currency is not a well-formed currency code'");
     79shouldThrow("Intl.NumberFormat('en', {style: 'currency', currency: 'US Dollar'})", "'RangeError: currency is not a well-formed currency code'");
     80shouldThrow("Intl.NumberFormat('en', {style: 'currency', get currency() { throw 42; }})", "'42'");
     81shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'decimal', currency: 'USD'}), [{locale: 'en', style: 'decimal'}])");
     82
     83// The option currencyDisplay is processed correctly.
     84shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'USD', currencyDisplay: 'code'}), [{locale: 'en', style: 'currency', currency: 'USD', currencyDisplay: 'code', minimumFractionDigits: 2, maximumFractionDigits: 2}])");
     85shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'USD', currencyDisplay: 'symbol'}), [{locale: 'en', style: 'currency', currency: 'USD', currencyDisplay: 'symbol', minimumFractionDigits: 2, maximumFractionDigits: 2}])");
     86shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'currency', currency: 'USD', currencyDisplay: 'name'}), [{locale: 'en', style: 'currency', currency: 'USD', currencyDisplay: 'name', minimumFractionDigits: 2, maximumFractionDigits: 2}])");
     87shouldThrow("Intl.NumberFormat('en', {style: 'currency', currency: 'USD', currencyDisplay: 'Code'})", '\'RangeError: currencyDisplay must be either "code", "symbol", or "name"\'');
     88shouldThrow("Intl.NumberFormat('en', {style: 'currency', currency: 'USD', get currencyDisplay() { throw 42; }})", "'42'");
     89shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'decimal', currencyDisplay: 'code'}), [{locale: 'en', style: 'decimal'}])");
     90
     91// The option minimumIntegerDigits is processed correctly.
     92shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {minimumIntegerDigits: 1}), [{locale: 'en', minimumIntegerDigits: 1}])");
     93shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {minimumIntegerDigits: '2'}), [{locale: 'en', minimumIntegerDigits: 2}])");
     94shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {minimumIntegerDigits: {valueOf() { return 3; }}}), [{locale: 'en', minimumIntegerDigits: 3}])");
     95shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {minimumIntegerDigits: 4.9}), [{locale: 'en', minimumIntegerDigits: 4}])");
     96shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {minimumIntegerDigits: 21}), [{locale: 'en', minimumIntegerDigits: 21}])");
     97shouldThrow("Intl.NumberFormat('en', {minimumIntegerDigits: 0})", "'RangeError: minimumIntegerDigits is out of range'");
     98shouldThrow("Intl.NumberFormat('en', {minimumIntegerDigits: 22})", "'RangeError: minimumIntegerDigits is out of range'");
     99shouldThrow("Intl.NumberFormat('en', {minimumIntegerDigits: 0.9})", "'RangeError: minimumIntegerDigits is out of range'");
     100shouldThrow("Intl.NumberFormat('en', {minimumIntegerDigits: 21.1})", "'RangeError: minimumIntegerDigits is out of range'");
     101shouldThrow("Intl.NumberFormat('en', {minimumIntegerDigits: NaN})", "'RangeError: minimumIntegerDigits is out of range'");
     102shouldThrow("Intl.NumberFormat('en', {minimumIntegerDigits: Infinity})", "'RangeError: minimumIntegerDigits is out of range'");
     103shouldThrow("Intl.NumberFormat('en', { get minimumIntegerDigits() { throw 42; } })", "'42'");
     104shouldThrow("Intl.NumberFormat('en', {minimumIntegerDigits: {valueOf() { throw 42; }}})", "'42'");
     105
     106// The option minimumFractionDigits is processed correctly.
     107shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {minimumFractionDigits: 0}), [{locale: 'en', minimumFractionDigits: 0, maximumFractionDigits: 3}])");
     108shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {style: 'percent', minimumFractionDigits: 0}), [{locale: 'en', style: 'percent', minimumFractionDigits: 0, maximumFractionDigits: 0}])");
     109shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {minimumFractionDigits: 6}), [{locale: 'en', minimumFractionDigits: 6, maximumFractionDigits: 6}])");
     110shouldThrow("Intl.NumberFormat('en', {minimumFractionDigits: -1})", "'RangeError: minimumFractionDigits is out of range'");
     111shouldThrow("Intl.NumberFormat('en', {minimumFractionDigits: 21})", "'RangeError: minimumFractionDigits is out of range'");
     112
     113// The option maximumFractionDigits is processed correctly.
     114shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {maximumFractionDigits: 6}), [{locale: 'en', maximumFractionDigits: 6}])");
     115shouldThrow("Intl.NumberFormat('en', {minimumFractionDigits: 7, maximumFractionDigits: 6})", "'RangeError: maximumFractionDigits is out of range'");
     116shouldThrow("Intl.NumberFormat('en', {maximumFractionDigits: -1})", "'RangeError: maximumFractionDigits is out of range'");
     117shouldThrow("Intl.NumberFormat('en', {maximumFractionDigits: 21})", "'RangeError: maximumFractionDigits is out of range'");
     118
     119// The option minimumSignificantDigits is processed correctly.
     120shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {minimumSignificantDigits: 6}), [{locale: 'en', minimumSignificantDigits: 6, maximumSignificantDigits: 21}])");
     121shouldThrow("Intl.NumberFormat('en', {minimumSignificantDigits: 0})", "'RangeError: minimumSignificantDigits is out of range'");
     122shouldThrow("Intl.NumberFormat('en', {minimumSignificantDigits: 22})", "'RangeError: minimumSignificantDigits is out of range'");
     123
     124// The option maximumSignificantDigits is processed correctly.
     125shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {maximumSignificantDigits: 6}), [{locale: 'en', minimumSignificantDigits: 1, maximumSignificantDigits: 6}])");
     126shouldThrow("Intl.NumberFormat('en', {minimumSignificantDigits: 7, maximumSignificantDigits: 6})", "'RangeError: maximumSignificantDigits is out of range'");
     127shouldThrow("Intl.NumberFormat('en', {maximumSignificantDigits: 0})", "'RangeError: maximumSignificantDigits is out of range'");
     128shouldThrow("Intl.NumberFormat('en', {maximumSignificantDigits: 22})", "'RangeError: maximumSignificantDigits is out of range'");
     129
     130// The option useGrouping is processed correctly.
     131shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {useGrouping: true}), [{locale: 'en', useGrouping: true}])");
     132shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {useGrouping: false}), [{locale: 'en', useGrouping: false}])");
     133shouldBeTrue("testNumberFormat(Intl.NumberFormat('en', {useGrouping: 'false'}), [{locale: 'en', useGrouping: true}])");
     134shouldThrow("Intl.NumberFormat('en', { get useGrouping() { throw 42; } })", "'42'");
    20135
    21136// 11.2 Properties of the Intl.NumberFormat Constructor
     
    136251shouldThrow("Intl.NumberFormat.prototype.resolvedOptions.call(5)", "'TypeError: Intl.NumberFormat.prototype.resolvedOptions called on value that\\'s not an object initialized as a NumberFormat'");
    137252
     253// Returns the default options.
     254shouldBe("var options = Intl.NumberFormat.prototype.resolvedOptions(); delete options['locale']; JSON.stringify(options)", '\'{"numberingSystem":"latn","style":"decimal","minimumIntegerDigits":1,"minimumFractionDigits":0,"maximumFractionDigits":3,"useGrouping":true}\'');
  • trunk/Source/JavaScriptCore/ChangeLog

    r196433 r196434  
     12016-02-11  Sukolsak Sakshuwong  <sukolsak@gmail.com>
     2
     3        [INTL] Implement Intl.NumberFormat.prototype.resolvedOptions ()
     4        https://bugs.webkit.org/show_bug.cgi?id=147602
     5
     6        Reviewed by Darin Adler.
     7
     8        This patch implements Intl.NumberFormat.prototype.resolvedOptions() according
     9        to the ECMAScript 2015 Internationalization API spec (ECMA-402 2nd edition.)
     10
     11        * runtime/IntlDateTimeFormat.cpp:
     12        (JSC::localeData):
     13        * runtime/IntlNumberFormat.cpp:
     14        (JSC::localeData):
     15        (JSC::computeCurrencySortKey):
     16        (JSC::extractCurrencySortKey):
     17        (JSC::computeCurrencyDigits):
     18        (JSC::IntlNumberFormat::initializeNumberFormat):
     19        (JSC::IntlNumberFormat::styleString):
     20        (JSC::IntlNumberFormat::currencyDisplayString):
     21        (JSC::IntlNumberFormat::resolvedOptions):
     22        (JSC::IntlNumberFormat::setBoundFormat):
     23        * runtime/IntlNumberFormat.h:
     24        * runtime/IntlNumberFormatConstructor.cpp:
     25        (JSC::constructIntlNumberFormat):
     26        (JSC::callIntlNumberFormat):
     27        * runtime/IntlNumberFormatPrototype.cpp:
     28        (JSC::IntlNumberFormatPrototypeFuncResolvedOptions):
     29        * runtime/IntlObject.cpp:
     30        (JSC::intlNumberOption):
     31        (JSC::numberingSystemsForLocale):
     32        (JSC::getNumberingSystemsForLocale): Deleted.
     33        * runtime/IntlObject.h:
     34
    1352016-02-11  Filip Pizlo  <fpizlo@apple.com>
    236
  • trunk/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp

    r195138 r196434  
    211211    }
    212212    case indexOfExtensionKeyNu:
    213         keyLocaleData = getNumberingSystemsForLocale(locale);
     213        keyLocaleData = numberingSystemsForLocale(locale);
    214214        break;
    215215    default:
  • trunk/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp

    r192831 r196434  
    11/*
    22 * Copyright (C) 2015 Andy VanWagoner (thetalecrafter@gmail.com)
     3 * Copyright (C) 2016 Sukolsak Sakshuwong (sukolsak@gmail.com)
    34 *
    45 * Redistribution and use in source and binary forms, with or without
     
    3031
    3132#include "Error.h"
     33#include "IdentifierInlines.h"
    3234#include "IntlNumberFormatConstructor.h"
    3335#include "IntlObject.h"
     
    3537#include "JSCJSValueInlines.h"
    3638#include "JSCellInlines.h"
     39#include "ObjectConstructor.h"
    3740#include "SlotVisitorInlines.h"
    3841#include "StructureInlines.h"
     
    4144
    4245const ClassInfo IntlNumberFormat::s_info = { "Object", &Base::s_info, 0, CREATE_METHOD_TABLE(IntlNumberFormat) };
     46
     47static const char* const relevantExtensionKeys[1] = { "nu" };
    4348
    4449IntlNumberFormat* IntlNumberFormat::create(VM& vm, IntlNumberFormatConstructor* constructor)
     
    8085}
    8186
    82 void IntlNumberFormat::setBoundFormat(VM& vm, JSBoundFunction* format)
    83 {
    84     m_boundFormat.set(vm, this, format);
     87static Vector<String> localeData(const String& locale, size_t keyIndex)
     88{
     89    // 9.1 Internal slots of Service Constructors & 11.2.3 Internal slots (ECMA-402 2.0)
     90    ASSERT_UNUSED(keyIndex, !keyIndex); // The index of the extension key "nu" in relevantExtensionKeys is 0.
     91    return numberingSystemsForLocale(locale);
     92}
     93
     94static inline unsigned computeCurrencySortKey(const String& currency)
     95{
     96    ASSERT(currency.length() == 3);
     97    ASSERT(currency.isAllSpecialCharacters<isASCIIUpper>());
     98    return (currency[0] << 16) + (currency[1] << 8) + currency[2];
     99}
     100
     101static inline unsigned computeCurrencySortKey(const char* currency)
     102{
     103    ASSERT(strlen(currency) == 3);
     104    ASSERT(isAllSpecialCharacters<isASCIIUpper>(currency, 3));
     105    return (currency[0] << 16) + (currency[1] << 8) + currency[2];
     106}
     107
     108static unsigned extractCurrencySortKey(std::pair<const char*, unsigned>* currencyMinorUnit)
     109{
     110    return computeCurrencySortKey(currencyMinorUnit->first);
     111}
     112
     113static unsigned computeCurrencyDigits(const String& currency)
     114{
     115    // 11.1.1 The abstract operation CurrencyDigits (currency)
     116    // "If the ISO 4217 currency and funds code list contains currency as an alphabetic code,
     117    // then return the minor unit value corresponding to the currency from the list; else return 2.
     118    std::pair<const char*, unsigned> currencyMinorUnits[] = {
     119        { "BHD", 3 },
     120        { "BIF", 0 },
     121        { "BYR", 0 },
     122        { "CLF", 4 },
     123        { "CLP", 0 },
     124        { "DJF", 0 },
     125        { "GNF", 0 },
     126        { "IQD", 3 },
     127        { "ISK", 0 },
     128        { "JOD", 3 },
     129        { "JPY", 0 },
     130        { "KMF", 0 },
     131        { "KRW", 0 },
     132        { "KWD", 3 },
     133        { "LYD", 3 },
     134        { "OMR", 3 },
     135        { "PYG", 0 },
     136        { "RWF", 0 },
     137        { "TND", 3 },
     138        { "UGX", 0 },
     139        { "UYI", 0 },
     140        { "VND", 0 },
     141        { "VUV", 0 },
     142        { "XAF", 0 },
     143        { "XOF", 0 },
     144        { "XPF", 0 }
     145    };
     146    auto* currencyMinorUnit = tryBinarySearch<std::pair<const char*, unsigned>>(currencyMinorUnits, WTF_ARRAY_LENGTH(currencyMinorUnits), computeCurrencySortKey(currency), extractCurrencySortKey);
     147    if (currencyMinorUnit)
     148        return currencyMinorUnit->second;
     149    return 2;
     150}
     151
     152void IntlNumberFormat::initializeNumberFormat(ExecState& state, JSValue locales, JSValue optionsValue)
     153{
     154    // 11.1.1 InitializeNumberFormat (numberFormat, locales, options) (ECMA-402 2.0)
     155    VM& vm = state.vm();
     156
     157    // 1. If numberFormat has an [[initializedIntlObject]] internal slot with value true, throw a TypeError exception.
     158    // 2. Set numberFormat.[[initializedIntlObject]] to true.
     159
     160    // 3. Let requestedLocales be CanonicalizeLocaleList(locales).
     161    auto requestedLocales = canonicalizeLocaleList(state, locales);
     162    // 4. ReturnIfAbrupt(requestedLocales).
     163    if (state.hadException())
     164        return;
     165
     166    // 5. If options is undefined, then
     167    JSObject* options;
     168    if (optionsValue.isUndefined()) {
     169        // a. Let options be ObjectCreate(%ObjectPrototype%).
     170        options = constructEmptyObject(&state);
     171    } else { // 6. Else
     172        // a. Let options be ToObject(options).
     173        options = optionsValue.toObject(&state);
     174        // b. ReturnIfAbrupt(options).
     175        if (state.hadException())
     176            return;
     177    }
     178
     179    // 7. Let opt be a new Record.
     180    HashMap<String, String> opt;
     181
     182    // 8. Let matcher be GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
     183    String matcher = intlStringOption(state, options, state.vm().propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit");
     184    // 9. ReturnIfAbrupt(matcher).
     185    if (state.hadException())
     186        return;
     187    // 10. Set opt.[[localeMatcher]] to matcher.
     188    opt.add(ASCIILiteral("localeMatcher"), matcher);
     189
     190    // 11. Let localeData be %NumberFormat%.[[localeData]].
     191    // 12. Let r be ResolveLocale(%NumberFormat%.[[availableLocales]], requestedLocales, opt, %NumberFormat%.[[relevantExtensionKeys]], localeData).
     192    auto& availableLocales = state.callee()->globalObject()->intlNumberFormatAvailableLocales();
     193    auto result = resolveLocale(availableLocales, requestedLocales, opt, relevantExtensionKeys, WTF_ARRAY_LENGTH(relevantExtensionKeys), localeData);
     194
     195    // 13. Set numberFormat.[[locale]] to the value of r.[[locale]].
     196    m_locale = result.get(ASCIILiteral("locale"));
     197
     198    // 14. Set numberFormat.[[numberingSystem]] to the value of r.[[nu]].
     199    m_numberingSystem = result.get(ASCIILiteral("nu"));
     200
     201    // 15. Let dataLocale be r.[[dataLocale]].
     202
     203    // 16. Let s be GetOption(options, "style", "string", « "decimal", "percent", "currency"», "decimal").
     204    String styleString = intlStringOption(state, options, Identifier::fromString(&vm, "style"), { "decimal", "percent", "currency" }, "style must be either \"decimal\", \"percent\", or \"currency\"", "decimal");
     205    // 17. ReturnIfAbrupt(s).
     206    if (state.hadException())
     207        return;
     208    // 18. Set numberFormat.[[style]] to s.
     209    if (styleString == "decimal")
     210        m_style = Style::Decimal;
     211    else if (styleString == "percent")
     212        m_style = Style::Percent;
     213    else if (styleString == "currency")
     214        m_style = Style::Currency;
     215    else
     216        ASSERT_NOT_REACHED();
     217
     218    // 19. Let c be GetOption(options, "currency", "string", undefined, undefined).
     219    String currency = intlStringOption(state, options, Identifier::fromString(&vm, "currency"), { }, nullptr, nullptr);
     220    // 20. ReturnIfAbrupt(c).
     221    if (state.hadException())
     222        return;
     223    // 21. If c is not undefined, then
     224    if (!currency.isNull()) {
     225        // a. If the result of IsWellFormedCurrencyCode(c), is false, then throw a RangeError exception.
     226        if (currency.length() != 3 || !currency.isAllSpecialCharacters<isASCIIAlpha>()) {
     227            state.vm().throwException(&state, createRangeError(&state, ASCIILiteral("currency is not a well-formed currency code")));
     228            return;
     229        }
     230    }
     231
     232    unsigned currencyDigits;
     233    if (m_style == Style::Currency) {
     234        // 22. If s is "currency" and c is undefined, throw a TypeError exception.
     235        if (currency.isNull()) {
     236            throwTypeError(&state, ASCIILiteral("currency must be a string"));
     237            return;
     238        }
     239
     240        // 23. If s is "currency", then
     241        // a. Let c be converting c to upper case as specified in 6.1.
     242        currency = currency.convertToASCIIUppercase();
     243        // b. Set numberFormat.[[currency]] to c.
     244        m_currency = currency;
     245        // c. Let cDigits be CurrencyDigits(c)
     246        currencyDigits = computeCurrencyDigits(currency);
     247    }
     248
     249    // 24. Let cd be GetOption(options, "currencyDisplay", "string", «"code", "symbol", "name"», "symbol").
     250    String currencyDisplayString = intlStringOption(state, options, Identifier::fromString(&vm, "currencyDisplay"), { "code", "symbol", "name" }, "currencyDisplay must be either \"code\", \"symbol\", or \"name\"", "symbol");
     251    // 25. ReturnIfAbrupt(cd).
     252    if (state.hadException())
     253        return;
     254    // 26. If s is "currency", set numberFormat.[[currencyDisplay]] to cd.
     255    if (m_style == Style::Currency) {
     256        if (currencyDisplayString == "code")
     257            m_currencyDisplay = CurrencyDisplay::Code;
     258        else if (currencyDisplayString == "symbol")
     259            m_currencyDisplay = CurrencyDisplay::Symbol;
     260        else if (currencyDisplayString == "name")
     261            m_currencyDisplay = CurrencyDisplay::Name;
     262        else
     263            ASSERT_NOT_REACHED();
     264    }
     265
     266    // 27. Let mnid be GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1).
     267    // 28. ReturnIfAbrupt(mnid).
     268    // 29. Set numberFormat.[[minimumIntegerDigits]] to mnid.
     269    unsigned minimumIntegerDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "minimumIntegerDigits"), 1, 21, 1);
     270    if (state.hadException())
     271        return;
     272    m_minimumIntegerDigits = minimumIntegerDigits;
     273
     274    // 30. If s is "currency", let mnfdDefault be cDigits; else let mnfdDefault be 0.
     275    unsigned minimumFractionDigitsDefault = (m_style == Style::Currency) ? currencyDigits : 0;
     276
     277    // 31. Let mnfd be GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault).
     278    // 32. ReturnIfAbrupt(mnfd).
     279    // 33. Set numberFormat.[[minimumFractionDigits]] to mnfd.
     280    unsigned minimumFractionDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "minimumFractionDigits"), 0, 20, minimumFractionDigitsDefault);
     281    if (state.hadException())
     282        return;
     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    if (state.hadException())
     299        return;
     300    m_maximumFractionDigits = maximumFractionDigits;
     301
     302    // 38. Let mnsd be Get(options, "minimumSignificantDigits").
     303    JSValue minimumSignificantDigitsValue = options->get(&state, Identifier::fromString(&vm, "minimumSignificantDigits"));
     304    // 39. ReturnIfAbrupt(mnsd).
     305    if (state.hadException())
     306        return;
     307
     308    // 40. Let mxsd be Get(options, "maximumSignificantDigits").
     309    JSValue maximumSignificantDigitsValue = options->get(&state, Identifier::fromString(&vm, "maximumSignificantDigits"));
     310    // 41. ReturnIfAbrupt(mxsd).
     311    if (state.hadException())
     312        return;
     313
     314    // 42. If mnsd is not undefined or mxsd is not undefined, then
     315    if (!minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined()) {
     316        // a. Let mnsd be GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1).
     317        unsigned minimumSignificantDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "minimumSignificantDigits"), 1, 21, 1);
     318        // b. ReturnIfAbrupt(mnsd).
     319        if (state.hadException())
     320            return;
     321        // c. Let mxsd be GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21).
     322        unsigned maximumSignificantDigits = intlNumberOption(state, options, Identifier::fromString(&vm, "maximumSignificantDigits"), minimumSignificantDigits, 21, 21);
     323        // d. ReturnIfAbrupt(mxsd).
     324        if (state.hadException())
     325            return;
     326        // e. Set numberFormat.[[minimumSignificantDigits]] to mnsd.
     327        m_minimumSignificantDigits = minimumSignificantDigits;
     328        // f. Set numberFormat.[[maximumSignificantDigits]] to mxsd.
     329        m_maximumSignificantDigits = maximumSignificantDigits;
     330    }
     331
     332    // 43. Let g be GetOption(options, "useGrouping", "boolean", undefined, true).
     333    bool usesFallback;
     334    bool useGrouping = intlBooleanOption(state, options, Identifier::fromString(&vm, "useGrouping"), usesFallback);
     335    if (usesFallback)
     336        useGrouping = true;
     337    // 44. ReturnIfAbrupt(g).
     338    if (state.hadException())
     339        return;
     340    // 45. Set numberFormat.[[useGrouping]] to g.
     341    m_useGrouping = useGrouping;
     342
     343    // FIXME: Implement Steps 46 - 51.
     344    // 46. Let dataLocaleData be Get(localeData, dataLocale).
     345    // 47. Let patterns be Get(dataLocaleData, "patterns").
     346    // 48. Assert: patterns is an object (see 11.2.3).
     347    // 49. Let stylePatterns be Get(patterns, s).
     348    // 50. Set numberFormat.[[positivePattern]] to Get(stylePatterns, "positivePattern").
     349    // 51. Set numberFormat.[[negativePattern]] to Get(stylePatterns, "negativePattern").
     350
     351    // 52. Set numberFormat.[[boundFormat]] to undefined.
     352    // 53. Set numberFormat.[[initializedNumberFormat]] to true.
     353    m_initializedNumberFormat = true;
     354
     355    // 54. Return numberFormat.
    85356}
    86357
     
    109380}
    110381
     382const char* IntlNumberFormat::styleString(Style style)
     383{
     384    switch (style) {
     385    case Style::Decimal:
     386        return "decimal";
     387    case Style::Percent:
     388        return "percent";
     389    case Style::Currency:
     390        return "currency";
     391    }
     392    ASSERT_NOT_REACHED();
     393    return nullptr;
     394}
     395
     396const char* IntlNumberFormat::currencyDisplayString(CurrencyDisplay currencyDisplay)
     397{
     398    switch (currencyDisplay) {
     399    case CurrencyDisplay::Code:
     400        return "code";
     401    case CurrencyDisplay::Symbol:
     402        return "symbol";
     403    case CurrencyDisplay::Name:
     404        return "name";
     405    }
     406    ASSERT_NOT_REACHED();
     407    return nullptr;
     408}
     409
     410JSObject* IntlNumberFormat::resolvedOptions(ExecState& state)
     411{
     412    // 11.3.5 Intl.NumberFormat.prototype.resolvedOptions() (ECMA-402 2.0)
     413    // The function returns a new object whose properties and attributes are set as if
     414    // constructed by an object literal assigning to each of the following properties the
     415    // value of the corresponding internal slot of this NumberFormat object (see 11.4):
     416    // locale, numberingSystem, style, currency, currencyDisplay, minimumIntegerDigits,
     417    // minimumFractionDigits, maximumFractionDigits, minimumSignificantDigits,
     418    // maximumSignificantDigits, and useGrouping. Properties whose corresponding internal
     419    // slots are not present are not assigned.
     420
     421    if (!m_initializedNumberFormat) {
     422        initializeNumberFormat(state, jsUndefined(), jsUndefined());
     423        ASSERT(!state.hadException());
     424    }
     425
     426    VM& vm = state.vm();
     427    JSObject* options = constructEmptyObject(&state);
     428    options->putDirect(vm, vm.propertyNames->locale, jsString(&state, m_locale));
     429    options->putDirect(vm, Identifier::fromString(&vm, "numberingSystem"), jsString(&state, m_numberingSystem));
     430    options->putDirect(vm, Identifier::fromString(&vm, "style"), jsNontrivialString(&state, ASCIILiteral(styleString(m_style))));
     431    if (m_style == Style::Currency) {
     432        options->putDirect(vm, Identifier::fromString(&vm, "currency"), jsNontrivialString(&state, m_currency));
     433        options->putDirect(vm, Identifier::fromString(&vm, "currencyDisplay"), jsNontrivialString(&state, ASCIILiteral(currencyDisplayString(m_currencyDisplay))));
     434    }
     435    options->putDirect(vm, Identifier::fromString(&vm, "minimumIntegerDigits"), jsNumber(m_minimumIntegerDigits));
     436    options->putDirect(vm, Identifier::fromString(&vm, "minimumFractionDigits"), jsNumber(m_minimumFractionDigits));
     437    options->putDirect(vm, Identifier::fromString(&vm, "maximumFractionDigits"), jsNumber(m_maximumFractionDigits));
     438    if (m_minimumSignificantDigits) {
     439        ASSERT(m_maximumSignificantDigits);
     440        options->putDirect(vm, Identifier::fromString(&vm, "minimumSignificantDigits"), jsNumber(m_minimumSignificantDigits));
     441        options->putDirect(vm, Identifier::fromString(&vm, "maximumSignificantDigits"), jsNumber(m_maximumSignificantDigits));
     442    }
     443    options->putDirect(vm, Identifier::fromString(&vm, "useGrouping"), jsBoolean(m_useGrouping));
     444    return options;
     445}
     446
     447void IntlNumberFormat::setBoundFormat(VM& vm, JSBoundFunction* format)
     448{
     449    m_boundFormat.set(vm, this, format);
     450}
     451
    111452} // namespace JSC
    112453
  • trunk/Source/JavaScriptCore/runtime/IntlNumberFormat.h

    r187575 r196434  
    4545    DECLARE_INFO;
    4646
     47    void initializeNumberFormat(ExecState&, JSValue locales, JSValue optionsValue);
     48    JSObject* resolvedOptions(ExecState&);
     49
    4750    JSBoundFunction* boundFormat() const { return m_boundFormat.get(); }
    4851    void setBoundFormat(VM&, JSBoundFunction*);
     
    5457    static void visitChildren(JSCell*, SlotVisitor&);
    5558
     59private:
     60    enum class Style { Decimal, Percent, Currency };
     61    enum class CurrencyDisplay { Code, Symbol, Name };
     62
     63    const char* styleString(Style);
     64    const char* currencyDisplayString(CurrencyDisplay);
     65
     66    String m_locale;
     67    String m_numberingSystem;
     68    Style m_style { Style::Decimal };
     69    String m_currency;
     70    CurrencyDisplay m_currencyDisplay;
     71    unsigned m_minimumIntegerDigits { 1 };
     72    unsigned m_minimumFractionDigits { 0 };
     73    unsigned m_maximumFractionDigits { 3 };
     74    unsigned m_minimumSignificantDigits { 0 };
     75    unsigned m_maximumSignificantDigits { 0 };
    5676    WriteBarrier<JSBoundFunction> m_boundFormat;
     77    bool m_useGrouping { true };
     78    bool m_initializedNumberFormat { false };
    5779};
    5880   
  • trunk/Source/JavaScriptCore/runtime/IntlNumberFormatConstructor.cpp

    r192831 r196434  
    105105
    106106    // 4. Return InitializeNumberFormat(numberFormat, locales, options).
    107     // FIXME: return JSValue::encode(InitializeNumberFormat(numberFormat, locales, options));
    108 
     107    JSValue locales = state->argument(0);
     108    JSValue options = state->argument(1);
     109    numberFormat->initializeNumberFormat(*state, locales, options);
    109110    return JSValue::encode(numberFormat);
    110111}
     
    124125
    125126    // 4. Return InitializeNumberFormat(numberFormat, locales, options).
    126     // FIXME: return JSValue::encode(InitializeNumberFormat(numberFormat, locales, options));
    127 
     127    JSValue locales = state->argument(0);
     128    JSValue options = state->argument(1);
     129    numberFormat->initializeNumberFormat(*state, locales, options);
    128130    return JSValue::encode(numberFormat);
    129131}
  • trunk/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp

    r192831 r196434  
    3535#include "JSCellInlines.h"
    3636#include "JSObject.h"
    37 #include "ObjectConstructor.h"
    3837#include "StructureInlines.h"
    3938
    4039namespace JSC {
    41 
    42 STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(IntlNumberFormatPrototype);
    4340
    4441static EncodedJSValue JSC_HOST_CALL IntlNumberFormatPrototypeGetterFormat(ExecState*);
     
    119116{
    120117    // 11.3.5 Intl.NumberFormat.prototype.resolvedOptions() (ECMA-402 2.0)
    121     IntlNumberFormat* nf = jsDynamicCast<IntlNumberFormat*>(state->thisValue());
    122     if (!nf)
     118    IntlNumberFormat* numberFormat = jsDynamicCast<IntlNumberFormat*>(state->thisValue());
     119    if (!numberFormat)
    123120        return JSValue::encode(throwTypeError(state, ASCIILiteral("Intl.NumberFormat.prototype.resolvedOptions called on value that's not an object initialized as a NumberFormat")));
    124121
    125     // The function returns a new object whose properties and attributes are set as if constructed by an object literal assigning to each of the following properties the value of the corresponding internal slot of this NumberFormat object (see 11.4): locale, numberingSystem, style, currency, currencyDisplay, minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, minimumSignificantDigits, maximumSignificantDigits, and useGrouping. Properties whose corresponding internal slots are not present are not assigned.
    126 
    127     JSObject* options = constructEmptyObject(state);
    128 
    129     // FIXME: Populate object from internal slots.
    130 
    131     return JSValue::encode(options);
     122    return JSValue::encode(numberFormat->resolvedOptions(*state));
    132123}
    133124
  • trunk/Source/JavaScriptCore/runtime/IntlObject.cpp

    r196223 r196434  
    208208        // e. Return value.
    209209        return stringValue;
     210    }
     211
     212    // 6. Else return fallback.
     213    return fallback;
     214}
     215
     216unsigned intlNumberOption(ExecState& state, JSValue options, PropertyName property, unsigned minimum, unsigned maximum, unsigned fallback)
     217{
     218    // 9.2.9 GetNumberOption (options, property, minimum, maximum, fallback) (ECMA-402 2.0)
     219    // 1. Let opts be ToObject(options).
     220    JSObject* opts = options.toObject(&state);
     221
     222    // 2. ReturnIfAbrupt(opts).
     223    if (state.hadException())
     224        return 0;
     225
     226    // 3. Let value be Get(opts, property).
     227    JSValue value = opts->get(&state, property);
     228
     229    // 4. ReturnIfAbrupt(value).
     230    if (state.hadException())
     231        return 0;
     232
     233    // 5. If value is not undefined, then
     234    if (!value.isUndefined()) {
     235        // a. Let value be ToNumber(value).
     236        double doubleValue = value.toNumber(&state);
     237        // b. ReturnIfAbrupt(value).
     238        if (state.hadException())
     239            return 0;
     240        // 1. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
     241        if (!(doubleValue >= minimum && doubleValue <= maximum)) {
     242            state.vm().throwException(&state, createRangeError(&state, *property.publicName() + " is out of range"));
     243            return 0;
     244        }
     245
     246        // c. Return floor(value).
     247        return static_cast<unsigned>(doubleValue);
    210248    }
    211249
     
    925963}
    926964
    927 Vector<String> getNumberingSystemsForLocale(const String& locale)
     965Vector<String> numberingSystemsForLocale(const String& locale)
    928966{
    929967    static NeverDestroyed<Vector<String>> cachedNumberingSystems;
  • trunk/Source/JavaScriptCore/runtime/IntlObject.h

    r194387 r196434  
    6262bool intlBooleanOption(ExecState&, JSValue options, PropertyName, bool& usesFallback);
    6363String intlStringOption(ExecState&, JSValue options, PropertyName, std::initializer_list<const char*> values, const char* notFound, const char* fallback);
     64unsigned intlNumberOption(ExecState&, JSValue options, PropertyName, unsigned minimum, unsigned maximum, unsigned fallback);
    6465Vector<String> canonicalizeLocaleList(ExecState&, JSValue locales);
    6566HashMap<String, String> resolveLocale(const HashSet<String>& availableLocales, const Vector<String>& requestedLocales, const HashMap<String, String>& options, const char* const relevantExtensionKeys[], size_t relevantExtensionKeyCount, Vector<String> (*localeData)(const String&, size_t));
     
    6768String removeUnicodeLocaleExtension(const String& locale);
    6869String bestAvailableLocale(const HashSet<String>& availableLocales, const String& requestedLocale);
    69 Vector<String> getNumberingSystemsForLocale(const String& locale);
     70Vector<String> numberingSystemsForLocale(const String& locale);
    7071
    7172} // namespace JSC
Note: See TracChangeset for help on using the changeset viewer.