[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 45a5e93..a1de7b4 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 ef3abe0..09acfb0 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 4ff9203..7b3941c 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 5388461..d261dfa 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 7901ef0..110d850 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 ff22514..67358b1 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 8fca42a..aeb8265 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 d46ddbf..8394282 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 941711b..3ec2297 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 849bdb3..fa2a635 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