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