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