Home | History | Annotate | Download | only in src
      1 // Copyright 2013 the V8 project authors. All rights reserved.
      2 // Redistribution and use in source and binary forms, with or without
      3 // modification, are permitted provided that the following conditions are
      4 // met:
      5 //
      6 //     * Redistributions of source code must retain the above copyright
      7 //       notice, this list of conditions and the following disclaimer.
      8 //     * Redistributions in binary form must reproduce the above
      9 //       copyright notice, this list of conditions and the following
     10 //       disclaimer in the documentation and/or other materials provided
     11 //       with the distribution.
     12 //     * Neither the name of Google Inc. nor the names of its
     13 //       contributors may be used to endorse or promote products derived
     14 //       from this software without specific prior written permission.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 // limitations under the License.
     28 
     29 // ECMAScript 402 API implementation.
     30 
     31 /**
     32  * Intl object is a single object that has some named properties,
     33  * all of which are constructors.
     34  */
     35 $Object.defineProperty(global, "Intl", { enumerable: false, value: (function() {
     36 
     37 'use strict';
     38 
     39 var Intl = {};
     40 
     41 var undefined = global.undefined;
     42 
     43 var AVAILABLE_SERVICES = ['collator',
     44                           'numberformat',
     45                           'dateformat',
     46                           'breakiterator'];
     47 
     48 /**
     49  * Caches available locales for each service.
     50  */
     51 var AVAILABLE_LOCALES = {
     52   'collator': undefined,
     53   'numberformat': undefined,
     54   'dateformat': undefined,
     55   'breakiterator': undefined
     56 };
     57 
     58 /**
     59  * Caches default ICU locale.
     60  */
     61 var DEFAULT_ICU_LOCALE = undefined;
     62 
     63 /**
     64  * Unicode extension regular expression.
     65  */
     66 var UNICODE_EXTENSION_RE = undefined;
     67 
     68 function GetUnicodeExtensionRE() {
     69   if (UNICODE_EXTENSION_RE === undefined) {
     70     UNICODE_EXTENSION_RE = new $RegExp('-u(-[a-z0-9]{2,8})+', 'g');
     71   }
     72   return UNICODE_EXTENSION_RE;
     73 }
     74 
     75 /**
     76  * Matches any Unicode extension.
     77  */
     78 var ANY_EXTENSION_RE = undefined;
     79 
     80 function GetAnyExtensionRE() {
     81   if (ANY_EXTENSION_RE === undefined) {
     82     ANY_EXTENSION_RE = new $RegExp('-[a-z0-9]{1}-.*', 'g');
     83   }
     84   return ANY_EXTENSION_RE;
     85 }
     86 
     87 /**
     88  * Replace quoted text (single quote, anything but the quote and quote again).
     89  */
     90 var QUOTED_STRING_RE = undefined;
     91 
     92 function GetQuotedStringRE() {
     93   if (QUOTED_STRING_RE === undefined) {
     94     QUOTED_STRING_RE = new $RegExp("'[^']+'", 'g');
     95   }
     96   return QUOTED_STRING_RE;
     97 }
     98 
     99 /**
    100  * Matches valid service name.
    101  */
    102 var SERVICE_RE = undefined;
    103 
    104 function GetServiceRE() {
    105   if (SERVICE_RE === undefined) {
    106     SERVICE_RE =
    107         new $RegExp('^(collator|numberformat|dateformat|breakiterator)$');
    108   }
    109   return SERVICE_RE;
    110 }
    111 
    112 /**
    113  * Validates a language tag against bcp47 spec.
    114  * Actual value is assigned on first run.
    115  */
    116 var LANGUAGE_TAG_RE = undefined;
    117 
    118 function GetLanguageTagRE() {
    119   if (LANGUAGE_TAG_RE === undefined) {
    120     BuildLanguageTagREs();
    121   }
    122   return LANGUAGE_TAG_RE;
    123 }
    124 
    125 /**
    126  * Helps find duplicate variants in the language tag.
    127  */
    128 var LANGUAGE_VARIANT_RE = undefined;
    129 
    130 function GetLanguageVariantRE() {
    131   if (LANGUAGE_VARIANT_RE === undefined) {
    132     BuildLanguageTagREs();
    133   }
    134   return LANGUAGE_VARIANT_RE;
    135 }
    136 
    137 /**
    138  * Helps find duplicate singletons in the language tag.
    139  */
    140 var LANGUAGE_SINGLETON_RE = undefined;
    141 
    142 function GetLanguageSingletonRE() {
    143   if (LANGUAGE_SINGLETON_RE === undefined) {
    144     BuildLanguageTagREs();
    145   }
    146   return LANGUAGE_SINGLETON_RE;
    147 }
    148 
    149 /**
    150  * Matches valid IANA time zone names.
    151  */
    152 var TIMEZONE_NAME_CHECK_RE = undefined;
    153 
    154 function GetTimezoneNameCheckRE() {
    155   if (TIMEZONE_NAME_CHECK_RE === undefined) {
    156     TIMEZONE_NAME_CHECK_RE =
    157         new $RegExp('^([A-Za-z]+)/([A-Za-z]+)(?:_([A-Za-z]+))*$');
    158   }
    159   return TIMEZONE_NAME_CHECK_RE;
    160 }
    161 
    162 /**
    163  * Maps ICU calendar names into LDML type.
    164  */
    165 var ICU_CALENDAR_MAP = {
    166   'gregorian': 'gregory',
    167   'japanese': 'japanese',
    168   'buddhist': 'buddhist',
    169   'roc': 'roc',
    170   'persian': 'persian',
    171   'islamic-civil': 'islamicc',
    172   'islamic': 'islamic',
    173   'hebrew': 'hebrew',
    174   'chinese': 'chinese',
    175   'indian': 'indian',
    176   'coptic': 'coptic',
    177   'ethiopic': 'ethiopic',
    178   'ethiopic-amete-alem': 'ethioaa'
    179 };
    180 
    181 /**
    182  * Map of Unicode extensions to option properties, and their values and types,
    183  * for a collator.
    184  */
    185 var COLLATOR_KEY_MAP = {
    186   'kn': {'property': 'numeric', 'type': 'boolean'},
    187   'kf': {'property': 'caseFirst', 'type': 'string',
    188          'values': ['false', 'lower', 'upper']}
    189 };
    190 
    191 /**
    192  * Map of Unicode extensions to option properties, and their values and types,
    193  * for a number format.
    194  */
    195 var NUMBER_FORMAT_KEY_MAP = {
    196   'nu': {'property': undefined, 'type': 'string'}
    197 };
    198 
    199 /**
    200  * Map of Unicode extensions to option properties, and their values and types,
    201  * for a date/time format.
    202  */
    203 var DATETIME_FORMAT_KEY_MAP = {
    204   'ca': {'property': undefined, 'type': 'string'},
    205   'nu': {'property': undefined, 'type': 'string'}
    206 };
    207 
    208 /**
    209  * Allowed -u-co- values. List taken from:
    210  * http://unicode.org/repos/cldr/trunk/common/bcp47/collation.xml
    211  */
    212 var ALLOWED_CO_VALUES = [
    213   'big5han', 'dict', 'direct', 'ducet', 'gb2312', 'phonebk', 'phonetic',
    214   'pinyin', 'reformed', 'searchjl', 'stroke', 'trad', 'unihan', 'zhuyin'
    215 ];
    216 
    217 /**
    218  * Error message for when function object is created with new and it's not
    219  * a constructor.
    220  */
    221 var ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR =
    222   'Function object that\'s not a constructor was created with new';
    223 
    224 
    225 /**
    226  * Adds bound method to the prototype of the given object.
    227  */
    228 function addBoundMethod(obj, methodName, implementation, length) {
    229   function getter() {
    230     if (!this || typeof this !== 'object' ||
    231         this.__initializedIntlObject === undefined) {
    232         throw new $TypeError('Method ' + methodName + ' called on a ' +
    233                             'non-object or on a wrong type of object.');
    234     }
    235     var internalName = '__bound' + methodName + '__';
    236     if (this[internalName] === undefined) {
    237       var that = this;
    238       var boundMethod;
    239       if (length === undefined || length === 2) {
    240         boundMethod = function(x, y) {
    241           if (%_IsConstructCall()) {
    242             throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
    243           }
    244           return implementation(that, x, y);
    245         }
    246       } else if (length === 1) {
    247         boundMethod = function(x) {
    248           if (%_IsConstructCall()) {
    249             throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
    250           }
    251           return implementation(that, x);
    252         }
    253       } else {
    254         boundMethod = function() {
    255           if (%_IsConstructCall()) {
    256             throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
    257           }
    258           // DateTimeFormat.format needs to be 0 arg method, but can stil
    259           // receive optional dateValue param. If one was provided, pass it
    260           // along.
    261           if (%_ArgumentsLength() > 0) {
    262             return implementation(that, %_Arguments(0));
    263           } else {
    264             return implementation(that);
    265           }
    266         }
    267       }
    268       %FunctionSetName(boundMethod, internalName);
    269       %FunctionRemovePrototype(boundMethod);
    270       %SetNativeFlag(boundMethod);
    271       this[internalName] = boundMethod;
    272     }
    273     return this[internalName];
    274   }
    275 
    276   %FunctionSetName(getter, methodName);
    277   %FunctionRemovePrototype(getter);
    278   %SetNativeFlag(getter);
    279 
    280   $Object.defineProperty(obj.prototype, methodName, {
    281     get: getter,
    282     enumerable: false,
    283     configurable: true
    284   });
    285 }
    286 
    287 
    288 /**
    289  * Returns an intersection of locales and service supported locales.
    290  * Parameter locales is treated as a priority list.
    291  */
    292 function supportedLocalesOf(service, locales, options) {
    293   if (IS_NULL(service.match(GetServiceRE()))) {
    294     throw new $Error('Internal error, wrong service type: ' + service);
    295   }
    296 
    297   // Provide defaults if matcher was not specified.
    298   if (options === undefined) {
    299     options = {};
    300   } else {
    301     options = toObject(options);
    302   }
    303 
    304   var matcher = options.localeMatcher;
    305   if (matcher !== undefined) {
    306     matcher = $String(matcher);
    307     if (matcher !== 'lookup' && matcher !== 'best fit') {
    308       throw new $RangeError('Illegal value for localeMatcher:' + matcher);
    309     }
    310   } else {
    311     matcher = 'best fit';
    312   }
    313 
    314   var requestedLocales = initializeLocaleList(locales);
    315 
    316   // Cache these, they don't ever change per service.
    317   if (AVAILABLE_LOCALES[service] === undefined) {
    318     AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
    319   }
    320 
    321   // Use either best fit or lookup algorithm to match locales.
    322   if (matcher === 'best fit') {
    323     return initializeLocaleList(bestFitSupportedLocalesOf(
    324         requestedLocales, AVAILABLE_LOCALES[service]));
    325   }
    326 
    327   return initializeLocaleList(lookupSupportedLocalesOf(
    328       requestedLocales, AVAILABLE_LOCALES[service]));
    329 }
    330 
    331 
    332 /**
    333  * Returns the subset of the provided BCP 47 language priority list for which
    334  * this service has a matching locale when using the BCP 47 Lookup algorithm.
    335  * Locales appear in the same order in the returned list as in the input list.
    336  */
    337 function lookupSupportedLocalesOf(requestedLocales, availableLocales) {
    338   var matchedLocales = [];
    339   for (var i = 0; i < requestedLocales.length; ++i) {
    340     // Remove -u- extension.
    341     var locale = requestedLocales[i].replace(GetUnicodeExtensionRE(), '');
    342     do {
    343       if (availableLocales[locale] !== undefined) {
    344         // Push requested locale not the resolved one.
    345         matchedLocales.push(requestedLocales[i]);
    346         break;
    347       }
    348       // Truncate locale if possible, if not break.
    349       var pos = locale.lastIndexOf('-');
    350       if (pos === -1) {
    351         break;
    352       }
    353       locale = locale.substring(0, pos);
    354     } while (true);
    355   }
    356 
    357   return matchedLocales;
    358 }
    359 
    360 
    361 /**
    362  * Returns the subset of the provided BCP 47 language priority list for which
    363  * this service has a matching locale when using the implementation
    364  * dependent algorithm.
    365  * Locales appear in the same order in the returned list as in the input list.
    366  */
    367 function bestFitSupportedLocalesOf(requestedLocales, availableLocales) {
    368   // TODO(cira): implement better best fit algorithm.
    369   return lookupSupportedLocalesOf(requestedLocales, availableLocales);
    370 }
    371 
    372 
    373 /**
    374  * Returns a getOption function that extracts property value for given
    375  * options object. If property is missing it returns defaultValue. If value
    376  * is out of range for that property it throws RangeError.
    377  */
    378 function getGetOption(options, caller) {
    379   if (options === undefined) {
    380     throw new $Error('Internal ' + caller + ' error. ' +
    381                     'Default options are missing.');
    382   }
    383 
    384   var getOption = function getOption(property, type, values, defaultValue) {
    385     if (options[property] !== undefined) {
    386       var value = options[property];
    387       switch (type) {
    388         case 'boolean':
    389           value = $Boolean(value);
    390           break;
    391         case 'string':
    392           value = $String(value);
    393           break;
    394         case 'number':
    395           value = $Number(value);
    396           break;
    397         default:
    398           throw new $Error('Internal error. Wrong value type.');
    399       }
    400       if (values !== undefined && values.indexOf(value) === -1) {
    401         throw new $RangeError('Value ' + value + ' out of range for ' + caller +
    402                              ' options property ' + property);
    403       }
    404 
    405       return value;
    406     }
    407 
    408     return defaultValue;
    409   }
    410 
    411   return getOption;
    412 }
    413 
    414 
    415 /**
    416  * Compares a BCP 47 language priority list requestedLocales against the locales
    417  * in availableLocales and determines the best available language to meet the
    418  * request. Two algorithms are available to match the locales: the Lookup
    419  * algorithm described in RFC 4647 section 3.4, and an implementation dependent
    420  * best-fit algorithm. Independent of the locale matching algorithm, options
    421  * specified through Unicode locale extension sequences are negotiated
    422  * separately, taking the caller's relevant extension keys and locale data as
    423  * well as client-provided options into consideration. Returns an object with
    424  * a locale property whose value is the language tag of the selected locale,
    425  * and properties for each key in relevantExtensionKeys providing the selected
    426  * value for that key.
    427  */
    428 function resolveLocale(service, requestedLocales, options) {
    429   requestedLocales = initializeLocaleList(requestedLocales);
    430 
    431   var getOption = getGetOption(options, service);
    432   var matcher = getOption('localeMatcher', 'string',
    433                           ['lookup', 'best fit'], 'best fit');
    434   var resolved;
    435   if (matcher === 'lookup') {
    436     resolved = lookupMatcher(service, requestedLocales);
    437   } else {
    438     resolved = bestFitMatcher(service, requestedLocales);
    439   }
    440 
    441   return resolved;
    442 }
    443 
    444 
    445 /**
    446  * Returns best matched supported locale and extension info using basic
    447  * lookup algorithm.
    448  */
    449 function lookupMatcher(service, requestedLocales) {
    450   if (IS_NULL(service.match(GetServiceRE()))) {
    451     throw new $Error('Internal error, wrong service type: ' + service);
    452   }
    453 
    454   // Cache these, they don't ever change per service.
    455   if (AVAILABLE_LOCALES[service] === undefined) {
    456     AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
    457   }
    458 
    459   for (var i = 0; i < requestedLocales.length; ++i) {
    460     // Remove all extensions.
    461     var locale = requestedLocales[i].replace(GetAnyExtensionRE(), '');
    462     do {
    463       if (AVAILABLE_LOCALES[service][locale] !== undefined) {
    464         // Return the resolved locale and extension.
    465         var extensionMatch = requestedLocales[i].match(GetUnicodeExtensionRE());
    466         var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0];
    467         return {'locale': locale, 'extension': extension, 'position': i};
    468       }
    469       // Truncate locale if possible.
    470       var pos = locale.lastIndexOf('-');
    471       if (pos === -1) {
    472         break;
    473       }
    474       locale = locale.substring(0, pos);
    475     } while (true);
    476   }
    477 
    478   // Didn't find a match, return default.
    479   if (DEFAULT_ICU_LOCALE === undefined) {
    480     DEFAULT_ICU_LOCALE = %GetDefaultICULocale();
    481   }
    482 
    483   return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1};
    484 }
    485 
    486 
    487 /**
    488  * Returns best matched supported locale and extension info using
    489  * implementation dependend algorithm.
    490  */
    491 function bestFitMatcher(service, requestedLocales) {
    492   // TODO(cira): implement better best fit algorithm.
    493   return lookupMatcher(service, requestedLocales);
    494 }
    495 
    496 
    497 /**
    498  * Parses Unicode extension into key - value map.
    499  * Returns empty object if the extension string is invalid.
    500  * We are not concerned with the validity of the values at this point.
    501  */
    502 function parseExtension(extension) {
    503   var extensionSplit = extension.split('-');
    504 
    505   // Assume ['', 'u', ...] input, but don't throw.
    506   if (extensionSplit.length <= 2 ||
    507       (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) {
    508     return {};
    509   }
    510 
    511   // Key is {2}alphanum, value is {3,8}alphanum.
    512   // Some keys may not have explicit values (booleans).
    513   var extensionMap = {};
    514   var previousKey = undefined;
    515   for (var i = 2; i < extensionSplit.length; ++i) {
    516     var length = extensionSplit[i].length;
    517     var element = extensionSplit[i];
    518     if (length === 2) {
    519       extensionMap[element] = undefined;
    520       previousKey = element;
    521     } else if (length >= 3 && length <=8 && previousKey !== undefined) {
    522       extensionMap[previousKey] = element;
    523       previousKey = undefined;
    524     } else {
    525       // There is a value that's too long, or that doesn't have a key.
    526       return {};
    527     }
    528   }
    529 
    530   return extensionMap;
    531 }
    532 
    533 
    534 /**
    535  * Converts parameter to an Object if possible.
    536  */
    537 function toObject(value) {
    538   if (IS_NULL_OR_UNDEFINED(value)) {
    539     throw new $TypeError('Value cannot be converted to an Object.');
    540   }
    541 
    542   return $Object(value);
    543 }
    544 
    545 
    546 /**
    547  * Populates internalOptions object with boolean key-value pairs
    548  * from extensionMap and options.
    549  * Returns filtered extension (number and date format constructors use
    550  * Unicode extensions for passing parameters to ICU).
    551  * It's used for extension-option pairs only, e.g. kn-normalization, but not
    552  * for 'sensitivity' since it doesn't have extension equivalent.
    553  * Extensions like nu and ca don't have options equivalent, so we place
    554  * undefined in the map.property to denote that.
    555  */
    556 function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) {
    557   var extension = '';
    558 
    559   var updateExtension = function updateExtension(key, value) {
    560     return '-' + key + '-' + $String(value);
    561   }
    562 
    563   var updateProperty = function updateProperty(property, type, value) {
    564     if (type === 'boolean' && (typeof value === 'string')) {
    565       value = (value === 'true') ? true : false;
    566     }
    567 
    568     if (property !== undefined) {
    569       defineWEProperty(outOptions, property, value);
    570     }
    571   }
    572 
    573   for (var key in keyValues) {
    574     if (keyValues.hasOwnProperty(key)) {
    575       var value = undefined;
    576       var map = keyValues[key];
    577       if (map.property !== undefined) {
    578         // This may return true if user specifies numeric: 'false', since
    579         // Boolean('nonempty') === true.
    580         value = getOption(map.property, map.type, map.values);
    581       }
    582       if (value !== undefined) {
    583         updateProperty(map.property, map.type, value);
    584         extension += updateExtension(key, value);
    585         continue;
    586       }
    587       // User options didn't have it, check Unicode extension.
    588       // Here we want to convert strings 'true', 'false' into proper Boolean
    589       // values (not a user error).
    590       if (extensionMap.hasOwnProperty(key)) {
    591         value = extensionMap[key];
    592         if (value !== undefined) {
    593           updateProperty(map.property, map.type, value);
    594           extension += updateExtension(key, value);
    595         } else if (map.type === 'boolean') {
    596           // Boolean keys are allowed not to have values in Unicode extension.
    597           // Those default to true.
    598           updateProperty(map.property, map.type, true);
    599           extension += updateExtension(key, true);
    600         }
    601       }
    602     }
    603   }
    604 
    605   return extension === ''? '' : '-u' + extension;
    606 }
    607 
    608 
    609 /**
    610  * Converts all OwnProperties into
    611  * configurable: false, writable: false, enumerable: true.
    612  */
    613 function freezeArray(array) {
    614   array.forEach(function(element, index) {
    615     $Object.defineProperty(array, index, {value: element,
    616                                           configurable: false,
    617                                           writable: false,
    618                                           enumerable: true});
    619   });
    620 
    621   $Object.defineProperty(array, 'length', {value: array.length,
    622                                            writable: false});
    623 
    624   return array;
    625 }
    626 
    627 
    628 /**
    629  * It's sometimes desireable to leave user requested locale instead of ICU
    630  * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter
    631  * one, if that was what user requested).
    632  * This function returns user specified tag if its maximized form matches ICU
    633  * resolved locale. If not we return ICU result.
    634  */
    635 function getOptimalLanguageTag(original, resolved) {
    636   // Returns Array<Object>, where each object has maximized and base properties.
    637   // Maximized: zh -> zh-Hans-CN
    638   // Base: zh-CN-u-ca-gregory -> zh-CN
    639   // Take care of grandfathered or simple cases.
    640   if (original === resolved) {
    641     return original;
    642   }
    643 
    644   var locales = %GetLanguageTagVariants([original, resolved]);
    645   if (locales[0].maximized !== locales[1].maximized) {
    646     return resolved;
    647   }
    648 
    649   // Preserve extensions of resolved locale, but swap base tags with original.
    650   var resolvedBase = new $RegExp('^' + locales[1].base);
    651   return resolved.replace(resolvedBase, locales[0].base);
    652 }
    653 
    654 
    655 /**
    656  * Returns an Object that contains all of supported locales for a given
    657  * service.
    658  * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ
    659  * that is supported. This is required by the spec.
    660  */
    661 function getAvailableLocalesOf(service) {
    662   var available = %AvailableLocalesOf(service);
    663 
    664   for (var i in available) {
    665     if (available.hasOwnProperty(i)) {
    666       var parts = i.match(/^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/);
    667       if (parts !== null) {
    668         // Build xx-ZZ. We don't care about the actual value,
    669         // as long it's not undefined.
    670         available[parts[1] + '-' + parts[3]] = null;
    671       }
    672     }
    673   }
    674 
    675   return available;
    676 }
    677 
    678 
    679 /**
    680  * Defines a property and sets writable and enumerable to true.
    681  * Configurable is false by default.
    682  */
    683 function defineWEProperty(object, property, value) {
    684   $Object.defineProperty(object, property,
    685                          {value: value, writable: true, enumerable: true});
    686 }
    687 
    688 
    689 /**
    690  * Adds property to an object if the value is not undefined.
    691  * Sets configurable descriptor to false.
    692  */
    693 function addWEPropertyIfDefined(object, property, value) {
    694   if (value !== undefined) {
    695     defineWEProperty(object, property, value);
    696   }
    697 }
    698 
    699 
    700 /**
    701  * Defines a property and sets writable, enumerable and configurable to true.
    702  */
    703 function defineWECProperty(object, property, value) {
    704   $Object.defineProperty(object, property,
    705                          {value: value,
    706                           writable: true,
    707                           enumerable: true,
    708                           configurable: true});
    709 }
    710 
    711 
    712 /**
    713  * Adds property to an object if the value is not undefined.
    714  * Sets all descriptors to true.
    715  */
    716 function addWECPropertyIfDefined(object, property, value) {
    717   if (value !== undefined) {
    718     defineWECProperty(object, property, value);
    719   }
    720 }
    721 
    722 
    723 /**
    724  * Returns titlecased word, aMeRricA -> America.
    725  */
    726 function toTitleCaseWord(word) {
    727   return word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase();
    728 }
    729 
    730 /**
    731  * Canonicalizes the language tag, or throws in case the tag is invalid.
    732  */
    733 function canonicalizeLanguageTag(localeID) {
    734   // null is typeof 'object' so we have to do extra check.
    735   if (typeof localeID !== 'string' && typeof localeID !== 'object' ||
    736       IS_NULL(localeID)) {
    737     throw new $TypeError('Language ID should be string or object.');
    738   }
    739 
    740   var localeString = $String(localeID);
    741 
    742   if (isValidLanguageTag(localeString) === false) {
    743     throw new $RangeError('Invalid language tag: ' + localeString);
    744   }
    745 
    746   // This call will strip -kn but not -kn-true extensions.
    747   // ICU bug filled - http://bugs.icu-project.org/trac/ticket/9265.
    748   // TODO(cira): check if -u-kn-true-kc-true-kh-true still throws after
    749   // upgrade to ICU 4.9.
    750   var tag = %CanonicalizeLanguageTag(localeString);
    751   if (tag === 'invalid-tag') {
    752     throw new $RangeError('Invalid language tag: ' + localeString);
    753   }
    754 
    755   return tag;
    756 }
    757 
    758 
    759 /**
    760  * Returns an array where all locales are canonicalized and duplicates removed.
    761  * Throws on locales that are not well formed BCP47 tags.
    762  */
    763 function initializeLocaleList(locales) {
    764   var seen = [];
    765   if (locales === undefined) {
    766     // Constructor is called without arguments.
    767     seen = [];
    768   } else {
    769     // We allow single string localeID.
    770     if (typeof locales === 'string') {
    771       seen.push(canonicalizeLanguageTag(locales));
    772       return freezeArray(seen);
    773     }
    774 
    775     var o = toObject(locales);
    776     // Converts it to UInt32 (>>> is shr on 32bit integers).
    777     var len = o.length >>> 0;
    778 
    779     for (var k = 0; k < len; k++) {
    780       if (k in o) {
    781         var value = o[k];
    782 
    783         var tag = canonicalizeLanguageTag(value);
    784 
    785         if (seen.indexOf(tag) === -1) {
    786           seen.push(tag);
    787         }
    788       }
    789     }
    790   }
    791 
    792   return freezeArray(seen);
    793 }
    794 
    795 
    796 /**
    797  * Validates the language tag. Section 2.2.9 of the bcp47 spec
    798  * defines a valid tag.
    799  *
    800  * ICU is too permissible and lets invalid tags, like
    801  * hant-cmn-cn, through.
    802  *
    803  * Returns false if the language tag is invalid.
    804  */
    805 function isValidLanguageTag(locale) {
    806   // Check if it's well-formed, including grandfadered tags.
    807   if (GetLanguageTagRE().test(locale) === false) {
    808     return false;
    809   }
    810 
    811   // Just return if it's a x- form. It's all private.
    812   if (locale.indexOf('x-') === 0) {
    813     return true;
    814   }
    815 
    816   // Check if there are any duplicate variants or singletons (extensions).
    817 
    818   // Remove private use section.
    819   locale = locale.split(/-x-/)[0];
    820 
    821   // Skip language since it can match variant regex, so we start from 1.
    822   // We are matching i-klingon here, but that's ok, since i-klingon-klingon
    823   // is not valid and would fail LANGUAGE_TAG_RE test.
    824   var variants = [];
    825   var extensions = [];
    826   var parts = locale.split(/-/);
    827   for (var i = 1; i < parts.length; i++) {
    828     var value = parts[i];
    829     if (GetLanguageVariantRE().test(value) === true && extensions.length === 0) {
    830       if (variants.indexOf(value) === -1) {
    831         variants.push(value);
    832       } else {
    833         return false;
    834       }
    835     }
    836 
    837     if (GetLanguageSingletonRE().test(value) === true) {
    838       if (extensions.indexOf(value) === -1) {
    839         extensions.push(value);
    840       } else {
    841         return false;
    842       }
    843     }
    844   }
    845 
    846   return true;
    847  }
    848 
    849 
    850 /**
    851  * Builds a regular expresion that validates the language tag
    852  * against bcp47 spec.
    853  * Uses http://tools.ietf.org/html/bcp47, section 2.1, ABNF.
    854  * Runs on load and initializes the global REs.
    855  */
    856 function BuildLanguageTagREs() {
    857   var alpha = '[a-zA-Z]';
    858   var digit = '[0-9]';
    859   var alphanum = '(' + alpha + '|' + digit + ')';
    860   var regular = '(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|' +
    861                 'zh-min|zh-min-nan|zh-xiang)';
    862   var irregular = '(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|' +
    863                   'i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|' +
    864                   'i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)';
    865   var grandfathered = '(' + irregular + '|' + regular + ')';
    866   var privateUse = '(x(-' + alphanum + '{1,8})+)';
    867 
    868   var singleton = '(' + digit + '|[A-WY-Za-wy-z])';
    869   LANGUAGE_SINGLETON_RE = new $RegExp('^' + singleton + '$', 'i');
    870 
    871   var extension = '(' + singleton + '(-' + alphanum + '{2,8})+)';
    872 
    873   var variant = '(' + alphanum + '{5,8}|(' + digit + alphanum + '{3}))';
    874   LANGUAGE_VARIANT_RE = new $RegExp('^' + variant + '$', 'i');
    875 
    876   var region = '(' + alpha + '{2}|' + digit + '{3})';
    877   var script = '(' + alpha + '{4})';
    878   var extLang = '(' + alpha + '{3}(-' + alpha + '{3}){0,2})';
    879   var language = '(' + alpha + '{2,3}(-' + extLang + ')?|' + alpha + '{4}|' +
    880                  alpha + '{5,8})';
    881   var langTag = language + '(-' + script + ')?(-' + region + ')?(-' +
    882                 variant + ')*(-' + extension + ')*(-' + privateUse + ')?';
    883 
    884   var languageTag =
    885       '^(' + langTag + '|' + privateUse + '|' + grandfathered + ')$';
    886   LANGUAGE_TAG_RE = new $RegExp(languageTag, 'i');
    887 }
    888 
    889 /**
    890  * Initializes the given object so it's a valid Collator instance.
    891  * Useful for subclassing.
    892  */
    893 function initializeCollator(collator, locales, options) {
    894   if (collator.hasOwnProperty('__initializedIntlObject')) {
    895     throw new $TypeError('Trying to re-initialize Collator object.');
    896   }
    897 
    898   if (options === undefined) {
    899     options = {};
    900   }
    901 
    902   var getOption = getGetOption(options, 'collator');
    903 
    904   var internalOptions = {};
    905 
    906   defineWEProperty(internalOptions, 'usage', getOption(
    907     'usage', 'string', ['sort', 'search'], 'sort'));
    908 
    909   var sensitivity = getOption('sensitivity', 'string',
    910                               ['base', 'accent', 'case', 'variant']);
    911   if (sensitivity === undefined && internalOptions.usage === 'sort') {
    912     sensitivity = 'variant';
    913   }
    914   defineWEProperty(internalOptions, 'sensitivity', sensitivity);
    915 
    916   defineWEProperty(internalOptions, 'ignorePunctuation', getOption(
    917     'ignorePunctuation', 'boolean', undefined, false));
    918 
    919   var locale = resolveLocale('collator', locales, options);
    920 
    921   // ICU can't take kb, kc... parameters through localeID, so we need to pass
    922   // them as options.
    923   // One exception is -co- which has to be part of the extension, but only for
    924   // usage: sort, and its value can't be 'standard' or 'search'.
    925   var extensionMap = parseExtension(locale.extension);
    926   setOptions(
    927       options, extensionMap, COLLATOR_KEY_MAP, getOption, internalOptions);
    928 
    929   var collation = 'default';
    930   var extension = '';
    931   if (extensionMap.hasOwnProperty('co') && internalOptions.usage === 'sort') {
    932     if (ALLOWED_CO_VALUES.indexOf(extensionMap.co) !== -1) {
    933       extension = '-u-co-' + extensionMap.co;
    934       // ICU can't tell us what the collation is, so save user's input.
    935       collation = extensionMap.co;
    936     }
    937   } else if (internalOptions.usage === 'search') {
    938     extension = '-u-co-search';
    939   }
    940   defineWEProperty(internalOptions, 'collation', collation);
    941 
    942   var requestedLocale = locale.locale + extension;
    943 
    944   // We define all properties C++ code may produce, to prevent security
    945   // problems. If malicious user decides to redefine Object.prototype.locale
    946   // we can't just use plain x.locale = 'us' or in C++ Set("locale", "us").
    947   // Object.defineProperties will either succeed defining or throw an error.
    948   var resolved = $Object.defineProperties({}, {
    949     caseFirst: {writable: true},
    950     collation: {value: internalOptions.collation, writable: true},
    951     ignorePunctuation: {writable: true},
    952     locale: {writable: true},
    953     numeric: {writable: true},
    954     requestedLocale: {value: requestedLocale, writable: true},
    955     sensitivity: {writable: true},
    956     strength: {writable: true},
    957     usage: {value: internalOptions.usage, writable: true}
    958   });
    959 
    960   var internalCollator = %CreateCollator(requestedLocale,
    961                                          internalOptions,
    962                                          resolved);
    963 
    964   // Writable, configurable and enumerable are set to false by default.
    965   $Object.defineProperty(collator, 'collator', {value: internalCollator});
    966   $Object.defineProperty(collator, '__initializedIntlObject',
    967                          {value: 'collator'});
    968   $Object.defineProperty(collator, 'resolved', {value: resolved});
    969 
    970   return collator;
    971 }
    972 
    973 
    974 /**
    975  * Constructs Intl.Collator object given optional locales and options
    976  * parameters.
    977  *
    978  * @constructor
    979  */
    980 %SetProperty(Intl, 'Collator', function() {
    981     var locales = %_Arguments(0);
    982     var options = %_Arguments(1);
    983 
    984     if (!this || this === Intl) {
    985       // Constructor is called as a function.
    986       return new Intl.Collator(locales, options);
    987     }
    988 
    989     return initializeCollator(toObject(this), locales, options);
    990   },
    991   DONT_ENUM
    992 );
    993 
    994 
    995 /**
    996  * Collator resolvedOptions method.
    997  */
    998 %SetProperty(Intl.Collator.prototype, 'resolvedOptions', function() {
    999     if (%_IsConstructCall()) {
   1000       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1001     }
   1002 
   1003     if (!this || typeof this !== 'object' ||
   1004         this.__initializedIntlObject !== 'collator') {
   1005       throw new $TypeError('resolvedOptions method called on a non-object ' +
   1006                            'or on a object that is not Intl.Collator.');
   1007     }
   1008 
   1009     var coll = this;
   1010     var locale = getOptimalLanguageTag(coll.resolved.requestedLocale,
   1011                                        coll.resolved.locale);
   1012 
   1013     return {
   1014       locale: locale,
   1015       usage: coll.resolved.usage,
   1016       sensitivity: coll.resolved.sensitivity,
   1017       ignorePunctuation: coll.resolved.ignorePunctuation,
   1018       numeric: coll.resolved.numeric,
   1019       caseFirst: coll.resolved.caseFirst,
   1020       collation: coll.resolved.collation
   1021     };
   1022   },
   1023   DONT_ENUM
   1024 );
   1025 %FunctionSetName(Intl.Collator.prototype.resolvedOptions, 'resolvedOptions');
   1026 %FunctionRemovePrototype(Intl.Collator.prototype.resolvedOptions);
   1027 %SetNativeFlag(Intl.Collator.prototype.resolvedOptions);
   1028 
   1029 
   1030 /**
   1031  * Returns the subset of the given locale list for which this locale list
   1032  * has a matching (possibly fallback) locale. Locales appear in the same
   1033  * order in the returned list as in the input list.
   1034  * Options are optional parameter.
   1035  */
   1036 %SetProperty(Intl.Collator, 'supportedLocalesOf', function(locales) {
   1037     if (%_IsConstructCall()) {
   1038       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1039     }
   1040 
   1041     return supportedLocalesOf('collator', locales, %_Arguments(1));
   1042   },
   1043   DONT_ENUM
   1044 );
   1045 %FunctionSetName(Intl.Collator.supportedLocalesOf, 'supportedLocalesOf');
   1046 %FunctionRemovePrototype(Intl.Collator.supportedLocalesOf);
   1047 %SetNativeFlag(Intl.Collator.supportedLocalesOf);
   1048 
   1049 
   1050 /**
   1051  * When the compare method is called with two arguments x and y, it returns a
   1052  * Number other than NaN that represents the result of a locale-sensitive
   1053  * String comparison of x with y.
   1054  * The result is intended to order String values in the sort order specified
   1055  * by the effective locale and collation options computed during construction
   1056  * of this Collator object, and will be negative, zero, or positive, depending
   1057  * on whether x comes before y in the sort order, the Strings are equal under
   1058  * the sort order, or x comes after y in the sort order, respectively.
   1059  */
   1060 function compare(collator, x, y) {
   1061   return %InternalCompare(collator.collator, $String(x), $String(y));
   1062 };
   1063 
   1064 
   1065 addBoundMethod(Intl.Collator, 'compare', compare, 2);
   1066 
   1067 /**
   1068  * Verifies that the input is a well-formed ISO 4217 currency code.
   1069  * Don't uppercase to test. It could convert invalid code into a valid one.
   1070  * For example \u00DFP (Eszett+P) becomes SSP.
   1071  */
   1072 function isWellFormedCurrencyCode(currency) {
   1073   return typeof currency == "string" &&
   1074       currency.length == 3 &&
   1075       currency.match(/[^A-Za-z]/) == null;
   1076 }
   1077 
   1078 
   1079 /**
   1080  * Returns the valid digit count for a property, or throws RangeError on
   1081  * a value out of the range.
   1082  */
   1083 function getNumberOption(options, property, min, max, fallback) {
   1084   var value = options[property];
   1085   if (value !== undefined) {
   1086     value = $Number(value);
   1087     if ($isNaN(value) || value < min || value > max) {
   1088       throw new $RangeError(property + ' value is out of range.');
   1089     }
   1090     return $floor(value);
   1091   }
   1092 
   1093   return fallback;
   1094 }
   1095 
   1096 
   1097 /**
   1098  * Initializes the given object so it's a valid NumberFormat instance.
   1099  * Useful for subclassing.
   1100  */
   1101 function initializeNumberFormat(numberFormat, locales, options) {
   1102   if (numberFormat.hasOwnProperty('__initializedIntlObject')) {
   1103     throw new $TypeError('Trying to re-initialize NumberFormat object.');
   1104   }
   1105 
   1106   if (options === undefined) {
   1107     options = {};
   1108   }
   1109 
   1110   var getOption = getGetOption(options, 'numberformat');
   1111 
   1112   var locale = resolveLocale('numberformat', locales, options);
   1113 
   1114   var internalOptions = {};
   1115   defineWEProperty(internalOptions, 'style', getOption(
   1116     'style', 'string', ['decimal', 'percent', 'currency'], 'decimal'));
   1117 
   1118   var currency = getOption('currency', 'string');
   1119   if (currency !== undefined && !isWellFormedCurrencyCode(currency)) {
   1120     throw new $RangeError('Invalid currency code: ' + currency);
   1121   }
   1122 
   1123   if (internalOptions.style === 'currency' && currency === undefined) {
   1124     throw new $TypeError('Currency code is required with currency style.');
   1125   }
   1126 
   1127   var currencyDisplay = getOption(
   1128       'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol');
   1129   if (internalOptions.style === 'currency') {
   1130     defineWEProperty(internalOptions, 'currency', currency.toUpperCase());
   1131     defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay);
   1132   }
   1133 
   1134   // Digit ranges.
   1135   var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1);
   1136   defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid);
   1137 
   1138   var mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0);
   1139   defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd);
   1140 
   1141   var mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, 3);
   1142   defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd);
   1143 
   1144   var mnsd = options['minimumSignificantDigits'];
   1145   var mxsd = options['maximumSignificantDigits'];
   1146   if (mnsd !== undefined || mxsd !== undefined) {
   1147     mnsd = getNumberOption(options, 'minimumSignificantDigits', 1, 21, 0);
   1148     defineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd);
   1149 
   1150     mxsd = getNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21);
   1151     defineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd);
   1152   }
   1153 
   1154   // Grouping.
   1155   defineWEProperty(internalOptions, 'useGrouping', getOption(
   1156     'useGrouping', 'boolean', undefined, true));
   1157 
   1158   // ICU prefers options to be passed using -u- extension key/values for
   1159   // number format, so we need to build that.
   1160   var extensionMap = parseExtension(locale.extension);
   1161   var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP,
   1162                              getOption, internalOptions);
   1163 
   1164   var requestedLocale = locale.locale + extension;
   1165   var resolved = $Object.defineProperties({}, {
   1166     currency: {writable: true},
   1167     currencyDisplay: {writable: true},
   1168     locale: {writable: true},
   1169     maximumFractionDigits: {writable: true},
   1170     minimumFractionDigits: {writable: true},
   1171     minimumIntegerDigits: {writable: true},
   1172     numberingSystem: {writable: true},
   1173     requestedLocale: {value: requestedLocale, writable: true},
   1174     style: {value: internalOptions.style, writable: true},
   1175     useGrouping: {writable: true}
   1176   });
   1177   if (internalOptions.hasOwnProperty('minimumSignificantDigits')) {
   1178     defineWEProperty(resolved, 'minimumSignificantDigits', undefined);
   1179   }
   1180   if (internalOptions.hasOwnProperty('maximumSignificantDigits')) {
   1181     defineWEProperty(resolved, 'maximumSignificantDigits', undefined);
   1182   }
   1183   var formatter = %CreateNumberFormat(requestedLocale,
   1184                                       internalOptions,
   1185                                       resolved);
   1186 
   1187   // We can't get information about number or currency style from ICU, so we
   1188   // assume user request was fulfilled.
   1189   if (internalOptions.style === 'currency') {
   1190     $Object.defineProperty(resolved, 'currencyDisplay', {value: currencyDisplay,
   1191                                                          writable: true});
   1192   }
   1193 
   1194   $Object.defineProperty(numberFormat, 'formatter', {value: formatter});
   1195   $Object.defineProperty(numberFormat, 'resolved', {value: resolved});
   1196   $Object.defineProperty(numberFormat, '__initializedIntlObject',
   1197                          {value: 'numberformat'});
   1198 
   1199   return numberFormat;
   1200 }
   1201 
   1202 
   1203 /**
   1204  * Constructs Intl.NumberFormat object given optional locales and options
   1205  * parameters.
   1206  *
   1207  * @constructor
   1208  */
   1209 %SetProperty(Intl, 'NumberFormat', function() {
   1210     var locales = %_Arguments(0);
   1211     var options = %_Arguments(1);
   1212 
   1213     if (!this || this === Intl) {
   1214       // Constructor is called as a function.
   1215       return new Intl.NumberFormat(locales, options);
   1216     }
   1217 
   1218     return initializeNumberFormat(toObject(this), locales, options);
   1219   },
   1220   DONT_ENUM
   1221 );
   1222 
   1223 
   1224 /**
   1225  * NumberFormat resolvedOptions method.
   1226  */
   1227 %SetProperty(Intl.NumberFormat.prototype, 'resolvedOptions', function() {
   1228     if (%_IsConstructCall()) {
   1229       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1230     }
   1231 
   1232     if (!this || typeof this !== 'object' ||
   1233         this.__initializedIntlObject !== 'numberformat') {
   1234       throw new $TypeError('resolvedOptions method called on a non-object' +
   1235           ' or on a object that is not Intl.NumberFormat.');
   1236     }
   1237 
   1238     var format = this;
   1239     var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
   1240                                        format.resolved.locale);
   1241 
   1242     var result = {
   1243       locale: locale,
   1244       numberingSystem: format.resolved.numberingSystem,
   1245       style: format.resolved.style,
   1246       useGrouping: format.resolved.useGrouping,
   1247       minimumIntegerDigits: format.resolved.minimumIntegerDigits,
   1248       minimumFractionDigits: format.resolved.minimumFractionDigits,
   1249       maximumFractionDigits: format.resolved.maximumFractionDigits,
   1250     };
   1251 
   1252     if (result.style === 'currency') {
   1253       defineWECProperty(result, 'currency', format.resolved.currency);
   1254       defineWECProperty(result, 'currencyDisplay',
   1255                         format.resolved.currencyDisplay);
   1256     }
   1257 
   1258     if (format.resolved.hasOwnProperty('minimumSignificantDigits')) {
   1259       defineWECProperty(result, 'minimumSignificantDigits',
   1260                         format.resolved.minimumSignificantDigits);
   1261     }
   1262 
   1263     if (format.resolved.hasOwnProperty('maximumSignificantDigits')) {
   1264       defineWECProperty(result, 'maximumSignificantDigits',
   1265                         format.resolved.maximumSignificantDigits);
   1266     }
   1267 
   1268     return result;
   1269   },
   1270   DONT_ENUM
   1271 );
   1272 %FunctionSetName(Intl.NumberFormat.prototype.resolvedOptions,
   1273                  'resolvedOptions');
   1274 %FunctionRemovePrototype(Intl.NumberFormat.prototype.resolvedOptions);
   1275 %SetNativeFlag(Intl.NumberFormat.prototype.resolvedOptions);
   1276 
   1277 
   1278 /**
   1279  * Returns the subset of the given locale list for which this locale list
   1280  * has a matching (possibly fallback) locale. Locales appear in the same
   1281  * order in the returned list as in the input list.
   1282  * Options are optional parameter.
   1283  */
   1284 %SetProperty(Intl.NumberFormat, 'supportedLocalesOf', function(locales) {
   1285     if (%_IsConstructCall()) {
   1286       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1287     }
   1288 
   1289     return supportedLocalesOf('numberformat', locales, %_Arguments(1));
   1290   },
   1291   DONT_ENUM
   1292 );
   1293 %FunctionSetName(Intl.NumberFormat.supportedLocalesOf, 'supportedLocalesOf');
   1294 %FunctionRemovePrototype(Intl.NumberFormat.supportedLocalesOf);
   1295 %SetNativeFlag(Intl.NumberFormat.supportedLocalesOf);
   1296 
   1297 
   1298 /**
   1299  * Returns a String value representing the result of calling ToNumber(value)
   1300  * according to the effective locale and the formatting options of this
   1301  * NumberFormat.
   1302  */
   1303 function formatNumber(formatter, value) {
   1304   // Spec treats -0 and +0 as 0.
   1305   var number = $Number(value) + 0;
   1306 
   1307   return %InternalNumberFormat(formatter.formatter, number);
   1308 }
   1309 
   1310 
   1311 /**
   1312  * Returns a Number that represents string value that was passed in.
   1313  */
   1314 function parseNumber(formatter, value) {
   1315   return %InternalNumberParse(formatter.formatter, $String(value));
   1316 }
   1317 
   1318 
   1319 addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1);
   1320 addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1);
   1321 
   1322 /**
   1323  * Returns a string that matches LDML representation of the options object.
   1324  */
   1325 function toLDMLString(options) {
   1326   var getOption = getGetOption(options, 'dateformat');
   1327 
   1328   var ldmlString = '';
   1329 
   1330   var option = getOption('weekday', 'string', ['narrow', 'short', 'long']);
   1331   ldmlString += appendToLDMLString(
   1332       option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'});
   1333 
   1334   option = getOption('era', 'string', ['narrow', 'short', 'long']);
   1335   ldmlString += appendToLDMLString(
   1336       option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'});
   1337 
   1338   option = getOption('year', 'string', ['2-digit', 'numeric']);
   1339   ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'});
   1340 
   1341   option = getOption('month', 'string',
   1342                      ['2-digit', 'numeric', 'narrow', 'short', 'long']);
   1343   ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M',
   1344           'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'});
   1345 
   1346   option = getOption('day', 'string', ['2-digit', 'numeric']);
   1347   ldmlString += appendToLDMLString(
   1348       option, {'2-digit': 'dd', 'numeric': 'd'});
   1349 
   1350   var hr12 = getOption('hour12', 'boolean');
   1351   option = getOption('hour', 'string', ['2-digit', 'numeric']);
   1352   if (hr12 === undefined) {
   1353     ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'});
   1354   } else if (hr12 === true) {
   1355     ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'});
   1356   } else {
   1357     ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'});
   1358   }
   1359 
   1360   option = getOption('minute', 'string', ['2-digit', 'numeric']);
   1361   ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'});
   1362 
   1363   option = getOption('second', 'string', ['2-digit', 'numeric']);
   1364   ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'});
   1365 
   1366   option = getOption('timeZoneName', 'string', ['short', 'long']);
   1367   ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'});
   1368 
   1369   return ldmlString;
   1370 }
   1371 
   1372 
   1373 /**
   1374  * Returns either LDML equivalent of the current option or empty string.
   1375  */
   1376 function appendToLDMLString(option, pairs) {
   1377   if (option !== undefined) {
   1378     return pairs[option];
   1379   } else {
   1380     return '';
   1381   }
   1382 }
   1383 
   1384 
   1385 /**
   1386  * Returns object that matches LDML representation of the date.
   1387  */
   1388 function fromLDMLString(ldmlString) {
   1389   // First remove '' quoted text, so we lose 'Uhr' strings.
   1390   ldmlString = ldmlString.replace(GetQuotedStringRE(), '');
   1391 
   1392   var options = {};
   1393   var match = ldmlString.match(/E{3,5}/g);
   1394   options = appendToDateTimeObject(
   1395       options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'});
   1396 
   1397   match = ldmlString.match(/G{3,5}/g);
   1398   options = appendToDateTimeObject(
   1399       options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'});
   1400 
   1401   match = ldmlString.match(/y{1,2}/g);
   1402   options = appendToDateTimeObject(
   1403       options, 'year', match, {y: 'numeric', yy: '2-digit'});
   1404 
   1405   match = ldmlString.match(/M{1,5}/g);
   1406   options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit',
   1407       M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'});
   1408 
   1409   // Sometimes we get L instead of M for month - standalone name.
   1410   match = ldmlString.match(/L{1,5}/g);
   1411   options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit',
   1412       L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'});
   1413 
   1414   match = ldmlString.match(/d{1,2}/g);
   1415   options = appendToDateTimeObject(
   1416       options, 'day', match, {d: 'numeric', dd: '2-digit'});
   1417 
   1418   match = ldmlString.match(/h{1,2}/g);
   1419   if (match !== null) {
   1420     options['hour12'] = true;
   1421   }
   1422   options = appendToDateTimeObject(
   1423       options, 'hour', match, {h: 'numeric', hh: '2-digit'});
   1424 
   1425   match = ldmlString.match(/H{1,2}/g);
   1426   if (match !== null) {
   1427     options['hour12'] = false;
   1428   }
   1429   options = appendToDateTimeObject(
   1430       options, 'hour', match, {H: 'numeric', HH: '2-digit'});
   1431 
   1432   match = ldmlString.match(/m{1,2}/g);
   1433   options = appendToDateTimeObject(
   1434       options, 'minute', match, {m: 'numeric', mm: '2-digit'});
   1435 
   1436   match = ldmlString.match(/s{1,2}/g);
   1437   options = appendToDateTimeObject(
   1438       options, 'second', match, {s: 'numeric', ss: '2-digit'});
   1439 
   1440   match = ldmlString.match(/z|zzzz/g);
   1441   options = appendToDateTimeObject(
   1442       options, 'timeZoneName', match, {z: 'short', zzzz: 'long'});
   1443 
   1444   return options;
   1445 }
   1446 
   1447 
   1448 function appendToDateTimeObject(options, option, match, pairs) {
   1449   if (IS_NULL(match)) {
   1450     if (!options.hasOwnProperty(option)) {
   1451       defineWEProperty(options, option, undefined);
   1452     }
   1453     return options;
   1454   }
   1455 
   1456   var property = match[0];
   1457   defineWEProperty(options, option, pairs[property]);
   1458 
   1459   return options;
   1460 }
   1461 
   1462 
   1463 /**
   1464  * Returns options with at least default values in it.
   1465  */
   1466 function toDateTimeOptions(options, required, defaults) {
   1467   if (options === undefined) {
   1468     options = null;
   1469   } else {
   1470     options = toObject(options);
   1471   }
   1472 
   1473   options = $Object.apply(this, [options]);
   1474 
   1475   var needsDefault = true;
   1476   if ((required === 'date' || required === 'any') &&
   1477       (options.weekday !== undefined || options.year !== undefined ||
   1478        options.month !== undefined || options.day !== undefined)) {
   1479     needsDefault = false;
   1480   }
   1481 
   1482   if ((required === 'time' || required === 'any') &&
   1483       (options.hour !== undefined || options.minute !== undefined ||
   1484        options.second !== undefined)) {
   1485     needsDefault = false;
   1486   }
   1487 
   1488   if (needsDefault && (defaults === 'date' || defaults === 'all')) {
   1489     $Object.defineProperty(options, 'year', {value: 'numeric',
   1490                                              writable: true,
   1491                                              enumerable: true,
   1492                                              configurable: true});
   1493     $Object.defineProperty(options, 'month', {value: 'numeric',
   1494                                               writable: true,
   1495                                               enumerable: true,
   1496                                               configurable: true});
   1497     $Object.defineProperty(options, 'day', {value: 'numeric',
   1498                                             writable: true,
   1499                                             enumerable: true,
   1500                                             configurable: true});
   1501   }
   1502 
   1503   if (needsDefault && (defaults === 'time' || defaults === 'all')) {
   1504     $Object.defineProperty(options, 'hour', {value: 'numeric',
   1505                                              writable: true,
   1506                                              enumerable: true,
   1507                                              configurable: true});
   1508     $Object.defineProperty(options, 'minute', {value: 'numeric',
   1509                                                writable: true,
   1510                                                enumerable: true,
   1511                                                configurable: true});
   1512     $Object.defineProperty(options, 'second', {value: 'numeric',
   1513                                                writable: true,
   1514                                                enumerable: true,
   1515                                                configurable: true});
   1516   }
   1517 
   1518   return options;
   1519 }
   1520 
   1521 
   1522 /**
   1523  * Initializes the given object so it's a valid DateTimeFormat instance.
   1524  * Useful for subclassing.
   1525  */
   1526 function initializeDateTimeFormat(dateFormat, locales, options) {
   1527 
   1528   if (dateFormat.hasOwnProperty('__initializedIntlObject')) {
   1529     throw new $TypeError('Trying to re-initialize DateTimeFormat object.');
   1530   }
   1531 
   1532   if (options === undefined) {
   1533     options = {};
   1534   }
   1535 
   1536   var locale = resolveLocale('dateformat', locales, options);
   1537 
   1538   options = toDateTimeOptions(options, 'any', 'date');
   1539 
   1540   var getOption = getGetOption(options, 'dateformat');
   1541 
   1542   // We implement only best fit algorithm, but still need to check
   1543   // if the formatMatcher values are in range.
   1544   var matcher = getOption('formatMatcher', 'string',
   1545                           ['basic', 'best fit'], 'best fit');
   1546 
   1547   // Build LDML string for the skeleton that we pass to the formatter.
   1548   var ldmlString = toLDMLString(options);
   1549 
   1550   // Filter out supported extension keys so we know what to put in resolved
   1551   // section later on.
   1552   // We need to pass calendar and number system to the method.
   1553   var tz = canonicalizeTimeZoneID(options.timeZone);
   1554 
   1555   // ICU prefers options to be passed using -u- extension key/values, so
   1556   // we need to build that.
   1557   var internalOptions = {};
   1558   var extensionMap = parseExtension(locale.extension);
   1559   var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP,
   1560                              getOption, internalOptions);
   1561 
   1562   var requestedLocale = locale.locale + extension;
   1563   var resolved = $Object.defineProperties({}, {
   1564     calendar: {writable: true},
   1565     day: {writable: true},
   1566     era: {writable: true},
   1567     hour12: {writable: true},
   1568     hour: {writable: true},
   1569     locale: {writable: true},
   1570     minute: {writable: true},
   1571     month: {writable: true},
   1572     numberingSystem: {writable: true},
   1573     pattern: {writable: true},
   1574     requestedLocale: {value: requestedLocale, writable: true},
   1575     second: {writable: true},
   1576     timeZone: {writable: true},
   1577     timeZoneName: {writable: true},
   1578     tz: {value: tz, writable: true},
   1579     weekday: {writable: true},
   1580     year: {writable: true}
   1581   });
   1582 
   1583   var formatter = %CreateDateTimeFormat(
   1584     requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved);
   1585 
   1586   if (tz !== undefined && tz !== resolved.timeZone) {
   1587     throw new $RangeError('Unsupported time zone specified ' + tz);
   1588   }
   1589 
   1590   $Object.defineProperty(dateFormat, 'formatter', {value: formatter});
   1591   $Object.defineProperty(dateFormat, 'resolved', {value: resolved});
   1592   $Object.defineProperty(dateFormat, '__initializedIntlObject',
   1593                          {value: 'dateformat'});
   1594 
   1595   return dateFormat;
   1596 }
   1597 
   1598 
   1599 /**
   1600  * Constructs Intl.DateTimeFormat object given optional locales and options
   1601  * parameters.
   1602  *
   1603  * @constructor
   1604  */
   1605 %SetProperty(Intl, 'DateTimeFormat', function() {
   1606     var locales = %_Arguments(0);
   1607     var options = %_Arguments(1);
   1608 
   1609     if (!this || this === Intl) {
   1610       // Constructor is called as a function.
   1611       return new Intl.DateTimeFormat(locales, options);
   1612     }
   1613 
   1614     return initializeDateTimeFormat(toObject(this), locales, options);
   1615   },
   1616   DONT_ENUM
   1617 );
   1618 
   1619 
   1620 /**
   1621  * DateTimeFormat resolvedOptions method.
   1622  */
   1623 %SetProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() {
   1624     if (%_IsConstructCall()) {
   1625       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1626     }
   1627 
   1628     if (!this || typeof this !== 'object' ||
   1629         this.__initializedIntlObject !== 'dateformat') {
   1630       throw new $TypeError('resolvedOptions method called on a non-object or ' +
   1631           'on a object that is not Intl.DateTimeFormat.');
   1632     }
   1633 
   1634     var format = this;
   1635     var fromPattern = fromLDMLString(format.resolved.pattern);
   1636     var userCalendar = ICU_CALENDAR_MAP[format.resolved.calendar];
   1637     if (userCalendar === undefined) {
   1638       // Use ICU name if we don't have a match. It shouldn't happen, but
   1639       // it would be too strict to throw for this.
   1640       userCalendar = format.resolved.calendar;
   1641     }
   1642 
   1643     var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
   1644                                        format.resolved.locale);
   1645 
   1646     var result = {
   1647       locale: locale,
   1648       numberingSystem: format.resolved.numberingSystem,
   1649       calendar: userCalendar,
   1650       timeZone: format.resolved.timeZone
   1651     };
   1652 
   1653     addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName);
   1654     addWECPropertyIfDefined(result, 'era', fromPattern.era);
   1655     addWECPropertyIfDefined(result, 'year', fromPattern.year);
   1656     addWECPropertyIfDefined(result, 'month', fromPattern.month);
   1657     addWECPropertyIfDefined(result, 'day', fromPattern.day);
   1658     addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday);
   1659     addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12);
   1660     addWECPropertyIfDefined(result, 'hour', fromPattern.hour);
   1661     addWECPropertyIfDefined(result, 'minute', fromPattern.minute);
   1662     addWECPropertyIfDefined(result, 'second', fromPattern.second);
   1663 
   1664     return result;
   1665   },
   1666   DONT_ENUM
   1667 );
   1668 %FunctionSetName(Intl.DateTimeFormat.prototype.resolvedOptions,
   1669                  'resolvedOptions');
   1670 %FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions);
   1671 %SetNativeFlag(Intl.DateTimeFormat.prototype.resolvedOptions);
   1672 
   1673 
   1674 /**
   1675  * Returns the subset of the given locale list for which this locale list
   1676  * has a matching (possibly fallback) locale. Locales appear in the same
   1677  * order in the returned list as in the input list.
   1678  * Options are optional parameter.
   1679  */
   1680 %SetProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) {
   1681     if (%_IsConstructCall()) {
   1682       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1683     }
   1684 
   1685     return supportedLocalesOf('dateformat', locales, %_Arguments(1));
   1686   },
   1687   DONT_ENUM
   1688 );
   1689 %FunctionSetName(Intl.DateTimeFormat.supportedLocalesOf, 'supportedLocalesOf');
   1690 %FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf);
   1691 %SetNativeFlag(Intl.DateTimeFormat.supportedLocalesOf);
   1692 
   1693 
   1694 /**
   1695  * Returns a String value representing the result of calling ToNumber(date)
   1696  * according to the effective locale and the formatting options of this
   1697  * DateTimeFormat.
   1698  */
   1699 function formatDate(formatter, dateValue) {
   1700   var dateMs;
   1701   if (dateValue === undefined) {
   1702     dateMs = $Date.now();
   1703   } else {
   1704     dateMs = $Number(dateValue);
   1705   }
   1706 
   1707   if (!$isFinite(dateMs)) {
   1708     throw new $RangeError('Provided date is not in valid range.');
   1709   }
   1710 
   1711   return %InternalDateFormat(formatter.formatter, new $Date(dateMs));
   1712 }
   1713 
   1714 
   1715 /**
   1716  * Returns a Date object representing the result of calling ToString(value)
   1717  * according to the effective locale and the formatting options of this
   1718  * DateTimeFormat.
   1719  * Returns undefined if date string cannot be parsed.
   1720  */
   1721 function parseDate(formatter, value) {
   1722   return %InternalDateParse(formatter.formatter, $String(value));
   1723 }
   1724 
   1725 
   1726 // 0 because date is optional argument.
   1727 addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0);
   1728 addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1);
   1729 
   1730 
   1731 /**
   1732  * Returns canonical Area/Location name, or throws an exception if the zone
   1733  * name is invalid IANA name.
   1734  */
   1735 function canonicalizeTimeZoneID(tzID) {
   1736   // Skip undefined zones.
   1737   if (tzID === undefined) {
   1738     return tzID;
   1739   }
   1740 
   1741   // Special case handling (UTC, GMT).
   1742   var upperID = tzID.toUpperCase();
   1743   if (upperID === 'UTC' || upperID === 'GMT' ||
   1744       upperID === 'ETC/UTC' || upperID === 'ETC/GMT') {
   1745     return 'UTC';
   1746   }
   1747 
   1748   // We expect only _ and / beside ASCII letters.
   1749   // All inputs should conform to Area/Location from now on.
   1750   var match = GetTimezoneNameCheckRE().exec(tzID);
   1751   if (IS_NULL(match)) {
   1752     throw new $RangeError('Expected Area/Location for time zone, got ' + tzID);
   1753   }
   1754 
   1755   var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]);
   1756   var i = 3;
   1757   while (match[i] !== undefined && i < match.length) {
   1758     result = result + '_' + toTitleCaseWord(match[i]);
   1759     i++;
   1760   }
   1761 
   1762   return result;
   1763 }
   1764 
   1765 /**
   1766  * Initializes the given object so it's a valid BreakIterator instance.
   1767  * Useful for subclassing.
   1768  */
   1769 function initializeBreakIterator(iterator, locales, options) {
   1770   if (iterator.hasOwnProperty('__initializedIntlObject')) {
   1771     throw new $TypeError('Trying to re-initialize v8BreakIterator object.');
   1772   }
   1773 
   1774   if (options === undefined) {
   1775     options = {};
   1776   }
   1777 
   1778   var getOption = getGetOption(options, 'breakiterator');
   1779 
   1780   var internalOptions = {};
   1781 
   1782   defineWEProperty(internalOptions, 'type', getOption(
   1783     'type', 'string', ['character', 'word', 'sentence', 'line'], 'word'));
   1784 
   1785   var locale = resolveLocale('breakiterator', locales, options);
   1786   var resolved = $Object.defineProperties({}, {
   1787     requestedLocale: {value: locale.locale, writable: true},
   1788     type: {value: internalOptions.type, writable: true},
   1789     locale: {writable: true}
   1790   });
   1791 
   1792   var internalIterator = %CreateBreakIterator(locale.locale,
   1793                                               internalOptions,
   1794                                               resolved);
   1795 
   1796   $Object.defineProperty(iterator, 'iterator', {value: internalIterator});
   1797   $Object.defineProperty(iterator, 'resolved', {value: resolved});
   1798   $Object.defineProperty(iterator, '__initializedIntlObject',
   1799                          {value: 'breakiterator'});
   1800 
   1801   return iterator;
   1802 }
   1803 
   1804 
   1805 /**
   1806  * Constructs Intl.v8BreakIterator object given optional locales and options
   1807  * parameters.
   1808  *
   1809  * @constructor
   1810  */
   1811 %SetProperty(Intl, 'v8BreakIterator', function() {
   1812     var locales = %_Arguments(0);
   1813     var options = %_Arguments(1);
   1814 
   1815     if (!this || this === Intl) {
   1816       // Constructor is called as a function.
   1817       return new Intl.v8BreakIterator(locales, options);
   1818     }
   1819 
   1820     return initializeBreakIterator(toObject(this), locales, options);
   1821   },
   1822   DONT_ENUM
   1823 );
   1824 
   1825 
   1826 /**
   1827  * BreakIterator resolvedOptions method.
   1828  */
   1829 %SetProperty(Intl.v8BreakIterator.prototype, 'resolvedOptions', function() {
   1830     if (%_IsConstructCall()) {
   1831       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1832     }
   1833 
   1834     if (!this || typeof this !== 'object' ||
   1835         this.__initializedIntlObject !== 'breakiterator') {
   1836       throw new $TypeError('resolvedOptions method called on a non-object or ' +
   1837           'on a object that is not Intl.v8BreakIterator.');
   1838     }
   1839 
   1840     var segmenter = this;
   1841     var locale = getOptimalLanguageTag(segmenter.resolved.requestedLocale,
   1842                                        segmenter.resolved.locale);
   1843 
   1844     return {
   1845       locale: locale,
   1846       type: segmenter.resolved.type
   1847     };
   1848   },
   1849   DONT_ENUM
   1850 );
   1851 %FunctionSetName(Intl.v8BreakIterator.prototype.resolvedOptions,
   1852                  'resolvedOptions');
   1853 %FunctionRemovePrototype(Intl.v8BreakIterator.prototype.resolvedOptions);
   1854 %SetNativeFlag(Intl.v8BreakIterator.prototype.resolvedOptions);
   1855 
   1856 
   1857 /**
   1858  * Returns the subset of the given locale list for which this locale list
   1859  * has a matching (possibly fallback) locale. Locales appear in the same
   1860  * order in the returned list as in the input list.
   1861  * Options are optional parameter.
   1862  */
   1863 %SetProperty(Intl.v8BreakIterator, 'supportedLocalesOf', function(locales) {
   1864     if (%_IsConstructCall()) {
   1865       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1866     }
   1867 
   1868     return supportedLocalesOf('breakiterator', locales, %_Arguments(1));
   1869   },
   1870   DONT_ENUM
   1871 );
   1872 %FunctionSetName(Intl.v8BreakIterator.supportedLocalesOf, 'supportedLocalesOf');
   1873 %FunctionRemovePrototype(Intl.v8BreakIterator.supportedLocalesOf);
   1874 %SetNativeFlag(Intl.v8BreakIterator.supportedLocalesOf);
   1875 
   1876 
   1877 /**
   1878  * Adopts text to segment using the iterator. Old text, if present,
   1879  * gets discarded.
   1880  */
   1881 function adoptText(iterator, text) {
   1882   %BreakIteratorAdoptText(iterator.iterator, $String(text));
   1883 }
   1884 
   1885 
   1886 /**
   1887  * Returns index of the first break in the string and moves current pointer.
   1888  */
   1889 function first(iterator) {
   1890   return %BreakIteratorFirst(iterator.iterator);
   1891 }
   1892 
   1893 
   1894 /**
   1895  * Returns the index of the next break and moves the pointer.
   1896  */
   1897 function next(iterator) {
   1898   return %BreakIteratorNext(iterator.iterator);
   1899 }
   1900 
   1901 
   1902 /**
   1903  * Returns index of the current break.
   1904  */
   1905 function current(iterator) {
   1906   return %BreakIteratorCurrent(iterator.iterator);
   1907 }
   1908 
   1909 
   1910 /**
   1911  * Returns type of the current break.
   1912  */
   1913 function breakType(iterator) {
   1914   return %BreakIteratorBreakType(iterator.iterator);
   1915 }
   1916 
   1917 
   1918 addBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1);
   1919 addBoundMethod(Intl.v8BreakIterator, 'first', first, 0);
   1920 addBoundMethod(Intl.v8BreakIterator, 'next', next, 0);
   1921 addBoundMethod(Intl.v8BreakIterator, 'current', current, 0);
   1922 addBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0);
   1923 
   1924 // Save references to Intl objects and methods we use, for added security.
   1925 var savedObjects = {
   1926   'collator': Intl.Collator,
   1927   'numberformat': Intl.NumberFormat,
   1928   'dateformatall': Intl.DateTimeFormat,
   1929   'dateformatdate': Intl.DateTimeFormat,
   1930   'dateformattime': Intl.DateTimeFormat
   1931 };
   1932 
   1933 
   1934 // Default (created with undefined locales and options parameters) collator,
   1935 // number and date format instances. They'll be created as needed.
   1936 var defaultObjects = {
   1937   'collator': undefined,
   1938   'numberformat': undefined,
   1939   'dateformatall': undefined,
   1940   'dateformatdate': undefined,
   1941   'dateformattime': undefined,
   1942 };
   1943 
   1944 
   1945 /**
   1946  * Returns cached or newly created instance of a given service.
   1947  * We cache only default instances (where no locales or options are provided).
   1948  */
   1949 function cachedOrNewService(service, locales, options, defaults) {
   1950   var useOptions = (defaults === undefined) ? options : defaults;
   1951   if (locales === undefined && options === undefined) {
   1952     if (defaultObjects[service] === undefined) {
   1953       defaultObjects[service] = new savedObjects[service](locales, useOptions);
   1954     }
   1955     return defaultObjects[service];
   1956   }
   1957   return new savedObjects[service](locales, useOptions);
   1958 }
   1959 
   1960 
   1961 /**
   1962  * Compares this and that, and returns less than 0, 0 or greater than 0 value.
   1963  * Overrides the built-in method.
   1964  */
   1965 $Object.defineProperty($String.prototype, 'localeCompare', {
   1966   value: function(that) {
   1967     if (%_IsConstructCall()) {
   1968       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1969     }
   1970 
   1971     if (IS_NULL_OR_UNDEFINED(this)) {
   1972       throw new $TypeError('Method invoked on undefined or null value.');
   1973     }
   1974 
   1975     var locales = %_Arguments(1);
   1976     var options = %_Arguments(2);
   1977     var collator = cachedOrNewService('collator', locales, options);
   1978     return compare(collator, this, that);
   1979   },
   1980   writable: true,
   1981   configurable: true,
   1982   enumerable: false
   1983 });
   1984 %FunctionSetName($String.prototype.localeCompare, 'localeCompare');
   1985 %FunctionRemovePrototype($String.prototype.localeCompare);
   1986 %SetNativeFlag($String.prototype.localeCompare);
   1987 
   1988 
   1989 /**
   1990  * Formats a Number object (this) using locale and options values.
   1991  * If locale or options are omitted, defaults are used.
   1992  */
   1993 $Object.defineProperty($Number.prototype, 'toLocaleString', {
   1994   value: function() {
   1995     if (%_IsConstructCall()) {
   1996       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   1997     }
   1998 
   1999     if (!(this instanceof $Number) && typeof(this) !== 'number') {
   2000       throw new $TypeError('Method invoked on an object that is not Number.');
   2001     }
   2002 
   2003     var locales = %_Arguments(0);
   2004     var options = %_Arguments(1);
   2005     var numberFormat = cachedOrNewService('numberformat', locales, options);
   2006     return formatNumber(numberFormat, this);
   2007   },
   2008   writable: true,
   2009   configurable: true,
   2010   enumerable: false
   2011 });
   2012 %FunctionSetName($Number.prototype.toLocaleString, 'toLocaleString');
   2013 %FunctionRemovePrototype($Number.prototype.toLocaleString);
   2014 %SetNativeFlag($Number.prototype.toLocaleString);
   2015 
   2016 
   2017 /**
   2018  * Returns actual formatted date or fails if date parameter is invalid.
   2019  */
   2020 function toLocaleDateTime(date, locales, options, required, defaults, service) {
   2021   if (!(date instanceof $Date)) {
   2022     throw new $TypeError('Method invoked on an object that is not Date.');
   2023   }
   2024 
   2025   if ($isNaN(date)) {
   2026     return 'Invalid Date';
   2027   }
   2028 
   2029   var internalOptions = toDateTimeOptions(options, required, defaults);
   2030 
   2031   var dateFormat =
   2032       cachedOrNewService(service, locales, options, internalOptions);
   2033 
   2034   return formatDate(dateFormat, date);
   2035 }
   2036 
   2037 
   2038 /**
   2039  * Formats a Date object (this) using locale and options values.
   2040  * If locale or options are omitted, defaults are used - both date and time are
   2041  * present in the output.
   2042  */
   2043 $Object.defineProperty($Date.prototype, 'toLocaleString', {
   2044   value: function() {
   2045     if (%_IsConstructCall()) {
   2046       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   2047     }
   2048 
   2049     var locales = %_Arguments(0);
   2050     var options = %_Arguments(1);
   2051     return toLocaleDateTime(
   2052         this, locales, options, 'any', 'all', 'dateformatall');
   2053   },
   2054   writable: true,
   2055   configurable: true,
   2056   enumerable: false
   2057 });
   2058 %FunctionSetName($Date.prototype.toLocaleString, 'toLocaleString');
   2059 %FunctionRemovePrototype($Date.prototype.toLocaleString);
   2060 %SetNativeFlag($Date.prototype.toLocaleString);
   2061 
   2062 
   2063 /**
   2064  * Formats a Date object (this) using locale and options values.
   2065  * If locale or options are omitted, defaults are used - only date is present
   2066  * in the output.
   2067  */
   2068 $Object.defineProperty($Date.prototype, 'toLocaleDateString', {
   2069   value: function() {
   2070     if (%_IsConstructCall()) {
   2071       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   2072     }
   2073 
   2074     var locales = %_Arguments(0);
   2075     var options = %_Arguments(1);
   2076     return toLocaleDateTime(
   2077         this, locales, options, 'date', 'date', 'dateformatdate');
   2078   },
   2079   writable: true,
   2080   configurable: true,
   2081   enumerable: false
   2082 });
   2083 %FunctionSetName($Date.prototype.toLocaleDateString, 'toLocaleDateString');
   2084 %FunctionRemovePrototype($Date.prototype.toLocaleDateString);
   2085 %SetNativeFlag($Date.prototype.toLocaleDateString);
   2086 
   2087 
   2088 /**
   2089  * Formats a Date object (this) using locale and options values.
   2090  * If locale or options are omitted, defaults are used - only time is present
   2091  * in the output.
   2092  */
   2093 $Object.defineProperty($Date.prototype, 'toLocaleTimeString', {
   2094   value: function() {
   2095     if (%_IsConstructCall()) {
   2096       throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
   2097     }
   2098 
   2099     var locales = %_Arguments(0);
   2100     var options = %_Arguments(1);
   2101     return toLocaleDateTime(
   2102         this, locales, options, 'time', 'time', 'dateformattime');
   2103   },
   2104   writable: true,
   2105   configurable: true,
   2106   enumerable: false
   2107 });
   2108 %FunctionSetName($Date.prototype.toLocaleTimeString, 'toLocaleTimeString');
   2109 %FunctionRemovePrototype($Date.prototype.toLocaleTimeString);
   2110 %SetNativeFlag($Date.prototype.toLocaleTimeString);
   2111 
   2112 return Intl;
   2113 }())});
   2114