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