[INTL] Implement Intl.Collator.prototype.resolvedOptions ()
authorsukolsak@gmail.com <sukolsak@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 21 Oct 2015 22:14:38 +0000 (22:14 +0000)
committersukolsak@gmail.com <sukolsak@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 21 Oct 2015 22:14:38 +0000 (22:14 +0000)
https://bugs.webkit.org/show_bug.cgi?id=147601

Reviewed by Benjamin Poulain.

Source/JavaScriptCore:

This patch implements Intl.Collator.prototype.resolvedOptions() according
to the ECMAScript 2015 Internationalization API spec (ECMA-402 2nd edition.)
It also implements the abstract operations InitializeCollator, ResolveLocale,
LookupMatcher, and BestFitMatcher.

* runtime/CommonIdentifiers.h:
* runtime/IntlCollator.h:
(JSC::IntlCollator::usage):
(JSC::IntlCollator::setUsage):
(JSC::IntlCollator::locale):
(JSC::IntlCollator::setLocale):
(JSC::IntlCollator::collation):
(JSC::IntlCollator::setCollation):
(JSC::IntlCollator::numeric):
(JSC::IntlCollator::setNumeric):
(JSC::IntlCollator::sensitivity):
(JSC::IntlCollator::setSensitivity):
(JSC::IntlCollator::ignorePunctuation):
(JSC::IntlCollator::setIgnorePunctuation):
* runtime/IntlCollatorConstructor.cpp:
(JSC::sortLocaleData):
(JSC::searchLocaleData):
(JSC::initializeCollator):
(JSC::constructIntlCollator):
(JSC::callIntlCollator):
* runtime/IntlCollatorPrototype.cpp:
(JSC::IntlCollatorPrototypeFuncResolvedOptions):
* runtime/IntlObject.cpp:
(JSC::defaultLocale):
(JSC::getIntlBooleanOption):
(JSC::getIntlStringOption):
(JSC::removeUnicodeLocaleExtension):
(JSC::lookupMatcher):
(JSC::bestFitMatcher):
(JSC::resolveLocale):
(JSC::lookupSupportedLocales):
* runtime/IntlObject.h:

LayoutTests:

* js/intl-collator-expected.txt:
* js/script-tests/intl-collator.js:
(testCollator):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@191406 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/js/intl-collator-expected.txt
LayoutTests/js/script-tests/intl-collator.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/CommonIdentifiers.h
Source/JavaScriptCore/runtime/IntlCollator.h
Source/JavaScriptCore/runtime/IntlCollatorConstructor.cpp
Source/JavaScriptCore/runtime/IntlCollatorPrototype.cpp
Source/JavaScriptCore/runtime/IntlObject.cpp
Source/JavaScriptCore/runtime/IntlObject.h

index 45a5e93bd31b23d4916a72dbafdfdb1eaa5a8d7d..a1de7b4700d79d3a5044e8448b0b1f1b1836ad36 100644 (file)
@@ -1,3 +1,14 @@
+2015-10-21  Sukolsak Sakshuwong  <sukolsak@gmail.com>
+
+        [INTL] Implement Intl.Collator.prototype.resolvedOptions ()
+        https://bugs.webkit.org/show_bug.cgi?id=147601
+
+        Reviewed by Benjamin Poulain.
+
+        * js/intl-collator-expected.txt:
+        * js/script-tests/intl-collator.js:
+        (testCollator):
+
 2015-10-21  Dean Jackson  <dino@apple.com>
 
         Null dereference loading Blink layout test svg/filters/display-none-filter-primitive.html
index ef3abe020d4359d6439b8bfb6f97eaf6b2d189f0..09acfb066b4e96be38c2aaf84d28d0dccc03bf10 100644 (file)
@@ -7,11 +7,73 @@ PASS Intl.Collator is an instance of Function
 PASS Intl.Collator() is an instance of Intl.Collator
 PASS Intl.Collator.call({}) is an instance of Intl.Collator
 PASS new Intl.Collator() is an instance of Intl.Collator
+PASS Intl.Collator('en') is an instance of Intl.Collator
+PASS Intl.Collator(42) is an instance of Intl.Collator
+PASS Intl.Collator(null) threw exception TypeError: null is not an object (evaluating 'Intl.Collator(null)').
+PASS Intl.Collator({ get length() { throw 42; } }) threw exception 42.
+PASS Intl.Collator('en', { }) is an instance of Intl.Collator
+PASS Intl.Collator('en', 42) is an instance of Intl.Collator
+PASS Intl.Collator('en', null) threw exception TypeError: null is not an object (evaluating 'Intl.Collator('en', null)').
 PASS class DerivedCollator extends Intl.Collator {};(new DerivedCollator) instanceof DerivedCollator is true
 PASS class DerivedCollator extends Intl.Collator {};(new DerivedCollator) instanceof Intl.Collator is true
 PASS class DerivedCollator extends Intl.Collator {};new DerivedCollator().compare('a', 'b') === -1 is true
 PASS class DerivedCollator extends Intl.Collator {};Object.getPrototypeOf(new DerivedCollator) === DerivedCollator.prototype is true
 PASS class DerivedCollator extends Intl.Collator {};Object.getPrototypeOf(Object.getPrototypeOf(new DerivedCollator)) === Intl.Collator.prototype is true
+PASS testCollator(Intl.Collator('en'), [{locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('eN-uS'), [{locale: 'en-US'}]) is true
+PASS testCollator(Intl.Collator(['en', 'de']), [{locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('de'), [{locale: 'de'}]) is true
+PASS testCollator(Intl.Collator('en-u-co-eor'), [{locale: 'en-u-co-eor', collation: 'eor'}, {locale: 'en'}]) is true
+PASS testCollator(new Intl.Collator('en-u-co-eor'), [{locale: 'en-u-co-eor', collation: 'eor'}, {locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('En-U-Co-Eor'), [{locale: 'en-u-co-eor', collation: 'eor'}, {locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('en-u-co-phonebk'), [{locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('en-u-co-standard'), [{locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('en-u-co-search'), [{locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('en-u-co-abcd'), [{locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('de-u-co-phonebk'), [{locale: 'de-u-co-phonebk', collation: 'phonebk'}]) is true
+PASS testCollator(Intl.Collator('en-u-kn'), [{locale: 'en', numeric: true}]) is true
+PASS testCollator(Intl.Collator('en-u-kn-true'), [{locale: 'en-u-kn-true', numeric: true}]) is true
+PASS testCollator(Intl.Collator('en-u-kn-false'), [{locale: 'en-u-kn-false', numeric: false}]) is true
+PASS testCollator(Intl.Collator('en-u-kn-abcd'), [{locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('en-u-aa-aaaa-kn-true-bb-bbbb-co-eor-cc-cccc-y-yyd'), [{locale: 'en-u-co-eor-kn-true', collation: 'eor', numeric: true}, {locale: 'en-u-kn-true', numeric: true}]) is true
+PASS testCollator(Intl.Collator('en-u-kn-true-a-aa'), [{locale: 'en-u-kn-true', numeric: true}]) is true
+PASS testCollator(Intl.Collator('en-a-aa-u-kn-true'), [{locale: 'en-u-kn-true', numeric: true}]) is true
+PASS testCollator(Intl.Collator('en-a-aa-u-kn-true-b-bb'), [{locale: 'en-u-kn-true', numeric: true}]) is true
+PASS testCollator(Intl.Collator('en', {usage: 'sort'}), [{locale: 'en', usage: 'sort'}]) is true
+PASS testCollator(Intl.Collator('en', {usage: 'search'}), [{locale: 'en', usage: 'search'}]) is true
+PASS Intl.Collator('en', {usage: 'Sort'}) threw exception RangeError: usage must be either "sort" or "search".
+PASS Intl.Collator('en', { get usage() { throw 42; } }) threw exception 42.
+PASS Intl.Collator('en', {usage: {toString() { throw 42; }}}) threw exception 42.
+PASS testCollator(Intl.Collator('en', {localeMatcher: 'lookup'}), [{locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('en', {localeMatcher: 'best fit'}), [{locale: 'en'}]) is true
+PASS Intl.Collator('en', {localeMatcher: 'LookUp'}) threw exception RangeError: localeMatcher must be either "lookup" or "best fit".
+PASS Intl.Collator('en', { get localeMatcher() { throw 42; } }) threw exception 42.
+PASS testCollator(Intl.Collator('en', {numeric: true}), [{locale: 'en', numeric: true}]) is true
+PASS testCollator(Intl.Collator('en', {numeric: false}), [{locale: 'en', numeric: false}]) is true
+PASS testCollator(Intl.Collator('en', {numeric: 'false'}), [{locale: 'en', numeric: true}]) is true
+PASS testCollator(Intl.Collator('en', {numeric: { }}), [{locale: 'en', numeric: true}]) is true
+PASS Intl.Collator('en', { get numeric() { throw 42; } }) threw exception 42.
+PASS testCollator(Intl.Collator('en', {caseFirst: 'upper'}), [{locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('en', {caseFirst: 'lower'}), [{locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('en', {caseFirst: 'false'}), [{locale: 'en'}]) is true
+PASS testCollator(Intl.Collator('en', {caseFirst: false}), [{locale: 'en'}]) is true
+PASS Intl.Collator('en', {caseFirst: 'true'}) threw exception RangeError: caseFirst must be either "upper", "lower", or "false".
+PASS Intl.Collator('en', { get caseFirst() { throw 42; } }) threw exception 42.
+PASS testCollator(Intl.Collator('en', {sensitivity: 'base'}), [{locale: 'en', sensitivity: 'base'}]) is true
+PASS testCollator(Intl.Collator('en', {sensitivity: 'accent'}), [{locale: 'en', sensitivity: 'accent'}]) is true
+PASS testCollator(Intl.Collator('en', {sensitivity: 'case'}), [{locale: 'en', sensitivity: 'case'}]) is true
+PASS testCollator(Intl.Collator('en', {sensitivity: 'variant'}), [{locale: 'en', sensitivity: 'variant'}]) is true
+PASS Intl.Collator('en', {sensitivity: 'abcd'}) threw exception RangeError: sensitivity must be either "base", "accent", "case", or "variant".
+PASS Intl.Collator('en', { get sensitivity() { throw 42; } }) threw exception 42.
+PASS testCollator(Intl.Collator('en', {ignorePunctuation: true}), [{locale: 'en', ignorePunctuation: true}]) is true
+PASS testCollator(Intl.Collator('en', {ignorePunctuation: false}), [{locale: 'en', ignorePunctuation: false}]) is true
+PASS testCollator(Intl.Collator('en', {ignorePunctuation: 'false'}), [{locale: 'en', ignorePunctuation: true}]) is true
+PASS Intl.Collator('en', { get ignorePunctuation() { throw 42; } }) threw exception 42.
+PASS testCollator(Intl.Collator('en-u-kn-true', {numeric: false}), [{locale: 'en', numeric: false}]) is true
+PASS testCollator(Intl.Collator('en-u-kn-false', {numeric: true}), [{locale: 'en', numeric: true}]) is true
+PASS testCollator(Intl.Collator('en-u-kn-true', {numeric: true}), [{locale: 'en-u-kn-true', numeric: true}]) is true
+PASS testCollator(Intl.Collator('en-u-kn-false', {numeric: false}), [{locale: 'en-u-kn-false', numeric: false}]) is true
+PASS testCollator(Intl.Collator('en-a-aa-u-kn-false-co-eor-b-bb', {usage: 'sort', numeric: true, caseFirst: 'lower', sensitivity: 'case', ignorePunctuation: true}), [{locale: 'en-u-co-eor', usage: 'sort', sensitivity: 'case', ignorePunctuation: true, collation: 'eor', numeric: true}, {locale: 'en', usage: 'sort', sensitivity: 'case', ignorePunctuation: true, numeric: true}]) is true
 PASS Intl.Collator.length is 0
 PASS Object.getOwnPropertyDescriptor(Intl.Collator, 'prototype').writable is false
 PASS Object.getOwnPropertyDescriptor(Intl.Collator, 'prototype').enumerable is false
index 4ff920345657248b555e7c5427b0157a6f9b657c..7b3941cccdad886a34a12f84970a55f655bd96b5 100644 (file)
@@ -9,6 +9,13 @@ shouldBeType("Intl.Collator", "Function");
 shouldBeType("Intl.Collator()", "Intl.Collator");
 shouldBeType("Intl.Collator.call({})", "Intl.Collator");
 shouldBeType("new Intl.Collator()", "Intl.Collator");
+shouldBeType("Intl.Collator('en')", "Intl.Collator");
+shouldBeType("Intl.Collator(42)", "Intl.Collator");
+shouldThrow("Intl.Collator(null)", "'TypeError: null is not an object (evaluating \\'Intl.Collator(null)\\')'");
+shouldThrow("Intl.Collator({ get length() { throw 42; } })", "'42'");
+shouldBeType("Intl.Collator('en', { })", "Intl.Collator");
+shouldBeType("Intl.Collator('en', 42)", "Intl.Collator");
+shouldThrow("Intl.Collator('en', null)", "'TypeError: null is not an object (evaluating \\'Intl.Collator(\\'en\\', null)\\')'");
 
 // Subclassable
 var classPrefix = "class DerivedCollator extends Intl.Collator {};";
@@ -18,6 +25,104 @@ shouldBeTrue(classPrefix + "new DerivedCollator().compare('a', 'b') === -1");
 shouldBeTrue(classPrefix + "Object.getPrototypeOf(new DerivedCollator) === DerivedCollator.prototype");
 shouldBeTrue(classPrefix + "Object.getPrototypeOf(Object.getPrototypeOf(new DerivedCollator)) === Intl.Collator.prototype");
 
+var testCollator = function(collator, possibleOptionDifferences) {
+    var possibleOptions = possibleOptionDifferences.map(function(difference) {
+        var defaultOptions = {
+            locale: "en",
+            usage: "sort",
+            sensitivity: "variant",
+            ignorePunctuation: false,
+            collation: "default",
+            numeric: false
+        }
+        Object.assign(defaultOptions, difference);
+        return JSON.stringify(defaultOptions);
+    });
+    var actualOptions = JSON.stringify(collator.resolvedOptions())
+    return possibleOptions.includes(actualOptions);
+}
+
+// Locale is processed correctly.
+shouldBeTrue("testCollator(Intl.Collator('en'), [{locale: 'en'}])");
+shouldBeTrue("testCollator(Intl.Collator('eN-uS'), [{locale: 'en-US'}])");
+shouldBeTrue("testCollator(Intl.Collator(['en', 'de']), [{locale: 'en'}])");
+shouldBeTrue("testCollator(Intl.Collator('de'), [{locale: 'de'}])");
+
+// The "co" key is processed correctly.
+shouldBeTrue("testCollator(Intl.Collator('en-u-co-eor'), [{locale: 'en-u-co-eor', collation: 'eor'}, {locale: 'en'}])");
+shouldBeTrue("testCollator(new Intl.Collator('en-u-co-eor'), [{locale: 'en-u-co-eor', collation: 'eor'}, {locale: 'en'}])");
+shouldBeTrue("testCollator(Intl.Collator('En-U-Co-Eor'), [{locale: 'en-u-co-eor', collation: 'eor'}, {locale: 'en'}])");
+shouldBeTrue("testCollator(Intl.Collator('en-u-co-phonebk'), [{locale: 'en'}])");
+shouldBeTrue("testCollator(Intl.Collator('en-u-co-standard'), [{locale: 'en'}])");
+shouldBeTrue("testCollator(Intl.Collator('en-u-co-search'), [{locale: 'en'}])");
+shouldBeTrue("testCollator(Intl.Collator('en-u-co-abcd'), [{locale: 'en'}])");
+shouldBeTrue("testCollator(Intl.Collator('de-u-co-phonebk'), [{locale: 'de-u-co-phonebk', collation: 'phonebk'}])");
+
+// The "kn" key is processed correctly.
+shouldBeTrue("testCollator(Intl.Collator('en-u-kn'), [{locale: 'en', numeric: true}])");
+shouldBeTrue("testCollator(Intl.Collator('en-u-kn-true'), [{locale: 'en-u-kn-true', numeric: true}])");
+shouldBeTrue("testCollator(Intl.Collator('en-u-kn-false'), [{locale: 'en-u-kn-false', numeric: false}])");
+shouldBeTrue("testCollator(Intl.Collator('en-u-kn-abcd'), [{locale: 'en'}])");
+
+// Ignores irrelevant extension keys.
+shouldBeTrue("testCollator(Intl.Collator('en-u-aa-aaaa-kn-true-bb-bbbb-co-eor-cc-cccc-y-yyd'), [{locale: 'en-u-co-eor-kn-true', collation: 'eor', numeric: true}, {locale: 'en-u-kn-true', numeric: true}])");
+
+// Ignores other extensions.
+shouldBeTrue("testCollator(Intl.Collator('en-u-kn-true-a-aa'), [{locale: 'en-u-kn-true', numeric: true}])");
+shouldBeTrue("testCollator(Intl.Collator('en-a-aa-u-kn-true'), [{locale: 'en-u-kn-true', numeric: true}])");
+shouldBeTrue("testCollator(Intl.Collator('en-a-aa-u-kn-true-b-bb'), [{locale: 'en-u-kn-true', numeric: true}])");
+
+// The option usage is processed correctly.
+shouldBeTrue("testCollator(Intl.Collator('en', {usage: 'sort'}), [{locale: 'en', usage: 'sort'}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {usage: 'search'}), [{locale: 'en', usage: 'search'}])");
+shouldThrow("Intl.Collator('en', {usage: 'Sort'})", '\'RangeError: usage must be either "sort" or "search"\'');
+shouldThrow("Intl.Collator('en', { get usage() { throw 42; } })", "'42'");
+shouldThrow("Intl.Collator('en', {usage: {toString() { throw 42; }}})", "'42'");
+
+// The option localeMatcher is processed correctly.
+shouldBeTrue("testCollator(Intl.Collator('en', {localeMatcher: 'lookup'}), [{locale: 'en'}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {localeMatcher: 'best fit'}), [{locale: 'en'}])");
+shouldThrow("Intl.Collator('en', {localeMatcher: 'LookUp'})", '\'RangeError: localeMatcher must be either "lookup" or "best fit"\'');
+shouldThrow("Intl.Collator('en', { get localeMatcher() { throw 42; } })", "'42'");
+
+// The option numeric is processed correctly.
+shouldBeTrue("testCollator(Intl.Collator('en', {numeric: true}), [{locale: 'en', numeric: true}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {numeric: false}), [{locale: 'en', numeric: false}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {numeric: 'false'}), [{locale: 'en', numeric: true}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {numeric: { }}), [{locale: 'en', numeric: true}])");
+shouldThrow("Intl.Collator('en', { get numeric() { throw 42; } })", "'42'");
+
+// The option caseFirst is processed correctly.
+shouldBeTrue("testCollator(Intl.Collator('en', {caseFirst: 'upper'}), [{locale: 'en'}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {caseFirst: 'lower'}), [{locale: 'en'}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {caseFirst: 'false'}), [{locale: 'en'}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {caseFirst: false}), [{locale: 'en'}])");
+shouldThrow("Intl.Collator('en', {caseFirst: 'true'})", '\'RangeError: caseFirst must be either "upper", "lower", or "false"\'');
+shouldThrow("Intl.Collator('en', { get caseFirst() { throw 42; } })", "'42'");
+
+// The option sensitivity is processed correctly.
+shouldBeTrue("testCollator(Intl.Collator('en', {sensitivity: 'base'}), [{locale: 'en', sensitivity: 'base'}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {sensitivity: 'accent'}), [{locale: 'en', sensitivity: 'accent'}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {sensitivity: 'case'}), [{locale: 'en', sensitivity: 'case'}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {sensitivity: 'variant'}), [{locale: 'en', sensitivity: 'variant'}])");
+shouldThrow("Intl.Collator('en', {sensitivity: 'abcd'})", '\'RangeError: sensitivity must be either "base", "accent", "case", or "variant"\'');
+shouldThrow("Intl.Collator('en', { get sensitivity() { throw 42; } })", "'42'");
+
+// The option ignorePunctuation is processed correctly.
+shouldBeTrue("testCollator(Intl.Collator('en', {ignorePunctuation: true}), [{locale: 'en', ignorePunctuation: true}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {ignorePunctuation: false}), [{locale: 'en', ignorePunctuation: false}])");
+shouldBeTrue("testCollator(Intl.Collator('en', {ignorePunctuation: 'false'}), [{locale: 'en', ignorePunctuation: true}])");
+shouldThrow("Intl.Collator('en', { get ignorePunctuation() { throw 42; } })", "'42'");
+
+// Options override the language tag.
+shouldBeTrue("testCollator(Intl.Collator('en-u-kn-true', {numeric: false}), [{locale: 'en', numeric: false}])");
+shouldBeTrue("testCollator(Intl.Collator('en-u-kn-false', {numeric: true}), [{locale: 'en', numeric: true}])");
+shouldBeTrue("testCollator(Intl.Collator('en-u-kn-true', {numeric: true}), [{locale: 'en-u-kn-true', numeric: true}])");
+shouldBeTrue("testCollator(Intl.Collator('en-u-kn-false', {numeric: false}), [{locale: 'en-u-kn-false', numeric: false}])");
+
+// Options and extension keys are processed correctly.
+shouldBeTrue("testCollator(Intl.Collator('en-a-aa-u-kn-false-co-eor-b-bb', {usage: 'sort', numeric: true, caseFirst: 'lower', sensitivity: 'case', ignorePunctuation: true}), [{locale: 'en-u-co-eor', usage: 'sort', sensitivity: 'case', ignorePunctuation: true, collation: 'eor', numeric: true}, {locale: 'en', usage: 'sort', sensitivity: 'case', ignorePunctuation: true, numeric: true}])");
+
 // 10.2 Properties of the Intl.Collator Constructor
 
 // length property (whose value is 0)
index 5388461c95d1c0fd41dc90d70a9e1002a2e8fe76..d261dfa8fc10c52c6464f2574ce17ff08726c1d6 100644 (file)
@@ -1,3 +1,48 @@
+2015-10-21  Sukolsak Sakshuwong  <sukolsak@gmail.com>
+
+        [INTL] Implement Intl.Collator.prototype.resolvedOptions ()
+        https://bugs.webkit.org/show_bug.cgi?id=147601
+
+        Reviewed by Benjamin Poulain.
+
+        This patch implements Intl.Collator.prototype.resolvedOptions() according
+        to the ECMAScript 2015 Internationalization API spec (ECMA-402 2nd edition.)
+        It also implements the abstract operations InitializeCollator, ResolveLocale,
+        LookupMatcher, and BestFitMatcher.
+
+        * runtime/CommonIdentifiers.h:
+        * runtime/IntlCollator.h:
+        (JSC::IntlCollator::usage):
+        (JSC::IntlCollator::setUsage):
+        (JSC::IntlCollator::locale):
+        (JSC::IntlCollator::setLocale):
+        (JSC::IntlCollator::collation):
+        (JSC::IntlCollator::setCollation):
+        (JSC::IntlCollator::numeric):
+        (JSC::IntlCollator::setNumeric):
+        (JSC::IntlCollator::sensitivity):
+        (JSC::IntlCollator::setSensitivity):
+        (JSC::IntlCollator::ignorePunctuation):
+        (JSC::IntlCollator::setIgnorePunctuation):
+        * runtime/IntlCollatorConstructor.cpp:
+        (JSC::sortLocaleData):
+        (JSC::searchLocaleData):
+        (JSC::initializeCollator):
+        (JSC::constructIntlCollator):
+        (JSC::callIntlCollator):
+        * runtime/IntlCollatorPrototype.cpp:
+        (JSC::IntlCollatorPrototypeFuncResolvedOptions):
+        * runtime/IntlObject.cpp:
+        (JSC::defaultLocale):
+        (JSC::getIntlBooleanOption):
+        (JSC::getIntlStringOption):
+        (JSC::removeUnicodeLocaleExtension):
+        (JSC::lookupMatcher):
+        (JSC::bestFitMatcher):
+        (JSC::resolveLocale):
+        (JSC::lookupSupportedLocales):
+        * runtime/IntlObject.h:
+
 2015-10-21  Saam barati  <sbarati@apple.com>
 
         C calls in PolymorphicAccess shouldn't assume that the top of the stack looks like a JSC JIT frame and enable *ByIdFlush in FTL
index 7901ef0c202d145747f84b82b5d48edb98d9d4f3..110d8502ee14c1f97eafd9dac0b9440df2c45fc0 100644 (file)
     macro(bytecodesID) \
     macro(callee) \
     macro(caller) \
+    macro(caseFirst) \
     macro(clear) \
     macro(close) \
     macro(closed) \
+    macro(collation) \
     macro(column) \
     macro(compilationKind) \
     macro(compilations) \
     macro(href) \
     macro(id) \
     macro(ignoreCase) \
+    macro(ignorePunctuation) \
     macro(index) \
     macro(indexedDB) \
     macro(inferredName) \
     macro(lastIndex) \
     macro(length) \
     macro(line) \
+    macro(locale) \
     macro(localeMatcher) \
     macro(message) \
     macro(multiline) \
     macro(numInlinedCalls) \
     macro(numInlinedGetByIds) \
     macro(numInlinedPutByIds) \
+    macro(numeric) \
     macro(of) \
     macro(opcode) \
     macro(origin) \
     macro(reload) \
     macro(replace) \
     macro(resolve) \
+    macro(sensitivity) \
     macro(set) \
     macro(showModalDialog) \
     macro(size) \
     macro(toLocaleString) \
     macro(toPrecision) \
     macro(toString) \
+    macro(usage) \
     macro(value) \
     macro(valueOf) \
     macro(values) \
index ff22514941a211863f9c2842fc170d61bca46102..67358b1face0668be514c8a3075aa295114ee268 100644 (file)
@@ -44,6 +44,18 @@ public:
 
     DECLARE_INFO;
 
+    const String& usage() const { return m_usage; }
+    void setUsage(const String& usage) { m_usage = usage; }
+    const String& locale() const { return m_locale; }
+    void setLocale(const String& locale) { m_locale = locale; }
+    const String& collation() const { return m_collation; }
+    void setCollation(const String& collation) { m_collation = collation; }
+    bool numeric() const { return m_numeric; }
+    void setNumeric(bool numeric) { m_numeric = numeric; }
+    const String& sensitivity() const { return m_sensitivity; }
+    void setSensitivity(const String& sensitivity) { m_sensitivity = sensitivity; }
+    bool ignorePunctuation() const { return m_ignorePunctuation; }
+    void setIgnorePunctuation(bool ignorePunctuation) { m_ignorePunctuation = ignorePunctuation; }
     JSBoundFunction* boundCompare() const { return m_boundCompare.get(); }
     void setBoundCompare(VM&, JSBoundFunction*);
 
@@ -54,7 +66,13 @@ protected:
     static void visitChildren(JSCell*, SlotVisitor&);
 
 private:
+    String m_usage;
+    String m_locale;
+    String m_collation;
+    String m_sensitivity;
     WriteBarrier<JSBoundFunction> m_boundCompare;
+    bool m_numeric;
+    bool m_ignorePunctuation;
 };
     
 EncodedJSValue JSC_HOST_CALL IntlCollatorFuncCompare(ExecState*);
index 8fca42ad3c250604971980c36a5eb85c9a21162a..aeb82650370b122cba5429e72cfe8105d9cff602 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2015 Andy VanWagoner (thetalecrafter@gmail.com)
+ * Copyright (C) 2015 Sukolsak Sakshuwong (sukolsak@gmail.com)
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #include "JSCJSValueInlines.h"
 #include "JSCellInlines.h"
 #include "Lookup.h"
+#include "ObjectConstructor.h"
 #include "SlotVisitorInlines.h"
 #include "StructureInlines.h"
+#include <unicode/ucol.h>
 
 namespace JSC {
 
@@ -58,6 +61,232 @@ const ClassInfo IntlCollatorConstructor::s_info = { "Function", &InternalFunctio
 @end
 */
 
+static Vector<String> sortLocaleData(const String& locale, const String& key)
+{
+    // 9.1 Internal slots of Service Constructors & 10.2.3 Internal slots (ECMA-402 2.0)
+    Vector<String> keyLocaleData;
+    if (key == "co") {
+        // 10.2.3 "The first element of [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co must be null for all locale values."
+        keyLocaleData.append(String());
+
+        UErrorCode status = U_ZERO_ERROR;
+        UEnumeration* enumeration = ucol_getKeywordValuesForLocale("collation", locale.utf8().data(), TRUE, &status);
+        if (U_SUCCESS(status)) {
+            const char* keywordValue;
+            int32_t length;
+            while ((keywordValue = uenum_next(enumeration, &length, &status)) && U_SUCCESS(status)) {
+                String collation(keywordValue, length);
+
+                // 10.2.3 "The values "standard" and "search" must not be used as elements in any [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co array."
+                if (collation == "standard" || collation == "search")
+                    continue;
+
+                // Map keyword values to BCP 47 equivalents.
+                if (collation == "dictionary")
+                    collation = ASCIILiteral("dict");
+                else if (collation == "gb2312han")
+                    collation = ASCIILiteral("gb2312");
+                else if (collation == "phonebook")
+                    collation = ASCIILiteral("phonebk");
+                else if (collation == "traditional")
+                    collation = ASCIILiteral("trad");
+
+                keyLocaleData.append(collation);
+            }
+            uenum_close(enumeration);
+        }
+    } else if (key == "kn") {
+        keyLocaleData.append(ASCIILiteral("false"));
+        keyLocaleData.append(ASCIILiteral("true"));
+    } else
+        ASSERT_NOT_REACHED();
+    return keyLocaleData;
+}
+
+static Vector<String> searchLocaleData(const String&, const String& key)
+{
+    // 9.1 Internal slots of Service Constructors & 10.2.3 Internal slots (ECMA-402 2.0)
+    Vector<String> keyLocaleData;
+    if (key == "co") {
+        // 10.2.3 "The first element of [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co must be null for all locale values."
+        keyLocaleData.append(String());
+    } else if (key == "kn") {
+        keyLocaleData.append(ASCIILiteral("false"));
+        keyLocaleData.append(ASCIILiteral("true"));
+    } else if (key == "sensitivity") {
+        // 10.2.3 "[[searchLocaleData]][locale] must have a sensitivity property with a String value equal to "base", "accent", "case", or "variant" for all locale values."
+        keyLocaleData.append("variant");
+    } else
+        ASSERT_NOT_REACHED();
+    return keyLocaleData;
+}
+
+static IntlCollator* initializeCollator(ExecState* exec, IntlCollator* collator, JSValue locales, JSValue optionsValue)
+{
+    // 10.1.1 InitializeCollator (collator, locales, options) (ECMA-402 2.0)
+
+    // 1. If collator has an [[initializedIntlObject]] internal slot with value true, throw a TypeError exception.
+    // 2. Set collator.[[initializedIntlObject]] to true.
+
+    // 3. Let requestedLocales be CanonicalizeLocaleList(locales).
+    Vector<String> requestedLocales = canonicalizeLocaleList(exec, locales);
+    // 4. ReturnIfAbrupt(requestedLocales).
+    if (exec->hadException())
+        return nullptr;
+
+    // 5. If options is undefined, then
+    JSObject* options;
+    if (optionsValue.isUndefined()) {
+        // a. Let options be ObjectCreate(%ObjectPrototype%).
+        options = constructEmptyObject(exec);
+    } else { // 6. Else
+        // a. Let options be ToObject(options).
+        options = optionsValue.toObject(exec);
+        // b. ReturnIfAbrupt(options).
+        if (exec->hadException())
+            return nullptr;
+    }
+
+    // 7. Let u be GetOption(options, "usage", "string", «"sort", "search"», "sort").
+    const HashSet<String> usages({ ASCIILiteral("sort"), ASCIILiteral("search") });
+    String usage = getIntlStringOption(exec, options, exec->vm().propertyNames->usage, usages, "usage must be either \"sort\" or \"search\"", ASCIILiteral("sort"));
+    // 8. ReturnIfAbrupt(u).
+    if (exec->hadException())
+        return nullptr;
+    // 9. Set collator.[[usage]] to u.
+    collator->setUsage(usage);
+
+    // 10. If u is "sort", then
+    // a. Let localeData be the value of %Collator%.[[sortLocaleData]];
+    // 11. Else
+    // a. Let localeData be the value of %Collator%.[[searchLocaleData]].
+    Vector<String> (*localeData)(const String&, const String&);
+    if (usage == "sort")
+        localeData = sortLocaleData;
+    else
+        localeData = searchLocaleData;
+
+    // 12. Let opt be a new Record.
+    HashMap<String, String> opt;
+
+    // 13. Let matcher be GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
+    const HashSet<String> matchers({ ASCIILiteral("lookup"), ASCIILiteral("best fit") });
+    String matcher = getIntlStringOption(exec, options, exec->vm().propertyNames->localeMatcher, matchers, "localeMatcher must be either \"lookup\" or \"best fit\"", ASCIILiteral("best fit"));
+    // 14. ReturnIfAbrupt(matcher).
+    if (exec->hadException())
+        return nullptr;
+    // 15. Set opt.[[localeMatcher]] to matcher.
+    opt.set(ASCIILiteral("localeMatcher"), matcher);
+
+    // 16. For each row in Table 1, except the header row, do:
+    // a. Let key be the name given in the Key column of the row.
+    // b. Let prop be the name given in the Property column of the row.
+    // c. Let type be the string given in the Type column of the row.
+    // d. Let list be a List containing the Strings given in the Values column of the row, or undefined if no strings are given.
+    // e. Let value be GetOption(options, prop, type, list, undefined).
+    // f. ReturnIfAbrupt(value).
+    // g. If the string given in the Type column of the row is "boolean" and value is not undefined, then
+    //    i. Let value be ToString(value).
+    //    ii. ReturnIfAbrupt(value).
+    // h. Set opt.[[<key>]] to value.
+    {
+        String numericString;
+        bool usesFallback;
+        bool numeric = getIntlBooleanOption(exec, options, exec->vm().propertyNames->numeric, usesFallback);
+        if (exec->hadException())
+            return nullptr;
+        if (!usesFallback)
+            numericString = ASCIILiteral(numeric ? "true" : "false");
+        opt.set(ASCIILiteral("kn"), numericString);
+    }
+    {
+        const HashSet<String> caseFirsts({ ASCIILiteral("upper"), ASCIILiteral("lower"), ASCIILiteral("false") });
+        String caseFirst = getIntlStringOption(exec, options, exec->vm().propertyNames->caseFirst, caseFirsts, "caseFirst must be either \"upper\", \"lower\", or \"false\"", String());
+        if (exec->hadException())
+            return nullptr;
+        opt.set(ASCIILiteral("kf"), caseFirst);
+    }
+
+    // 17. Let relevantExtensionKeys be the value of %Collator%.[[relevantExtensionKeys]].
+    // FIXME: Implement kf (caseFirst).
+    const Vector<String> relevantExtensionKeys { ASCIILiteral("co"), ASCIILiteral("kn") };
+
+    // 18. Let r be ResolveLocale(%Collator%.[[availableLocales]], requestedLocales, opt, relevantExtensionKeys, localeData).
+    const HashSet<String>& availableLocales = exec->callee()->globalObject()->intlCollatorAvailableLocales();
+    HashMap<String, String> result = resolveLocale(availableLocales, requestedLocales, opt, relevantExtensionKeys, localeData);
+
+    // 19. Set collator.[[locale]] to the value of r.[[locale]].
+    collator->setLocale(result.get(ASCIILiteral("locale")));
+
+    // 20. Let k be 0.
+    // 21. Let lenValue be Get(relevantExtensionKeys, "length").
+    // 22. Let len be ToLength(lenValue).
+    // 23. Repeat while k < len:
+    // a. Let Pk be ToString(k).
+    // b. Let key be Get(relevantExtensionKeys, Pk).
+    // c. ReturnIfAbrupt(key).
+    // d. If key is "co", then
+    //    i. Let property be "collation".
+    //    ii. Let value be the value of r.[[co]].
+    //    iii. If value is null, let value be "default".
+    // e. Else use the row of Table 1 that contains the value of key in the Key column:
+    //    i. Let property be the name given in the Property column of the row.
+    //    ii. Let value be the value of r.[[<key>]].
+    //    iii. If the name given in the Type column of the row is "boolean", let value be the result of comparing value with "true".
+    // f. Set collator.[[<property>]] to value.
+    // g. Increase k by 1.
+    ASSERT(relevantExtensionKeys.size() == 2);
+    {
+        ASSERT(relevantExtensionKeys[0] == "co");
+        const String& value = result.get(ASCIILiteral("co"));
+        collator->setCollation(value.isNull() ? ASCIILiteral("default") : value);
+    }
+    {
+        ASSERT(relevantExtensionKeys[1] == "kn");
+        const String& value = result.get(ASCIILiteral("kn"));
+        collator->setNumeric(value == "true");
+    }
+
+    // 24. Let s be GetOption(options, "sensitivity", "string", «"base", "accent", "case", "variant"», undefined).
+    const HashSet<String> sensitivities({ ASCIILiteral("base"), ASCIILiteral("accent"), ASCIILiteral("case"), ASCIILiteral("variant") });
+    String sensitivity = getIntlStringOption(exec, options, exec->vm().propertyNames->sensitivity, sensitivities, "sensitivity must be either \"base\", \"accent\", \"case\", or \"variant\"", String());
+    // 25. ReturnIfAbrupt(s).
+    if (exec->hadException())
+        return nullptr;
+    // 26. If s is undefined, then
+    if (sensitivity.isNull()) {
+        // a. If u is "sort", then let s be "variant".
+        if (usage == "sort")
+            sensitivity = ASCIILiteral("variant");
+        else {
+            // b. Else
+            //    i. Let dataLocale be the value of r.[[dataLocale]].
+            //    ii. Let dataLocaleData be Get(localeData, dataLocale).
+            //    iii. Let s be Get(dataLocaleData, "sensitivity").
+            const String& dataLocale = result.get(ASCIILiteral("dataLocale"));
+            sensitivity = localeData(dataLocale, ASCIILiteral("sensitivity"))[0];
+        }
+    }
+    // 27. Set collator.[[sensitivity]] to s.
+    collator->setSensitivity(sensitivity);
+
+    // 28. Let ip be GetOption(options, "ignorePunctuation", "boolean", undefined, false).
+    bool usesFallback;
+    bool ignorePunctuation = getIntlBooleanOption(exec, options, exec->vm().propertyNames->ignorePunctuation, usesFallback);
+    if (usesFallback)
+        ignorePunctuation = false;
+    // 29. ReturnIfAbrupt(ip).
+    if (exec->hadException())
+        return nullptr;
+    // 30. Set collator.[[ignorePunctuation]] to ip.
+    collator->setIgnorePunctuation(ignorePunctuation);
+
+    // 31. Set collator.[[boundCompare]] to undefined.
+    // 32. Set collator.[[initializedCollator]] to true.
+    // 33. Return collator.
+    return collator;
+}
+
 IntlCollatorConstructor* IntlCollatorConstructor::create(VM& vm, Structure* structure, IntlCollatorPrototype* collatorPrototype, Structure* collatorStructure)
 {
     IntlCollatorConstructor* constructor = new (NotNull, allocateCell<IntlCollatorConstructor>(vm.heap)) IntlCollatorConstructor(vm, structure);
@@ -103,9 +332,9 @@ static EncodedJSValue JSC_HOST_CALL constructIntlCollator(ExecState* exec)
     ASSERT(collator);
 
     // 4. Return InitializeCollator(collator, locales, options).
-    // FIXME: return JSValue::encode(InitializeCollator(collator, locales, options));
-
-    return JSValue::encode(collator);
+    JSValue locales = exec->argument(0);
+    JSValue options = exec->argument(1);
+    return JSValue::encode(initializeCollator(exec, collator, locales, options));
 }
 
 static EncodedJSValue JSC_HOST_CALL callIntlCollator(ExecState* exec)
@@ -122,9 +351,9 @@ static EncodedJSValue JSC_HOST_CALL callIntlCollator(ExecState* exec)
     ASSERT(collator);
 
     // 4. Return InitializeCollator(collator, locales, options).
-    // FIXME: return JSValue::encode(InitializeCollator(collator, locales, options));
-
-    return JSValue::encode(collator);
+    JSValue locales = exec->argument(0);
+    JSValue options = exec->argument(1);
+    return JSValue::encode(initializeCollator(exec, collator, locales, options));
 }
 
 ConstructType IntlCollatorConstructor::getConstructData(JSCell*, ConstructData& constructData)
index d46ddbfc8c0f9d61f9fc52a279c5df7f30bca734..83942823f5683c5e80d3e5916442a72d4f7c4bcd 100644 (file)
@@ -39,8 +39,6 @@
 
 namespace JSC {
 
-STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(IntlCollatorPrototype);
-
 static EncodedJSValue JSC_HOST_CALL IntlCollatorPrototypeGetterCompare(ExecState*);
 static EncodedJSValue JSC_HOST_CALL IntlCollatorPrototypeFuncResolvedOptions(ExecState*);
 
@@ -122,12 +120,22 @@ EncodedJSValue JSC_HOST_CALL IntlCollatorPrototypeFuncResolvedOptions(ExecState*
     if (!collator)
         return JSValue::encode(throwTypeError(exec, ASCIILiteral("Intl.Collator.prototype.resolvedOptions called on value that's not an object initialized as a Collator")));
 
-    // 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 Collator object (see 10.4): locale, usage, sensitivity, ignorePunctuation, collation, as well as those properties shown in Table 1 whose keys are included in the %Collator%[[relevantExtensionKeys]] internal slot of the standard built-in object that is the initial value of Intl.Collator.
+    // 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 Collator object (see 10.4): locale,
+    // usage, sensitivity, ignorePunctuation, collation, as well as those properties shown
+    // in Table 1 whose keys are included in the %Collator%[[relevantExtensionKeys]]
+    // internal slot of the standard built-in object that is the initial value of
+    // Intl.Collator.
 
+    VM& vm = exec->vm();
     JSObject* options = constructEmptyObject(exec);
-
-    // FIXME: Populate object from internal slots.
-
+    options->putDirect(vm, vm.propertyNames->locale, jsString(exec, collator->locale()));
+    options->putDirect(vm, vm.propertyNames->usage, jsString(exec, collator->usage()));
+    options->putDirect(vm, vm.propertyNames->sensitivity, jsString(exec, collator->sensitivity()));
+    options->putDirect(vm, vm.propertyNames->ignorePunctuation, jsBoolean(collator->ignorePunctuation()));
+    options->putDirect(vm, vm.propertyNames->collation, jsString(exec, collator->collation()));
+    options->putDirect(vm, vm.propertyNames->numeric, jsBoolean(collator->numeric()));
     return JSValue::encode(options);
 }
 
index 941711bbd0176d72dd71b8a520a3843e01358a87..3ec2297b0c9687f339b86daad271cb05eb68e581 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2015 Andy VanWagoner (thetalecrafter@gmail.com)
+ * Copyright (C) 2015 Sukolsak Sakshuwong (sukolsak@gmail.com)
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -54,6 +55,12 @@ STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(IntlObject);
 
 namespace JSC {
 
+struct MatcherResult {
+    String locale;
+    String extension;
+    size_t extensionIndex;
+};
+
 const ClassInfo IntlObject::s_info = { "Object", &Base::s_info, 0, CREATE_METHOD_TABLE(IntlObject) };
 
 IntlObject::IntlObject(VM& vm, Structure* structure)
@@ -105,7 +112,53 @@ Structure* IntlObject::createStructure(VM& vm, JSGlobalObject* globalObject, JSV
     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
 }
 
-static String getIntlStringOption(ExecState* exec, JSValue options, PropertyName property, const HashSet<String>& values, const char* notFound, String fallback)
+static String defaultLocale()
+{
+    // 6.2.4 DefaultLocale ()
+    // FIXME: Implement this method.
+    return ASCIILiteral("en");
+}
+
+bool getIntlBooleanOption(ExecState* exec, JSValue options, PropertyName property, bool& usesFallback)
+{
+    // 9.2.9 GetOption (options, property, type, values, fallback)
+    // For type="boolean". values is always undefined.
+
+    // 1. Let opts be ToObject(options).
+    JSObject* opts = options.toObject(exec);
+
+    // 2. ReturnIfAbrupt(opts).
+    if (exec->hadException())
+        return false;
+
+    // 3. Let value be Get(opts, property).
+    JSValue value = opts->get(exec, property);
+
+    // 4. ReturnIfAbrupt(value).
+    if (exec->hadException())
+        return false;
+
+    // 5. If value is not undefined, then
+    if (!value.isUndefined()) {
+        // a. Assert: type is "boolean" or "string".
+        // Function dedicated to "boolean".
+
+        // b. If type is "boolean", then
+        // i. Let value be ToBoolean(value).
+        bool booleanValue = value.toBoolean(exec);
+
+        // e. Return value.
+        usesFallback = false;
+        return booleanValue;
+    }
+
+    // 6. Else return fallback.
+    // Because fallback can be undefined, we let the caller handle it instead.
+    usesFallback = true;
+    return false;
+}
+
+String getIntlStringOption(ExecState* exec, JSValue options, PropertyName property, const HashSet<String>& values, const char* notFound, String fallback)
 {
     // 9.2.9 GetOption (options, property, type, values, fallback)
     // For type="string".
@@ -551,6 +604,203 @@ static String bestAvailableLocale(const HashSet<String>& availableLocales, const
     return String();
 }
 
+static String removeUnicodeLocaleExtension(const String& locale)
+{
+    Vector<String> parts;
+    locale.split('-', parts);
+    StringBuilder builder;
+    size_t partsSize = parts.size();
+    if (partsSize > 0)
+        builder.append(parts[0]);
+    for (size_t p = 1; p < partsSize; ++p) {
+        if (parts[p] == "u") {
+            // Skip the u- and anything that follows until another singleton.
+            // While the next part is part of the unicode extension, skip it.
+            while (p + 1 < partsSize && parts[p + 1].length() > 1)
+                ++p;
+        } else {
+            builder.append('-');
+            builder.append(parts[p]);
+        }
+    }
+    return builder.toString();
+}
+
+static MatcherResult lookupMatcher(const HashSet<String>& availableLocales, const Vector<String>& requestedLocales)
+{
+    // 9.2.3 LookupMatcher (availableLocales, requestedLocales) (ECMA-402 2.0)
+    String locale;
+    String noExtensionsLocale;
+    String availableLocale;
+    for (size_t i = 0; i < requestedLocales.size() && availableLocale.isNull(); ++i) {
+        locale = requestedLocales[i];
+        noExtensionsLocale = removeUnicodeLocaleExtension(locale);
+        availableLocale = bestAvailableLocale(availableLocales, noExtensionsLocale);
+    }
+
+    MatcherResult result;
+    if (!availableLocale.isNull()) {
+        result.locale = availableLocale;
+        if (locale != noExtensionsLocale) {
+            // i. Let extension be the String value consisting of the first substring of locale that is a Unicode locale extension sequence.
+            // ii. Let extensionIndex be the character position of the initial "-" extension sequence within locale.
+            size_t extensionIndex = locale.find("-u-");
+            RELEASE_ASSERT(extensionIndex != notFound);
+
+            size_t extensionLength = locale.length() - extensionIndex;
+            size_t end = extensionIndex + 3;
+            while (end < locale.length()) {
+                end = locale.find('-', end);
+                if (end == notFound)
+                    break;
+                if (end + 2 < locale.length() && locale[end + 2] == '-') {
+                    extensionLength = end - extensionIndex;
+                    break;
+                }
+                end++;
+            }
+            result.extension = locale.substring(extensionIndex, extensionLength);
+            result.extensionIndex = extensionIndex;
+        }
+    } else
+        result.locale = defaultLocale();
+    return result;
+}
+
+static MatcherResult bestFitMatcher(const HashSet<String>& availableLocales, const Vector<String>& requestedLocales)
+{
+    // 9.2.4 BestFitMatcher (availableLocales, requestedLocales) (ECMA-402 2.0)
+    // FIXME: Implement something better than lookup.
+    return lookupMatcher(availableLocales, requestedLocales);
+}
+
+HashMap<String, String> resolveLocale(const HashSet<String>& availableLocales, const Vector<String>& requestedLocales, const HashMap<String, String>& options, const Vector<String>& relevantExtensionKeys, Vector<String> (*localeData)(const String&, const String&))
+{
+    // 9.2.5 ResolveLocale (availableLocales, requestedLocales, options, relevantExtensionKeys, localeData) (ECMA-402 2.0)
+    // 1. Let matcher be the value of options.[[localeMatcher]].
+    const String& matcher = options.get(ASCIILiteral("localeMatcher"));
+
+    // 2. If matcher is "lookup", then
+    MatcherResult (*matcherOperation)(const HashSet<String>&, const Vector<String>&);
+    if (matcher == "lookup") {
+        // a. Let MatcherOperation be the abstract operation LookupMatcher.
+        matcherOperation = lookupMatcher;
+    } else { // 3. Else
+        // a. Let MatcherOperation be the abstract operation BestFitMatcher.
+        matcherOperation = bestFitMatcher;
+    }
+
+    // 4. Let r be MatcherOperation(availableLocales, requestedLocales).
+    MatcherResult matcherResult = matcherOperation(availableLocales, requestedLocales);
+
+    // 5. Let foundLocale be the value of r.[[locale]].
+    String foundLocale = matcherResult.locale;
+
+    // 6. If r has an [[extension]] field, then
+    Vector<String> extensionSubtags;
+    if (!matcherResult.extension.isNull()) {
+        // a. Let extension be the value of r.[[extension]].
+        // b. Let extensionIndex be the value of r.[[extensionIndex]].
+        // c. Let extensionSubtags be Call(%StringProto_split%, extension, «"-"») .
+        // d. Let extensionSubtagsLength be Get(CreateArrayFromList(extensionSubtags), "length").
+        matcherResult.extension.split('-', extensionSubtags);
+    }
+
+    // 7. Let result be a new Record.
+    HashMap<String, String> result;
+
+    // 8. Set result.[[dataLocale]] to foundLocale.
+    result.set(ASCIILiteral("dataLocale"), foundLocale);
+
+    // 9. Let supportedExtension be "-u".
+    String supportedExtension = ASCIILiteral("-u");
+
+    // 10. Let k be 0.
+    // 11. Let rExtensionKeys be ToObject(CreateArrayFromList(relevantExtensionKeys)).
+    // 12. ReturnIfAbrupt(rExtensionKeys).
+    // 13. Let len be ToLength(Get(rExtensionKeys, "length")).
+    // 14. Repeat while k < len
+    for (size_t k = 0; k < relevantExtensionKeys.size(); ++k) {
+        // a. Let key be Get(rExtensionKeys, ToString(k)).
+        // b. ReturnIfAbrupt(key).
+        const String& key = relevantExtensionKeys[k];
+
+        // c. Let foundLocaleData be Get(localeData, foundLocale).
+        // d. ReturnIfAbrupt(foundLocaleData).
+        // e. Let keyLocaleData be ToObject(Get(foundLocaleData, key)).
+        // f. ReturnIfAbrupt(keyLocaleData).
+        Vector<String> keyLocaleData = localeData(foundLocale, key);
+
+        // g. Let value be ToString(Get(keyLocaleData, "0")).
+        // h. ReturnIfAbrupt(value).
+        ASSERT(!keyLocaleData.isEmpty());
+        String value = keyLocaleData[0];
+
+        // i. Let supportedExtensionAddition be "".
+        String supportedExtensionAddition;
+
+        // j. If extensionSubtags is not undefined, then
+        if (!extensionSubtags.isEmpty()) {
+            // i. Let keyPos be Call(%ArrayProto_indexOf%, extensionSubtags, «key») .
+            size_t keyPos = extensionSubtags.find(key);
+            // ii. If keyPos != -1, then
+            if (keyPos != notFound) {
+                // 1. If keyPos + 1 < extensionSubtagsLength and the length of the result of Get(extensionSubtags, ToString(keyPos +1)) is greater than 2, then
+                if (keyPos + 1 < extensionSubtags.size() && extensionSubtags[keyPos + 1].length() > 2) {
+                    const String& requestedValue = extensionSubtags[keyPos + 1];
+                    if (keyLocaleData.contains(requestedValue)) {
+                        value = requestedValue;
+                        supportedExtensionAddition = "-" + key + '-' + value;
+                    }
+                } else if (keyLocaleData.contains(static_cast<String>(ASCIILiteral("true")))) {
+                    // 2. Else, if the result of Call(%StringProto_includes%, keyLocaleData, «"true"») is true, then
+                    value = ASCIILiteral("true");
+                }
+            }
+        }
+
+        // k. If options has a field [[<key>]], then
+        HashMap<String, String>::const_iterator iterator = options.find(key);
+        if (iterator != options.end()) {
+            // i. Let optionsValue be the value of ToString(options.[[<key>]]).
+            // ii. ReturnIfAbrupt(optionsValue).
+            const String& optionsValue = iterator->value;
+            // iii. If the result of Call(%StringProto_includes%, keyLocaleData, «optionsValue») is true, then
+            if (!optionsValue.isNull() && keyLocaleData.contains(optionsValue)) {
+                // 1. If optionsValue is not equal to value, then
+                if (optionsValue != value) {
+                    value = optionsValue;
+                    supportedExtensionAddition = String();
+                }
+            }
+        }
+
+        // l. Set result.[[<key>]] to value.
+        result.set(key, value);
+
+        // m. Append supportedExtensionAddition to supportedExtension.
+        supportedExtension.append(supportedExtensionAddition);
+
+        // n. Increase k by 1.
+    }
+
+    // 15. If the number of elements in supportedExtension is greater than 2, then
+    if (supportedExtension.length() > 2) {
+        // a. Let preExtension be the substring of foundLocale from position 0, inclusive, to position extensionIndex, exclusive.
+        // b. Let postExtension be the substring of foundLocale from position extensionIndex to the end of the string.
+        // c. Let foundLocale be the concatenation of preExtension, supportedExtension, and postExtension.
+        String preExtension = foundLocale.substring(0, matcherResult.extensionIndex);
+        String postExtension = foundLocale.substring(matcherResult.extensionIndex);
+        foundLocale = preExtension + supportedExtension + postExtension;
+    }
+
+    // 16. Set result.[[locale]] to foundLocale.
+    result.set(ASCIILiteral("locale"), foundLocale);
+
+    // 17. Return result.
+    return result;
+}
+
 static JSArray* lookupSupportedLocales(ExecState* exec, const HashSet<String>& availableLocales, const Vector<String>& requestedLocales)
 {
     // 9.2.6 LookupSupportedLocales (availableLocales, requestedLocales)
@@ -576,27 +826,10 @@ static JSArray* lookupSupportedLocales(ExecState* exec, const HashSet<String>& a
         // a. Let Pk be ToString(k).
         // b. Let locale be Get(rLocales, Pk).
         // c. ReturnIfAbrupt(locale).
-        String locale = requestedLocales[k];
+        const String& locale = requestedLocales[k];
 
         // d. Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences removed.
-        Vector<String> parts;
-        locale.split('-', parts);
-        StringBuilder builder;
-        size_t partsSize = parts.size();
-        if (partsSize > 0)
-            builder.append(parts[0]);
-        for (size_t p = 1; p < partsSize; ++p) {
-            if (parts[p] == "u") {
-                // Skip the u- and anything that follows until another singleton.
-                // While the next part is part of the unicode extension, skip it.
-                while (p + 1 < partsSize && parts[p + 1].length() > 1)
-                    ++p;
-            } else {
-                builder.append('-');
-                builder.append(parts[p]);
-            }
-        }
-        String noExtensionsLocale = builder.toString();
+        String noExtensionsLocale = removeUnicodeLocaleExtension(locale);
 
         // e. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
         String availableLocale = bestAvailableLocale(availableLocales, noExtensionsLocale);
index 849bdb3902447bcf98acec7095f0b21d6526ea84..fa2a6350eaa54859c9174d7d5ca0cb4a9eb0675a 100644 (file)
@@ -57,7 +57,10 @@ private:
     IntlObject(VM&, Structure*);
 };
 
+bool getIntlBooleanOption(ExecState*, JSValue options, PropertyName, bool& usesFallback);
+String getIntlStringOption(ExecState*, JSValue options, PropertyName, const HashSet<String>& values, const char* notFound, String fallback);
 Vector<String> canonicalizeLocaleList(ExecState*, JSValue locales);
+HashMap<String, String> resolveLocale(const HashSet<String>& availableLocales, const Vector<String>& requestedLocales, const HashMap<String, String>& options, const Vector<String>& relevantExtensionKeys, Vector<String> (*localeData)(const String&, const String&));
 JSValue supportedLocales(ExecState*, const HashSet<String>& availableLocales, const Vector<String>& requestedLocales, JSValue options);
 
 } // namespace JSC