The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.

Please select the desired protocol below to get the URL.

This URL has Read-Only access.

Statistics
| Branch: | Revision:

main_repo / deps / v8 / src / i18n.js @ f230a1cf

History | View | Annotate | Download (65.9 KB)

1
// Copyright 2013 the V8 project authors. All rights reserved.
2
// Redistribution and use in source and binary forms, with or without
3
// modification, are permitted provided that the following conditions are
4
// met:
5
//
6
//     * Redistributions of source code must retain the above copyright
7
//       notice, this list of conditions and the following disclaimer.
8
//     * Redistributions in binary form must reproduce the above
9
//       copyright notice, this list of conditions and the following
10
//       disclaimer in the documentation and/or other materials provided
11
//       with the distribution.
12
//     * Neither the name of Google Inc. nor the names of its
13
//       contributors may be used to endorse or promote products derived
14
//       from this software without specific prior written permission.
15
//
16
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
// limitations under the License.
28

    
29
// ECMAScript 402 API implementation.
30

    
31
/**
32
 * Intl object is a single object that has some named properties,
33
 * all of which are constructors.
34
 */
35
$Object.defineProperty(global, "Intl", { enumerable: false, value: (function() {
36

    
37
'use strict';
38

    
39
var Intl = {};
40

    
41
var undefined = global.undefined;
42

    
43
var AVAILABLE_SERVICES = ['collator',
44
                          'numberformat',
45
                          'dateformat',
46
                          'breakiterator'];
47

    
48
/**
49
 * Caches available locales for each service.
50
 */
51
var AVAILABLE_LOCALES = {
52
  'collator': undefined,
53
  'numberformat': undefined,
54
  'dateformat': undefined,
55
  'breakiterator': undefined
56
};
57

    
58
/**
59
 * Caches default ICU locale.
60
 */
61
var DEFAULT_ICU_LOCALE = undefined;
62

    
63
/**
64
 * Unicode extension regular expression.
65
 */
66
var UNICODE_EXTENSION_RE = undefined;
67

    
68
function GetUnicodeExtensionRE() {
69
  if (UNICODE_EXTENSION_RE === undefined) {
70
    UNICODE_EXTENSION_RE = new $RegExp('-u(-[a-z0-9]{2,8})+', 'g');
71
  }
72
  return UNICODE_EXTENSION_RE;
73
}
74

    
75
/**
76
 * Matches any Unicode extension.
77
 */
78
var ANY_EXTENSION_RE = undefined;
79

    
80
function GetAnyExtensionRE() {
81
  if (ANY_EXTENSION_RE === undefined) {
82
    ANY_EXTENSION_RE = new $RegExp('-[a-z0-9]{1}-.*', 'g');
83
  }
84
  return ANY_EXTENSION_RE;
85
}
86

    
87
/**
88
 * Replace quoted text (single quote, anything but the quote and quote again).
89
 */
90
var QUOTED_STRING_RE = undefined;
91

    
92
function GetQuotedStringRE() {
93
  if (QUOTED_STRING_RE === undefined) {
94
    QUOTED_STRING_RE = new $RegExp("'[^']+'", 'g');
95
  }
96
  return QUOTED_STRING_RE;
97
}
98

    
99
/**
100
 * Matches valid service name.
101
 */
102
var SERVICE_RE = undefined;
103

    
104
function GetServiceRE() {
105
  if (SERVICE_RE === undefined) {
106
    SERVICE_RE =
107
        new $RegExp('^(collator|numberformat|dateformat|breakiterator)$');
108
  }
109
  return SERVICE_RE;
110
}
111

    
112
/**
113
 * Validates a language tag against bcp47 spec.
114
 * Actual value is assigned on first run.
115
 */
116
var LANGUAGE_TAG_RE = undefined;
117

    
118
function GetLanguageTagRE() {
119
  if (LANGUAGE_TAG_RE === undefined) {
120
    BuildLanguageTagREs();
121
  }
122
  return LANGUAGE_TAG_RE;
123
}
124

    
125
/**
126
 * Helps find duplicate variants in the language tag.
127
 */
128
var LANGUAGE_VARIANT_RE = undefined;
129

    
130
function GetLanguageVariantRE() {
131
  if (LANGUAGE_VARIANT_RE === undefined) {
132
    BuildLanguageTagREs();
133
  }
134
  return LANGUAGE_VARIANT_RE;
135
}
136

    
137
/**
138
 * Helps find duplicate singletons in the language tag.
139
 */
140
var LANGUAGE_SINGLETON_RE = undefined;
141

    
142
function GetLanguageSingletonRE() {
143
  if (LANGUAGE_SINGLETON_RE === undefined) {
144
    BuildLanguageTagREs();
145
  }
146
  return LANGUAGE_SINGLETON_RE;
147
}
148

    
149
/**
150
 * Matches valid IANA time zone names.
151
 */
152
var TIMEZONE_NAME_CHECK_RE = undefined;
153

    
154
function GetTimezoneNameCheckRE() {
155
  if (TIMEZONE_NAME_CHECK_RE === undefined) {
156
    TIMEZONE_NAME_CHECK_RE =
157
        new $RegExp('^([A-Za-z]+)/([A-Za-z]+)(?:_([A-Za-z]+))*$');
158
  }
159
  return TIMEZONE_NAME_CHECK_RE;
160
}
161

    
162
/**
163
 * Maps ICU calendar names into LDML type.
164
 */
165
var ICU_CALENDAR_MAP = {
166
  'gregorian': 'gregory',
167
  'japanese': 'japanese',
168
  'buddhist': 'buddhist',
169
  'roc': 'roc',
170
  'persian': 'persian',
171
  'islamic-civil': 'islamicc',
172
  'islamic': 'islamic',
173
  'hebrew': 'hebrew',
174
  'chinese': 'chinese',
175
  'indian': 'indian',
176
  'coptic': 'coptic',
177
  'ethiopic': 'ethiopic',
178
  'ethiopic-amete-alem': 'ethioaa'
179
};
180

    
181
/**
182
 * Map of Unicode extensions to option properties, and their values and types,
183
 * for a collator.
184
 */
185
var COLLATOR_KEY_MAP = {
186
  'kn': {'property': 'numeric', 'type': 'boolean'},
187
  'kf': {'property': 'caseFirst', 'type': 'string',
188
         'values': ['false', 'lower', 'upper']}
189
};
190

    
191
/**
192
 * Map of Unicode extensions to option properties, and their values and types,
193
 * for a number format.
194
 */
195
var NUMBER_FORMAT_KEY_MAP = {
196
  'nu': {'property': undefined, 'type': 'string'}
197
};
198

    
199
/**
200
 * Map of Unicode extensions to option properties, and their values and types,
201
 * for a date/time format.
202
 */
203
var DATETIME_FORMAT_KEY_MAP = {
204
  'ca': {'property': undefined, 'type': 'string'},
205
  'nu': {'property': undefined, 'type': 'string'}
206
};
207

    
208
/**
209
 * Allowed -u-co- values. List taken from:
210
 * http://unicode.org/repos/cldr/trunk/common/bcp47/collation.xml
211
 */
212
var ALLOWED_CO_VALUES = [
213
  'big5han', 'dict', 'direct', 'ducet', 'gb2312', 'phonebk', 'phonetic',
214
  'pinyin', 'reformed', 'searchjl', 'stroke', 'trad', 'unihan', 'zhuyin'
215
];
216

    
217
/**
218
 * Error message for when function object is created with new and it's not
219
 * a constructor.
220
 */
221
var ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR =
222
  'Function object that\'s not a constructor was created with new';
223

    
224

    
225
/**
226
 * Adds bound method to the prototype of the given object.
227
 */
228
function addBoundMethod(obj, methodName, implementation, length) {
229
  function getter() {
230
    if (!this || typeof this !== 'object' ||
231
        this.__initializedIntlObject === undefined) {
232
        throw new $TypeError('Method ' + methodName + ' called on a ' +
233
                            'non-object or on a wrong type of object.');
234
    }
235
    var internalName = '__bound' + methodName + '__';
236
    if (this[internalName] === undefined) {
237
      var that = this;
238
      var boundMethod;
239
      if (length === undefined || length === 2) {
240
        boundMethod = function(x, y) {
241
          if (%_IsConstructCall()) {
242
            throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
243
          }
244
          return implementation(that, x, y);
245
        }
246
      } else if (length === 1) {
247
        boundMethod = function(x) {
248
          if (%_IsConstructCall()) {
249
            throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
250
          }
251
          return implementation(that, x);
252
        }
253
      } else {
254
        boundMethod = function() {
255
          if (%_IsConstructCall()) {
256
            throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
257
          }
258
          // DateTimeFormat.format needs to be 0 arg method, but can stil
259
          // receive optional dateValue param. If one was provided, pass it
260
          // along.
261
          if (%_ArgumentsLength() > 0) {
262
            return implementation(that, %_Arguments(0));
263
          } else {
264
            return implementation(that);
265
          }
266
        }
267
      }
268
      %FunctionSetName(boundMethod, internalName);
269
      %FunctionRemovePrototype(boundMethod);
270
      %SetNativeFlag(boundMethod);
271
      this[internalName] = boundMethod;
272
    }
273
    return this[internalName];
274
  }
275

    
276
  %FunctionSetName(getter, methodName);
277
  %FunctionRemovePrototype(getter);
278
  %SetNativeFlag(getter);
279

    
280
  $Object.defineProperty(obj.prototype, methodName, {
281
    get: getter,
282
    enumerable: false,
283
    configurable: true
284
  });
285
}
286

    
287

    
288
/**
289
 * Returns an intersection of locales and service supported locales.
290
 * Parameter locales is treated as a priority list.
291
 */
292
function supportedLocalesOf(service, locales, options) {
293
  if (IS_NULL(service.match(GetServiceRE()))) {
294
    throw new $Error('Internal error, wrong service type: ' + service);
295
  }
296

    
297
  // Provide defaults if matcher was not specified.
298
  if (options === undefined) {
299
    options = {};
300
  } else {
301
    options = toObject(options);
302
  }
303

    
304
  var matcher = options.localeMatcher;
305
  if (matcher !== undefined) {
306
    matcher = $String(matcher);
307
    if (matcher !== 'lookup' && matcher !== 'best fit') {
308
      throw new $RangeError('Illegal value for localeMatcher:' + matcher);
309
    }
310
  } else {
311
    matcher = 'best fit';
312
  }
313

    
314
  var requestedLocales = initializeLocaleList(locales);
315

    
316
  // Cache these, they don't ever change per service.
317
  if (AVAILABLE_LOCALES[service] === undefined) {
318
    AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
319
  }
320

    
321
  // Use either best fit or lookup algorithm to match locales.
322
  if (matcher === 'best fit') {
323
    return initializeLocaleList(bestFitSupportedLocalesOf(
324
        requestedLocales, AVAILABLE_LOCALES[service]));
325
  }
326

    
327
  return initializeLocaleList(lookupSupportedLocalesOf(
328
      requestedLocales, AVAILABLE_LOCALES[service]));
329
}
330

    
331

    
332
/**
333
 * Returns the subset of the provided BCP 47 language priority list for which
334
 * this service has a matching locale when using the BCP 47 Lookup algorithm.
335
 * Locales appear in the same order in the returned list as in the input list.
336
 */
337
function lookupSupportedLocalesOf(requestedLocales, availableLocales) {
338
  var matchedLocales = [];
339
  for (var i = 0; i < requestedLocales.length; ++i) {
340
    // Remove -u- extension.
341
    var locale = requestedLocales[i].replace(GetUnicodeExtensionRE(), '');
342
    do {
343
      if (availableLocales[locale] !== undefined) {
344
        // Push requested locale not the resolved one.
345
        matchedLocales.push(requestedLocales[i]);
346
        break;
347
      }
348
      // Truncate locale if possible, if not break.
349
      var pos = locale.lastIndexOf('-');
350
      if (pos === -1) {
351
        break;
352
      }
353
      locale = locale.substring(0, pos);
354
    } while (true);
355
  }
356

    
357
  return matchedLocales;
358
}
359

    
360

    
361
/**
362
 * Returns the subset of the provided BCP 47 language priority list for which
363
 * this service has a matching locale when using the implementation
364
 * dependent algorithm.
365
 * Locales appear in the same order in the returned list as in the input list.
366
 */
367
function bestFitSupportedLocalesOf(requestedLocales, availableLocales) {
368
  // TODO(cira): implement better best fit algorithm.
369
  return lookupSupportedLocalesOf(requestedLocales, availableLocales);
370
}
371

    
372

    
373
/**
374
 * Returns a getOption function that extracts property value for given
375
 * options object. If property is missing it returns defaultValue. If value
376
 * is out of range for that property it throws RangeError.
377
 */
378
function getGetOption(options, caller) {
379
  if (options === undefined) {
380
    throw new $Error('Internal ' + caller + ' error. ' +
381
                    'Default options are missing.');
382
  }
383

    
384
  var getOption = function getOption(property, type, values, defaultValue) {
385
    if (options[property] !== undefined) {
386
      var value = options[property];
387
      switch (type) {
388
        case 'boolean':
389
          value = $Boolean(value);
390
          break;
391
        case 'string':
392
          value = $String(value);
393
          break;
394
        case 'number':
395
          value = $Number(value);
396
          break;
397
        default:
398
          throw new $Error('Internal error. Wrong value type.');
399
      }
400
      if (values !== undefined && values.indexOf(value) === -1) {
401
        throw new $RangeError('Value ' + value + ' out of range for ' + caller +
402
                             ' options property ' + property);
403
      }
404

    
405
      return value;
406
    }
407

    
408
    return defaultValue;
409
  }
410

    
411
  return getOption;
412
}
413

    
414

    
415
/**
416
 * Compares a BCP 47 language priority list requestedLocales against the locales
417
 * in availableLocales and determines the best available language to meet the
418
 * request. Two algorithms are available to match the locales: the Lookup
419
 * algorithm described in RFC 4647 section 3.4, and an implementation dependent
420
 * best-fit algorithm. Independent of the locale matching algorithm, options
421
 * specified through Unicode locale extension sequences are negotiated
422
 * separately, taking the caller's relevant extension keys and locale data as
423
 * well as client-provided options into consideration. Returns an object with
424
 * a locale property whose value is the language tag of the selected locale,
425
 * and properties for each key in relevantExtensionKeys providing the selected
426
 * value for that key.
427
 */
428
function resolveLocale(service, requestedLocales, options) {
429
  requestedLocales = initializeLocaleList(requestedLocales);
430

    
431
  var getOption = getGetOption(options, service);
432
  var matcher = getOption('localeMatcher', 'string',
433
                          ['lookup', 'best fit'], 'best fit');
434
  var resolved;
435
  if (matcher === 'lookup') {
436
    resolved = lookupMatcher(service, requestedLocales);
437
  } else {
438
    resolved = bestFitMatcher(service, requestedLocales);
439
  }
440

    
441
  return resolved;
442
}
443

    
444

    
445
/**
446
 * Returns best matched supported locale and extension info using basic
447
 * lookup algorithm.
448
 */
449
function lookupMatcher(service, requestedLocales) {
450
  if (IS_NULL(service.match(GetServiceRE()))) {
451
    throw new $Error('Internal error, wrong service type: ' + service);
452
  }
453

    
454
  // Cache these, they don't ever change per service.
455
  if (AVAILABLE_LOCALES[service] === undefined) {
456
    AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
457
  }
458

    
459
  for (var i = 0; i < requestedLocales.length; ++i) {
460
    // Remove all extensions.
461
    var locale = requestedLocales[i].replace(GetAnyExtensionRE(), '');
462
    do {
463
      if (AVAILABLE_LOCALES[service][locale] !== undefined) {
464
        // Return the resolved locale and extension.
465
        var extensionMatch = requestedLocales[i].match(GetUnicodeExtensionRE());
466
        var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0];
467
        return {'locale': locale, 'extension': extension, 'position': i};
468
      }
469
      // Truncate locale if possible.
470
      var pos = locale.lastIndexOf('-');
471
      if (pos === -1) {
472
        break;
473
      }
474
      locale = locale.substring(0, pos);
475
    } while (true);
476
  }
477

    
478
  // Didn't find a match, return default.
479
  if (DEFAULT_ICU_LOCALE === undefined) {
480
    DEFAULT_ICU_LOCALE = %GetDefaultICULocale();
481
  }
482

    
483
  return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1};
484
}
485

    
486

    
487
/**
488
 * Returns best matched supported locale and extension info using
489
 * implementation dependend algorithm.
490
 */
491
function bestFitMatcher(service, requestedLocales) {
492
  // TODO(cira): implement better best fit algorithm.
493
  return lookupMatcher(service, requestedLocales);
494
}
495

    
496

    
497
/**
498
 * Parses Unicode extension into key - value map.
499
 * Returns empty object if the extension string is invalid.
500
 * We are not concerned with the validity of the values at this point.
501
 */
502
function parseExtension(extension) {
503
  var extensionSplit = extension.split('-');
504

    
505
  // Assume ['', 'u', ...] input, but don't throw.
506
  if (extensionSplit.length <= 2 ||
507
      (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) {
508
    return {};
509
  }
510

    
511
  // Key is {2}alphanum, value is {3,8}alphanum.
512
  // Some keys may not have explicit values (booleans).
513
  var extensionMap = {};
514
  var previousKey = undefined;
515
  for (var i = 2; i < extensionSplit.length; ++i) {
516
    var length = extensionSplit[i].length;
517
    var element = extensionSplit[i];
518
    if (length === 2) {
519
      extensionMap[element] = undefined;
520
      previousKey = element;
521
    } else if (length >= 3 && length <=8 && previousKey !== undefined) {
522
      extensionMap[previousKey] = element;
523
      previousKey = undefined;
524
    } else {
525
      // There is a value that's too long, or that doesn't have a key.
526
      return {};
527
    }
528
  }
529

    
530
  return extensionMap;
531
}
532

    
533

    
534
/**
535
 * Converts parameter to an Object if possible.
536
 */
537
function toObject(value) {
538
  if (IS_NULL_OR_UNDEFINED(value)) {
539
    throw new $TypeError('Value cannot be converted to an Object.');
540
  }
541

    
542
  return $Object(value);
543
}
544

    
545

    
546
/**
547
 * Populates internalOptions object with boolean key-value pairs
548
 * from extensionMap and options.
549
 * Returns filtered extension (number and date format constructors use
550
 * Unicode extensions for passing parameters to ICU).
551
 * It's used for extension-option pairs only, e.g. kn-normalization, but not
552
 * for 'sensitivity' since it doesn't have extension equivalent.
553
 * Extensions like nu and ca don't have options equivalent, so we place
554
 * undefined in the map.property to denote that.
555
 */
556
function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) {
557
  var extension = '';
558

    
559
  var updateExtension = function updateExtension(key, value) {
560
    return '-' + key + '-' + $String(value);
561
  }
562

    
563
  var updateProperty = function updateProperty(property, type, value) {
564
    if (type === 'boolean' && (typeof value === 'string')) {
565
      value = (value === 'true') ? true : false;
566
    }
567

    
568
    if (property !== undefined) {
569
      defineWEProperty(outOptions, property, value);
570
    }
571
  }
572

    
573
  for (var key in keyValues) {
574
    if (keyValues.hasOwnProperty(key)) {
575
      var value = undefined;
576
      var map = keyValues[key];
577
      if (map.property !== undefined) {
578
        // This may return true if user specifies numeric: 'false', since
579
        // Boolean('nonempty') === true.
580
        value = getOption(map.property, map.type, map.values);
581
      }
582
      if (value !== undefined) {
583
        updateProperty(map.property, map.type, value);
584
        extension += updateExtension(key, value);
585
        continue;
586
      }
587
      // User options didn't have it, check Unicode extension.
588
      // Here we want to convert strings 'true', 'false' into proper Boolean
589
      // values (not a user error).
590
      if (extensionMap.hasOwnProperty(key)) {
591
        value = extensionMap[key];
592
        if (value !== undefined) {
593
          updateProperty(map.property, map.type, value);
594
          extension += updateExtension(key, value);
595
        } else if (map.type === 'boolean') {
596
          // Boolean keys are allowed not to have values in Unicode extension.
597
          // Those default to true.
598
          updateProperty(map.property, map.type, true);
599
          extension += updateExtension(key, true);
600
        }
601
      }
602
    }
603
  }
604

    
605
  return extension === ''? '' : '-u' + extension;
606
}
607

    
608

    
609
/**
610
 * Converts all OwnProperties into
611
 * configurable: false, writable: false, enumerable: true.
612
 */
613
function freezeArray(array) {
614
  array.forEach(function(element, index) {
615
    $Object.defineProperty(array, index, {value: element,
616
                                          configurable: false,
617
                                          writable: false,
618
                                          enumerable: true});
619
  });
620

    
621
  $Object.defineProperty(array, 'length', {value: array.length,
622
                                           writable: false});
623

    
624
  return array;
625
}
626

    
627

    
628
/**
629
 * It's sometimes desireable to leave user requested locale instead of ICU
630
 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter
631
 * one, if that was what user requested).
632
 * This function returns user specified tag if its maximized form matches ICU
633
 * resolved locale. If not we return ICU result.
634
 */
635
function getOptimalLanguageTag(original, resolved) {
636
  // Returns Array<Object>, where each object has maximized and base properties.
637
  // Maximized: zh -> zh-Hans-CN
638
  // Base: zh-CN-u-ca-gregory -> zh-CN
639
  // Take care of grandfathered or simple cases.
640
  if (original === resolved) {
641
    return original;
642
  }
643

    
644
  var locales = %GetLanguageTagVariants([original, resolved]);
645
  if (locales[0].maximized !== locales[1].maximized) {
646
    return resolved;
647
  }
648

    
649
  // Preserve extensions of resolved locale, but swap base tags with original.
650
  var resolvedBase = new $RegExp('^' + locales[1].base);
651
  return resolved.replace(resolvedBase, locales[0].base);
652
}
653

    
654

    
655
/**
656
 * Returns an Object that contains all of supported locales for a given
657
 * service.
658
 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ
659
 * that is supported. This is required by the spec.
660
 */
661
function getAvailableLocalesOf(service) {
662
  var available = %AvailableLocalesOf(service);
663

    
664
  for (var i in available) {
665
    if (available.hasOwnProperty(i)) {
666
      var parts = i.match(/^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/);
667
      if (parts !== null) {
668
        // Build xx-ZZ. We don't care about the actual value,
669
        // as long it's not undefined.
670
        available[parts[1] + '-' + parts[3]] = null;
671
      }
672
    }
673
  }
674

    
675
  return available;
676
}
677

    
678

    
679
/**
680
 * Defines a property and sets writable and enumerable to true.
681
 * Configurable is false by default.
682
 */
683
function defineWEProperty(object, property, value) {
684
  $Object.defineProperty(object, property,
685
                         {value: value, writable: true, enumerable: true});
686
}
687

    
688

    
689
/**
690
 * Adds property to an object if the value is not undefined.
691
 * Sets configurable descriptor to false.
692
 */
693
function addWEPropertyIfDefined(object, property, value) {
694
  if (value !== undefined) {
695
    defineWEProperty(object, property, value);
696
  }
697
}
698

    
699

    
700
/**
701
 * Defines a property and sets writable, enumerable and configurable to true.
702
 */
703
function defineWECProperty(object, property, value) {
704
  $Object.defineProperty(object, property,
705
                         {value: value,
706
                          writable: true,
707
                          enumerable: true,
708
                          configurable: true});
709
}
710

    
711

    
712
/**
713
 * Adds property to an object if the value is not undefined.
714
 * Sets all descriptors to true.
715
 */
716
function addWECPropertyIfDefined(object, property, value) {
717
  if (value !== undefined) {
718
    defineWECProperty(object, property, value);
719
  }
720
}
721

    
722

    
723
/**
724
 * Returns titlecased word, aMeRricA -> America.
725
 */
726
function toTitleCaseWord(word) {
727
  return word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase();
728
}
729

    
730
/**
731
 * Canonicalizes the language tag, or throws in case the tag is invalid.
732
 */
733
function canonicalizeLanguageTag(localeID) {
734
  // null is typeof 'object' so we have to do extra check.
735
  if (typeof localeID !== 'string' && typeof localeID !== 'object' ||
736
      IS_NULL(localeID)) {
737
    throw new $TypeError('Language ID should be string or object.');
738
  }
739

    
740
  var localeString = $String(localeID);
741

    
742
  if (isValidLanguageTag(localeString) === false) {
743
    throw new $RangeError('Invalid language tag: ' + localeString);
744
  }
745

    
746
  // This call will strip -kn but not -kn-true extensions.
747
  // ICU bug filled - http://bugs.icu-project.org/trac/ticket/9265.
748
  // TODO(cira): check if -u-kn-true-kc-true-kh-true still throws after
749
  // upgrade to ICU 4.9.
750
  var tag = %CanonicalizeLanguageTag(localeString);
751
  if (tag === 'invalid-tag') {
752
    throw new $RangeError('Invalid language tag: ' + localeString);
753
  }
754

    
755
  return tag;
756
}
757

    
758

    
759
/**
760
 * Returns an array where all locales are canonicalized and duplicates removed.
761
 * Throws on locales that are not well formed BCP47 tags.
762
 */
763
function initializeLocaleList(locales) {
764
  var seen = [];
765
  if (locales === undefined) {
766
    // Constructor is called without arguments.
767
    seen = [];
768
  } else {
769
    // We allow single string localeID.
770
    if (typeof locales === 'string') {
771
      seen.push(canonicalizeLanguageTag(locales));
772
      return freezeArray(seen);
773
    }
774

    
775
    var o = toObject(locales);
776
    // Converts it to UInt32 (>>> is shr on 32bit integers).
777
    var len = o.length >>> 0;
778

    
779
    for (var k = 0; k < len; k++) {
780
      if (k in o) {
781
        var value = o[k];
782

    
783
        var tag = canonicalizeLanguageTag(value);
784

    
785
        if (seen.indexOf(tag) === -1) {
786
          seen.push(tag);
787
        }
788
      }
789
    }
790
  }
791

    
792
  return freezeArray(seen);
793
}
794

    
795

    
796
/**
797
 * Validates the language tag. Section 2.2.9 of the bcp47 spec
798
 * defines a valid tag.
799
 *
800
 * ICU is too permissible and lets invalid tags, like
801
 * hant-cmn-cn, through.
802
 *
803
 * Returns false if the language tag is invalid.
804
 */
805
function isValidLanguageTag(locale) {
806
  // Check if it's well-formed, including grandfadered tags.
807
  if (GetLanguageTagRE().test(locale) === false) {
808
    return false;
809
  }
810

    
811
  // Just return if it's a x- form. It's all private.
812
  if (locale.indexOf('x-') === 0) {
813
    return true;
814
  }
815

    
816
  // Check if there are any duplicate variants or singletons (extensions).
817

    
818
  // Remove private use section.
819
  locale = locale.split(/-x-/)[0];
820

    
821
  // Skip language since it can match variant regex, so we start from 1.
822
  // We are matching i-klingon here, but that's ok, since i-klingon-klingon
823
  // is not valid and would fail LANGUAGE_TAG_RE test.
824
  var variants = [];
825
  var extensions = [];
826
  var parts = locale.split(/-/);
827
  for (var i = 1; i < parts.length; i++) {
828
    var value = parts[i];
829
    if (GetLanguageVariantRE().test(value) === true && extensions.length === 0) {
830
      if (variants.indexOf(value) === -1) {
831
        variants.push(value);
832
      } else {
833
        return false;
834
      }
835
    }
836

    
837
    if (GetLanguageSingletonRE().test(value) === true) {
838
      if (extensions.indexOf(value) === -1) {
839
        extensions.push(value);
840
      } else {
841
        return false;
842
      }
843
    }
844
  }
845

    
846
  return true;
847
 }
848

    
849

    
850
/**
851
 * Builds a regular expresion that validates the language tag
852
 * against bcp47 spec.
853
 * Uses http://tools.ietf.org/html/bcp47, section 2.1, ABNF.
854
 * Runs on load and initializes the global REs.
855
 */
856
function BuildLanguageTagREs() {
857
  var alpha = '[a-zA-Z]';
858
  var digit = '[0-9]';
859
  var alphanum = '(' + alpha + '|' + digit + ')';
860
  var regular = '(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|' +
861
                'zh-min|zh-min-nan|zh-xiang)';
862
  var irregular = '(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|' +
863
                  'i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|' +
864
                  'i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)';
865
  var grandfathered = '(' + irregular + '|' + regular + ')';
866
  var privateUse = '(x(-' + alphanum + '{1,8})+)';
867

    
868
  var singleton = '(' + digit + '|[A-WY-Za-wy-z])';
869
  LANGUAGE_SINGLETON_RE = new $RegExp('^' + singleton + '$', 'i');
870

    
871
  var extension = '(' + singleton + '(-' + alphanum + '{2,8})+)';
872

    
873
  var variant = '(' + alphanum + '{5,8}|(' + digit + alphanum + '{3}))';
874
  LANGUAGE_VARIANT_RE = new $RegExp('^' + variant + '$', 'i');
875

    
876
  var region = '(' + alpha + '{2}|' + digit + '{3})';
877
  var script = '(' + alpha + '{4})';
878
  var extLang = '(' + alpha + '{3}(-' + alpha + '{3}){0,2})';
879
  var language = '(' + alpha + '{2,3}(-' + extLang + ')?|' + alpha + '{4}|' +
880
                 alpha + '{5,8})';
881
  var langTag = language + '(-' + script + ')?(-' + region + ')?(-' +
882
                variant + ')*(-' + extension + ')*(-' + privateUse + ')?';
883

    
884
  var languageTag =
885
      '^(' + langTag + '|' + privateUse + '|' + grandfathered + ')$';
886
  LANGUAGE_TAG_RE = new $RegExp(languageTag, 'i');
887
}
888

    
889
/**
890
 * Initializes the given object so it's a valid Collator instance.
891
 * Useful for subclassing.
892
 */
893
function initializeCollator(collator, locales, options) {
894
  if (collator.hasOwnProperty('__initializedIntlObject')) {
895
    throw new $TypeError('Trying to re-initialize Collator object.');
896
  }
897

    
898
  if (options === undefined) {
899
    options = {};
900
  }
901

    
902
  var getOption = getGetOption(options, 'collator');
903

    
904
  var internalOptions = {};
905

    
906
  defineWEProperty(internalOptions, 'usage', getOption(
907
    'usage', 'string', ['sort', 'search'], 'sort'));
908

    
909
  var sensitivity = getOption('sensitivity', 'string',
910
                              ['base', 'accent', 'case', 'variant']);
911
  if (sensitivity === undefined && internalOptions.usage === 'sort') {
912
    sensitivity = 'variant';
913
  }
914
  defineWEProperty(internalOptions, 'sensitivity', sensitivity);
915

    
916
  defineWEProperty(internalOptions, 'ignorePunctuation', getOption(
917
    'ignorePunctuation', 'boolean', undefined, false));
918

    
919
  var locale = resolveLocale('collator', locales, options);
920

    
921
  // ICU can't take kb, kc... parameters through localeID, so we need to pass
922
  // them as options.
923
  // One exception is -co- which has to be part of the extension, but only for
924
  // usage: sort, and its value can't be 'standard' or 'search'.
925
  var extensionMap = parseExtension(locale.extension);
926
  setOptions(
927
      options, extensionMap, COLLATOR_KEY_MAP, getOption, internalOptions);
928

    
929
  var collation = 'default';
930
  var extension = '';
931
  if (extensionMap.hasOwnProperty('co') && internalOptions.usage === 'sort') {
932
    if (ALLOWED_CO_VALUES.indexOf(extensionMap.co) !== -1) {
933
      extension = '-u-co-' + extensionMap.co;
934
      // ICU can't tell us what the collation is, so save user's input.
935
      collation = extensionMap.co;
936
    }
937
  } else if (internalOptions.usage === 'search') {
938
    extension = '-u-co-search';
939
  }
940
  defineWEProperty(internalOptions, 'collation', collation);
941

    
942
  var requestedLocale = locale.locale + extension;
943

    
944
  // We define all properties C++ code may produce, to prevent security
945
  // problems. If malicious user decides to redefine Object.prototype.locale
946
  // we can't just use plain x.locale = 'us' or in C++ Set("locale", "us").
947
  // Object.defineProperties will either succeed defining or throw an error.
948
  var resolved = $Object.defineProperties({}, {
949
    caseFirst: {writable: true},
950
    collation: {value: internalOptions.collation, writable: true},
951
    ignorePunctuation: {writable: true},
952
    locale: {writable: true},
953
    numeric: {writable: true},
954
    requestedLocale: {value: requestedLocale, writable: true},
955
    sensitivity: {writable: true},
956
    strength: {writable: true},
957
    usage: {value: internalOptions.usage, writable: true}
958
  });
959

    
960
  var internalCollator = %CreateCollator(requestedLocale,
961
                                         internalOptions,
962
                                         resolved);
963

    
964
  // Writable, configurable and enumerable are set to false by default.
965
  $Object.defineProperty(collator, 'collator', {value: internalCollator});
966
  $Object.defineProperty(collator, '__initializedIntlObject',
967
                         {value: 'collator'});
968
  $Object.defineProperty(collator, 'resolved', {value: resolved});
969

    
970
  return collator;
971
}
972

    
973

    
974
/**
975
 * Constructs Intl.Collator object given optional locales and options
976
 * parameters.
977
 *
978
 * @constructor
979
 */
980
%SetProperty(Intl, 'Collator', function() {
981
    var locales = %_Arguments(0);
982
    var options = %_Arguments(1);
983

    
984
    if (!this || this === Intl) {
985
      // Constructor is called as a function.
986
      return new Intl.Collator(locales, options);
987
    }
988

    
989
    return initializeCollator(toObject(this), locales, options);
990
  },
991
  DONT_ENUM
992
);
993

    
994

    
995
/**
996
 * Collator resolvedOptions method.
997
 */
998
%SetProperty(Intl.Collator.prototype, 'resolvedOptions', function() {
999
    if (%_IsConstructCall()) {
1000
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1001
    }
1002

    
1003
    if (!this || typeof this !== 'object' ||
1004
        this.__initializedIntlObject !== 'collator') {
1005
      throw new $TypeError('resolvedOptions method called on a non-object ' +
1006
                           'or on a object that is not Intl.Collator.');
1007
    }
1008

    
1009
    var coll = this;
1010
    var locale = getOptimalLanguageTag(coll.resolved.requestedLocale,
1011
                                       coll.resolved.locale);
1012

    
1013
    return {
1014
      locale: locale,
1015
      usage: coll.resolved.usage,
1016
      sensitivity: coll.resolved.sensitivity,
1017
      ignorePunctuation: coll.resolved.ignorePunctuation,
1018
      numeric: coll.resolved.numeric,
1019
      caseFirst: coll.resolved.caseFirst,
1020
      collation: coll.resolved.collation
1021
    };
1022
  },
1023
  DONT_ENUM
1024
);
1025
%FunctionSetName(Intl.Collator.prototype.resolvedOptions, 'resolvedOptions');
1026
%FunctionRemovePrototype(Intl.Collator.prototype.resolvedOptions);
1027
%SetNativeFlag(Intl.Collator.prototype.resolvedOptions);
1028

    
1029

    
1030
/**
1031
 * Returns the subset of the given locale list for which this locale list
1032
 * has a matching (possibly fallback) locale. Locales appear in the same
1033
 * order in the returned list as in the input list.
1034
 * Options are optional parameter.
1035
 */
1036
%SetProperty(Intl.Collator, 'supportedLocalesOf', function(locales) {
1037
    if (%_IsConstructCall()) {
1038
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1039
    }
1040

    
1041
    return supportedLocalesOf('collator', locales, %_Arguments(1));
1042
  },
1043
  DONT_ENUM
1044
);
1045
%FunctionSetName(Intl.Collator.supportedLocalesOf, 'supportedLocalesOf');
1046
%FunctionRemovePrototype(Intl.Collator.supportedLocalesOf);
1047
%SetNativeFlag(Intl.Collator.supportedLocalesOf);
1048

    
1049

    
1050
/**
1051
 * When the compare method is called with two arguments x and y, it returns a
1052
 * Number other than NaN that represents the result of a locale-sensitive
1053
 * String comparison of x with y.
1054
 * The result is intended to order String values in the sort order specified
1055
 * by the effective locale and collation options computed during construction
1056
 * of this Collator object, and will be negative, zero, or positive, depending
1057
 * on whether x comes before y in the sort order, the Strings are equal under
1058
 * the sort order, or x comes after y in the sort order, respectively.
1059
 */
1060
function compare(collator, x, y) {
1061
  return %InternalCompare(collator.collator, $String(x), $String(y));
1062
};
1063

    
1064

    
1065
addBoundMethod(Intl.Collator, 'compare', compare, 2);
1066

    
1067
/**
1068
 * Verifies that the input is a well-formed ISO 4217 currency code.
1069
 * Don't uppercase to test. It could convert invalid code into a valid one.
1070
 * For example \u00DFP (Eszett+P) becomes SSP.
1071
 */
1072
function isWellFormedCurrencyCode(currency) {
1073
  return typeof currency == "string" &&
1074
      currency.length == 3 &&
1075
      currency.match(/[^A-Za-z]/) == null;
1076
}
1077

    
1078

    
1079
/**
1080
 * Returns the valid digit count for a property, or throws RangeError on
1081
 * a value out of the range.
1082
 */
1083
function getNumberOption(options, property, min, max, fallback) {
1084
  var value = options[property];
1085
  if (value !== undefined) {
1086
    value = $Number(value);
1087
    if ($isNaN(value) || value < min || value > max) {
1088
      throw new $RangeError(property + ' value is out of range.');
1089
    }
1090
    return $floor(value);
1091
  }
1092

    
1093
  return fallback;
1094
}
1095

    
1096

    
1097
/**
1098
 * Initializes the given object so it's a valid NumberFormat instance.
1099
 * Useful for subclassing.
1100
 */
1101
function initializeNumberFormat(numberFormat, locales, options) {
1102
  if (numberFormat.hasOwnProperty('__initializedIntlObject')) {
1103
    throw new $TypeError('Trying to re-initialize NumberFormat object.');
1104
  }
1105

    
1106
  if (options === undefined) {
1107
    options = {};
1108
  }
1109

    
1110
  var getOption = getGetOption(options, 'numberformat');
1111

    
1112
  var locale = resolveLocale('numberformat', locales, options);
1113

    
1114
  var internalOptions = {};
1115
  defineWEProperty(internalOptions, 'style', getOption(
1116
    'style', 'string', ['decimal', 'percent', 'currency'], 'decimal'));
1117

    
1118
  var currency = getOption('currency', 'string');
1119
  if (currency !== undefined && !isWellFormedCurrencyCode(currency)) {
1120
    throw new $RangeError('Invalid currency code: ' + currency);
1121
  }
1122

    
1123
  if (internalOptions.style === 'currency' && currency === undefined) {
1124
    throw new $TypeError('Currency code is required with currency style.');
1125
  }
1126

    
1127
  var currencyDisplay = getOption(
1128
      'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol');
1129
  if (internalOptions.style === 'currency') {
1130
    defineWEProperty(internalOptions, 'currency', currency.toUpperCase());
1131
    defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay);
1132
  }
1133

    
1134
  // Digit ranges.
1135
  var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1);
1136
  defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid);
1137

    
1138
  var mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0);
1139
  defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd);
1140

    
1141
  var mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, 3);
1142
  defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd);
1143

    
1144
  var mnsd = options['minimumSignificantDigits'];
1145
  var mxsd = options['maximumSignificantDigits'];
1146
  if (mnsd !== undefined || mxsd !== undefined) {
1147
    mnsd = getNumberOption(options, 'minimumSignificantDigits', 1, 21, 0);
1148
    defineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd);
1149

    
1150
    mxsd = getNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21);
1151
    defineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd);
1152
  }
1153

    
1154
  // Grouping.
1155
  defineWEProperty(internalOptions, 'useGrouping', getOption(
1156
    'useGrouping', 'boolean', undefined, true));
1157

    
1158
  // ICU prefers options to be passed using -u- extension key/values for
1159
  // number format, so we need to build that.
1160
  var extensionMap = parseExtension(locale.extension);
1161
  var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP,
1162
                             getOption, internalOptions);
1163

    
1164
  var requestedLocale = locale.locale + extension;
1165
  var resolved = $Object.defineProperties({}, {
1166
    currency: {writable: true},
1167
    currencyDisplay: {writable: true},
1168
    locale: {writable: true},
1169
    maximumFractionDigits: {writable: true},
1170
    minimumFractionDigits: {writable: true},
1171
    minimumIntegerDigits: {writable: true},
1172
    numberingSystem: {writable: true},
1173
    requestedLocale: {value: requestedLocale, writable: true},
1174
    style: {value: internalOptions.style, writable: true},
1175
    useGrouping: {writable: true}
1176
  });
1177
  if (internalOptions.hasOwnProperty('minimumSignificantDigits')) {
1178
    defineWEProperty(resolved, 'minimumSignificantDigits', undefined);
1179
  }
1180
  if (internalOptions.hasOwnProperty('maximumSignificantDigits')) {
1181
    defineWEProperty(resolved, 'maximumSignificantDigits', undefined);
1182
  }
1183
  var formatter = %CreateNumberFormat(requestedLocale,
1184
                                      internalOptions,
1185
                                      resolved);
1186

    
1187
  // We can't get information about number or currency style from ICU, so we
1188
  // assume user request was fulfilled.
1189
  if (internalOptions.style === 'currency') {
1190
    $Object.defineProperty(resolved, 'currencyDisplay', {value: currencyDisplay,
1191
                                                         writable: true});
1192
  }
1193

    
1194
  $Object.defineProperty(numberFormat, 'formatter', {value: formatter});
1195
  $Object.defineProperty(numberFormat, 'resolved', {value: resolved});
1196
  $Object.defineProperty(numberFormat, '__initializedIntlObject',
1197
                         {value: 'numberformat'});
1198

    
1199
  return numberFormat;
1200
}
1201

    
1202

    
1203
/**
1204
 * Constructs Intl.NumberFormat object given optional locales and options
1205
 * parameters.
1206
 *
1207
 * @constructor
1208
 */
1209
%SetProperty(Intl, 'NumberFormat', function() {
1210
    var locales = %_Arguments(0);
1211
    var options = %_Arguments(1);
1212

    
1213
    if (!this || this === Intl) {
1214
      // Constructor is called as a function.
1215
      return new Intl.NumberFormat(locales, options);
1216
    }
1217

    
1218
    return initializeNumberFormat(toObject(this), locales, options);
1219
  },
1220
  DONT_ENUM
1221
);
1222

    
1223

    
1224
/**
1225
 * NumberFormat resolvedOptions method.
1226
 */
1227
%SetProperty(Intl.NumberFormat.prototype, 'resolvedOptions', function() {
1228
    if (%_IsConstructCall()) {
1229
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1230
    }
1231

    
1232
    if (!this || typeof this !== 'object' ||
1233
        this.__initializedIntlObject !== 'numberformat') {
1234
      throw new $TypeError('resolvedOptions method called on a non-object' +
1235
          ' or on a object that is not Intl.NumberFormat.');
1236
    }
1237

    
1238
    var format = this;
1239
    var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
1240
                                       format.resolved.locale);
1241

    
1242
    var result = {
1243
      locale: locale,
1244
      numberingSystem: format.resolved.numberingSystem,
1245
      style: format.resolved.style,
1246
      useGrouping: format.resolved.useGrouping,
1247
      minimumIntegerDigits: format.resolved.minimumIntegerDigits,
1248
      minimumFractionDigits: format.resolved.minimumFractionDigits,
1249
      maximumFractionDigits: format.resolved.maximumFractionDigits,
1250
    };
1251

    
1252
    if (result.style === 'currency') {
1253
      defineWECProperty(result, 'currency', format.resolved.currency);
1254
      defineWECProperty(result, 'currencyDisplay',
1255
                        format.resolved.currencyDisplay);
1256
    }
1257

    
1258
    if (format.resolved.hasOwnProperty('minimumSignificantDigits')) {
1259
      defineWECProperty(result, 'minimumSignificantDigits',
1260
                        format.resolved.minimumSignificantDigits);
1261
    }
1262

    
1263
    if (format.resolved.hasOwnProperty('maximumSignificantDigits')) {
1264
      defineWECProperty(result, 'maximumSignificantDigits',
1265
                        format.resolved.maximumSignificantDigits);
1266
    }
1267

    
1268
    return result;
1269
  },
1270
  DONT_ENUM
1271
);
1272
%FunctionSetName(Intl.NumberFormat.prototype.resolvedOptions,
1273
                 'resolvedOptions');
1274
%FunctionRemovePrototype(Intl.NumberFormat.prototype.resolvedOptions);
1275
%SetNativeFlag(Intl.NumberFormat.prototype.resolvedOptions);
1276

    
1277

    
1278
/**
1279
 * Returns the subset of the given locale list for which this locale list
1280
 * has a matching (possibly fallback) locale. Locales appear in the same
1281
 * order in the returned list as in the input list.
1282
 * Options are optional parameter.
1283
 */
1284
%SetProperty(Intl.NumberFormat, 'supportedLocalesOf', function(locales) {
1285
    if (%_IsConstructCall()) {
1286
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1287
    }
1288

    
1289
    return supportedLocalesOf('numberformat', locales, %_Arguments(1));
1290
  },
1291
  DONT_ENUM
1292
);
1293
%FunctionSetName(Intl.NumberFormat.supportedLocalesOf, 'supportedLocalesOf');
1294
%FunctionRemovePrototype(Intl.NumberFormat.supportedLocalesOf);
1295
%SetNativeFlag(Intl.NumberFormat.supportedLocalesOf);
1296

    
1297

    
1298
/**
1299
 * Returns a String value representing the result of calling ToNumber(value)
1300
 * according to the effective locale and the formatting options of this
1301
 * NumberFormat.
1302
 */
1303
function formatNumber(formatter, value) {
1304
  // Spec treats -0 and +0 as 0.
1305
  var number = $Number(value);
1306
  if (number === -0) {
1307
    number = 0;
1308
  }
1309

    
1310
  return %InternalNumberFormat(formatter.formatter, number);
1311
}
1312

    
1313

    
1314
/**
1315
 * Returns a Number that represents string value that was passed in.
1316
 */
1317
function parseNumber(formatter, value) {
1318
  return %InternalNumberParse(formatter.formatter, $String(value));
1319
}
1320

    
1321

    
1322
addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1);
1323
addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1);
1324

    
1325
/**
1326
 * Returns a string that matches LDML representation of the options object.
1327
 */
1328
function toLDMLString(options) {
1329
  var getOption = getGetOption(options, 'dateformat');
1330

    
1331
  var ldmlString = '';
1332

    
1333
  var option = getOption('weekday', 'string', ['narrow', 'short', 'long']);
1334
  ldmlString += appendToLDMLString(
1335
      option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'});
1336

    
1337
  option = getOption('era', 'string', ['narrow', 'short', 'long']);
1338
  ldmlString += appendToLDMLString(
1339
      option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'});
1340

    
1341
  option = getOption('year', 'string', ['2-digit', 'numeric']);
1342
  ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'});
1343

    
1344
  option = getOption('month', 'string',
1345
                     ['2-digit', 'numeric', 'narrow', 'short', 'long']);
1346
  ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M',
1347
          'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'});
1348

    
1349
  option = getOption('day', 'string', ['2-digit', 'numeric']);
1350
  ldmlString += appendToLDMLString(
1351
      option, {'2-digit': 'dd', 'numeric': 'd'});
1352

    
1353
  var hr12 = getOption('hour12', 'boolean');
1354
  option = getOption('hour', 'string', ['2-digit', 'numeric']);
1355
  if (hr12 === undefined) {
1356
    ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'});
1357
  } else if (hr12 === true) {
1358
    ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'});
1359
  } else {
1360
    ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'});
1361
  }
1362

    
1363
  option = getOption('minute', 'string', ['2-digit', 'numeric']);
1364
  ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'});
1365

    
1366
  option = getOption('second', 'string', ['2-digit', 'numeric']);
1367
  ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'});
1368

    
1369
  option = getOption('timeZoneName', 'string', ['short', 'long']);
1370
  ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'});
1371

    
1372
  return ldmlString;
1373
}
1374

    
1375

    
1376
/**
1377
 * Returns either LDML equivalent of the current option or empty string.
1378
 */
1379
function appendToLDMLString(option, pairs) {
1380
  if (option !== undefined) {
1381
    return pairs[option];
1382
  } else {
1383
    return '';
1384
  }
1385
}
1386

    
1387

    
1388
/**
1389
 * Returns object that matches LDML representation of the date.
1390
 */
1391
function fromLDMLString(ldmlString) {
1392
  // First remove '' quoted text, so we lose 'Uhr' strings.
1393
  ldmlString = ldmlString.replace(GetQuotedStringRE(), '');
1394

    
1395
  var options = {};
1396
  var match = ldmlString.match(/E{3,5}/g);
1397
  options = appendToDateTimeObject(
1398
      options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'});
1399

    
1400
  match = ldmlString.match(/G{3,5}/g);
1401
  options = appendToDateTimeObject(
1402
      options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'});
1403

    
1404
  match = ldmlString.match(/y{1,2}/g);
1405
  options = appendToDateTimeObject(
1406
      options, 'year', match, {y: 'numeric', yy: '2-digit'});
1407

    
1408
  match = ldmlString.match(/M{1,5}/g);
1409
  options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit',
1410
      M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'});
1411

    
1412
  // Sometimes we get L instead of M for month - standalone name.
1413
  match = ldmlString.match(/L{1,5}/g);
1414
  options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit',
1415
      L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'});
1416

    
1417
  match = ldmlString.match(/d{1,2}/g);
1418
  options = appendToDateTimeObject(
1419
      options, 'day', match, {d: 'numeric', dd: '2-digit'});
1420

    
1421
  match = ldmlString.match(/h{1,2}/g);
1422
  if (match !== null) {
1423
    options['hour12'] = true;
1424
  }
1425
  options = appendToDateTimeObject(
1426
      options, 'hour', match, {h: 'numeric', hh: '2-digit'});
1427

    
1428
  match = ldmlString.match(/H{1,2}/g);
1429
  if (match !== null) {
1430
    options['hour12'] = false;
1431
  }
1432
  options = appendToDateTimeObject(
1433
      options, 'hour', match, {H: 'numeric', HH: '2-digit'});
1434

    
1435
  match = ldmlString.match(/m{1,2}/g);
1436
  options = appendToDateTimeObject(
1437
      options, 'minute', match, {m: 'numeric', mm: '2-digit'});
1438

    
1439
  match = ldmlString.match(/s{1,2}/g);
1440
  options = appendToDateTimeObject(
1441
      options, 'second', match, {s: 'numeric', ss: '2-digit'});
1442

    
1443
  match = ldmlString.match(/z|zzzz/g);
1444
  options = appendToDateTimeObject(
1445
      options, 'timeZoneName', match, {z: 'short', zzzz: 'long'});
1446

    
1447
  return options;
1448
}
1449

    
1450

    
1451
function appendToDateTimeObject(options, option, match, pairs) {
1452
  if (IS_NULL(match)) {
1453
    if (!options.hasOwnProperty(option)) {
1454
      defineWEProperty(options, option, undefined);
1455
    }
1456
    return options;
1457
  }
1458

    
1459
  var property = match[0];
1460
  defineWEProperty(options, option, pairs[property]);
1461

    
1462
  return options;
1463
}
1464

    
1465

    
1466
/**
1467
 * Returns options with at least default values in it.
1468
 */
1469
function toDateTimeOptions(options, required, defaults) {
1470
  if (options === undefined) {
1471
    options = null;
1472
  } else {
1473
    options = toObject(options);
1474
  }
1475

    
1476
  options = $Object.apply(this, [options]);
1477

    
1478
  var needsDefault = true;
1479
  if ((required === 'date' || required === 'any') &&
1480
      (options.weekday !== undefined || options.year !== undefined ||
1481
       options.month !== undefined || options.day !== undefined)) {
1482
    needsDefault = false;
1483
  }
1484

    
1485
  if ((required === 'time' || required === 'any') &&
1486
      (options.hour !== undefined || options.minute !== undefined ||
1487
       options.second !== undefined)) {
1488
    needsDefault = false;
1489
  }
1490

    
1491
  if (needsDefault && (defaults === 'date' || defaults === 'all')) {
1492
    $Object.defineProperty(options, 'year', {value: 'numeric',
1493
                                             writable: true,
1494
                                             enumerable: true,
1495
                                             configurable: true});
1496
    $Object.defineProperty(options, 'month', {value: 'numeric',
1497
                                              writable: true,
1498
                                              enumerable: true,
1499
                                              configurable: true});
1500
    $Object.defineProperty(options, 'day', {value: 'numeric',
1501
                                            writable: true,
1502
                                            enumerable: true,
1503
                                            configurable: true});
1504
  }
1505

    
1506
  if (needsDefault && (defaults === 'time' || defaults === 'all')) {
1507
    $Object.defineProperty(options, 'hour', {value: 'numeric',
1508
                                             writable: true,
1509
                                             enumerable: true,
1510
                                             configurable: true});
1511
    $Object.defineProperty(options, 'minute', {value: 'numeric',
1512
                                               writable: true,
1513
                                               enumerable: true,
1514
                                               configurable: true});
1515
    $Object.defineProperty(options, 'second', {value: 'numeric',
1516
                                               writable: true,
1517
                                               enumerable: true,
1518
                                               configurable: true});
1519
  }
1520

    
1521
  return options;
1522
}
1523

    
1524

    
1525
/**
1526
 * Initializes the given object so it's a valid DateTimeFormat instance.
1527
 * Useful for subclassing.
1528
 */
1529
function initializeDateTimeFormat(dateFormat, locales, options) {
1530

    
1531
  if (dateFormat.hasOwnProperty('__initializedIntlObject')) {
1532
    throw new $TypeError('Trying to re-initialize DateTimeFormat object.');
1533
  }
1534

    
1535
  if (options === undefined) {
1536
    options = {};
1537
  }
1538

    
1539
  var locale = resolveLocale('dateformat', locales, options);
1540

    
1541
  options = toDateTimeOptions(options, 'any', 'date');
1542

    
1543
  var getOption = getGetOption(options, 'dateformat');
1544

    
1545
  // We implement only best fit algorithm, but still need to check
1546
  // if the formatMatcher values are in range.
1547
  var matcher = getOption('formatMatcher', 'string',
1548
                          ['basic', 'best fit'], 'best fit');
1549

    
1550
  // Build LDML string for the skeleton that we pass to the formatter.
1551
  var ldmlString = toLDMLString(options);
1552

    
1553
  // Filter out supported extension keys so we know what to put in resolved
1554
  // section later on.
1555
  // We need to pass calendar and number system to the method.
1556
  var tz = canonicalizeTimeZoneID(options.timeZone);
1557

    
1558
  // ICU prefers options to be passed using -u- extension key/values, so
1559
  // we need to build that.
1560
  var internalOptions = {};
1561
  var extensionMap = parseExtension(locale.extension);
1562
  var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP,
1563
                             getOption, internalOptions);
1564

    
1565
  var requestedLocale = locale.locale + extension;
1566
  var resolved = $Object.defineProperties({}, {
1567
    calendar: {writable: true},
1568
    day: {writable: true},
1569
    era: {writable: true},
1570
    hour12: {writable: true},
1571
    hour: {writable: true},
1572
    locale: {writable: true},
1573
    minute: {writable: true},
1574
    month: {writable: true},
1575
    numberingSystem: {writable: true},
1576
    pattern: {writable: true},
1577
    requestedLocale: {value: requestedLocale, writable: true},
1578
    second: {writable: true},
1579
    timeZone: {writable: true},
1580
    timeZoneName: {writable: true},
1581
    tz: {value: tz, writable: true},
1582
    weekday: {writable: true},
1583
    year: {writable: true}
1584
  });
1585

    
1586
  var formatter = %CreateDateTimeFormat(
1587
    requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved);
1588

    
1589
  if (tz !== undefined && tz !== resolved.timeZone) {
1590
    throw new $RangeError('Unsupported time zone specified ' + tz);
1591
  }
1592

    
1593
  $Object.defineProperty(dateFormat, 'formatter', {value: formatter});
1594
  $Object.defineProperty(dateFormat, 'resolved', {value: resolved});
1595
  $Object.defineProperty(dateFormat, '__initializedIntlObject',
1596
                         {value: 'dateformat'});
1597

    
1598
  return dateFormat;
1599
}
1600

    
1601

    
1602
/**
1603
 * Constructs Intl.DateTimeFormat object given optional locales and options
1604
 * parameters.
1605
 *
1606
 * @constructor
1607
 */
1608
%SetProperty(Intl, 'DateTimeFormat', function() {
1609
    var locales = %_Arguments(0);
1610
    var options = %_Arguments(1);
1611

    
1612
    if (!this || this === Intl) {
1613
      // Constructor is called as a function.
1614
      return new Intl.DateTimeFormat(locales, options);
1615
    }
1616

    
1617
    return initializeDateTimeFormat(toObject(this), locales, options);
1618
  },
1619
  DONT_ENUM
1620
);
1621

    
1622

    
1623
/**
1624
 * DateTimeFormat resolvedOptions method.
1625
 */
1626
%SetProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() {
1627
    if (%_IsConstructCall()) {
1628
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1629
    }
1630

    
1631
    if (!this || typeof this !== 'object' ||
1632
        this.__initializedIntlObject !== 'dateformat') {
1633
      throw new $TypeError('resolvedOptions method called on a non-object or ' +
1634
          'on a object that is not Intl.DateTimeFormat.');
1635
    }
1636

    
1637
    var format = this;
1638
    var fromPattern = fromLDMLString(format.resolved.pattern);
1639
    var userCalendar = ICU_CALENDAR_MAP[format.resolved.calendar];
1640
    if (userCalendar === undefined) {
1641
      // Use ICU name if we don't have a match. It shouldn't happen, but
1642
      // it would be too strict to throw for this.
1643
      userCalendar = format.resolved.calendar;
1644
    }
1645

    
1646
    var locale = getOptimalLanguageTag(format.resolved.requestedLocale,
1647
                                       format.resolved.locale);
1648

    
1649
    var result = {
1650
      locale: locale,
1651
      numberingSystem: format.resolved.numberingSystem,
1652
      calendar: userCalendar,
1653
      timeZone: format.resolved.timeZone
1654
    };
1655

    
1656
    addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName);
1657
    addWECPropertyIfDefined(result, 'era', fromPattern.era);
1658
    addWECPropertyIfDefined(result, 'year', fromPattern.year);
1659
    addWECPropertyIfDefined(result, 'month', fromPattern.month);
1660
    addWECPropertyIfDefined(result, 'day', fromPattern.day);
1661
    addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday);
1662
    addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12);
1663
    addWECPropertyIfDefined(result, 'hour', fromPattern.hour);
1664
    addWECPropertyIfDefined(result, 'minute', fromPattern.minute);
1665
    addWECPropertyIfDefined(result, 'second', fromPattern.second);
1666

    
1667
    return result;
1668
  },
1669
  DONT_ENUM
1670
);
1671
%FunctionSetName(Intl.DateTimeFormat.prototype.resolvedOptions,
1672
                 'resolvedOptions');
1673
%FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions);
1674
%SetNativeFlag(Intl.DateTimeFormat.prototype.resolvedOptions);
1675

    
1676

    
1677
/**
1678
 * Returns the subset of the given locale list for which this locale list
1679
 * has a matching (possibly fallback) locale. Locales appear in the same
1680
 * order in the returned list as in the input list.
1681
 * Options are optional parameter.
1682
 */
1683
%SetProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) {
1684
    if (%_IsConstructCall()) {
1685
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1686
    }
1687

    
1688
    return supportedLocalesOf('dateformat', locales, %_Arguments(1));
1689
  },
1690
  DONT_ENUM
1691
);
1692
%FunctionSetName(Intl.DateTimeFormat.supportedLocalesOf, 'supportedLocalesOf');
1693
%FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf);
1694
%SetNativeFlag(Intl.DateTimeFormat.supportedLocalesOf);
1695

    
1696

    
1697
/**
1698
 * Returns a String value representing the result of calling ToNumber(date)
1699
 * according to the effective locale and the formatting options of this
1700
 * DateTimeFormat.
1701
 */
1702
function formatDate(formatter, dateValue) {
1703
  var dateMs;
1704
  if (dateValue === undefined) {
1705
    dateMs = $Date.now();
1706
  } else {
1707
    dateMs = $Number(dateValue);
1708
  }
1709

    
1710
  if (!$isFinite(dateMs)) {
1711
    throw new $RangeError('Provided date is not in valid range.');
1712
  }
1713

    
1714
  return %InternalDateFormat(formatter.formatter, new $Date(dateMs));
1715
}
1716

    
1717

    
1718
/**
1719
 * Returns a Date object representing the result of calling ToString(value)
1720
 * according to the effective locale and the formatting options of this
1721
 * DateTimeFormat.
1722
 * Returns undefined if date string cannot be parsed.
1723
 */
1724
function parseDate(formatter, value) {
1725
  return %InternalDateParse(formatter.formatter, $String(value));
1726
}
1727

    
1728

    
1729
// 0 because date is optional argument.
1730
addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0);
1731
addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1);
1732

    
1733

    
1734
/**
1735
 * Returns canonical Area/Location name, or throws an exception if the zone
1736
 * name is invalid IANA name.
1737
 */
1738
function canonicalizeTimeZoneID(tzID) {
1739
  // Skip undefined zones.
1740
  if (tzID === undefined) {
1741
    return tzID;
1742
  }
1743

    
1744
  // Special case handling (UTC, GMT).
1745
  var upperID = tzID.toUpperCase();
1746
  if (upperID === 'UTC' || upperID === 'GMT' ||
1747
      upperID === 'ETC/UTC' || upperID === 'ETC/GMT') {
1748
    return 'UTC';
1749
  }
1750

    
1751
  // We expect only _ and / beside ASCII letters.
1752
  // All inputs should conform to Area/Location from now on.
1753
  var match = GetTimezoneNameCheckRE().exec(tzID);
1754
  if (IS_NULL(match)) {
1755
    throw new $RangeError('Expected Area/Location for time zone, got ' + tzID);
1756
  }
1757

    
1758
  var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]);
1759
  var i = 3;
1760
  while (match[i] !== undefined && i < match.length) {
1761
    result = result + '_' + toTitleCaseWord(match[i]);
1762
    i++;
1763
  }
1764

    
1765
  return result;
1766
}
1767

    
1768
/**
1769
 * Initializes the given object so it's a valid BreakIterator instance.
1770
 * Useful for subclassing.
1771
 */
1772
function initializeBreakIterator(iterator, locales, options) {
1773
  if (iterator.hasOwnProperty('__initializedIntlObject')) {
1774
    throw new $TypeError('Trying to re-initialize v8BreakIterator object.');
1775
  }
1776

    
1777
  if (options === undefined) {
1778
    options = {};
1779
  }
1780

    
1781
  var getOption = getGetOption(options, 'breakiterator');
1782

    
1783
  var internalOptions = {};
1784

    
1785
  defineWEProperty(internalOptions, 'type', getOption(
1786
    'type', 'string', ['character', 'word', 'sentence', 'line'], 'word'));
1787

    
1788
  var locale = resolveLocale('breakiterator', locales, options);
1789
  var resolved = $Object.defineProperties({}, {
1790
    requestedLocale: {value: locale.locale, writable: true},
1791
    type: {value: internalOptions.type, writable: true},
1792
    locale: {writable: true}
1793
  });
1794

    
1795
  var internalIterator = %CreateBreakIterator(locale.locale,
1796
                                              internalOptions,
1797
                                              resolved);
1798

    
1799
  $Object.defineProperty(iterator, 'iterator', {value: internalIterator});
1800
  $Object.defineProperty(iterator, 'resolved', {value: resolved});
1801
  $Object.defineProperty(iterator, '__initializedIntlObject',
1802
                         {value: 'breakiterator'});
1803

    
1804
  return iterator;
1805
}
1806

    
1807

    
1808
/**
1809
 * Constructs Intl.v8BreakIterator object given optional locales and options
1810
 * parameters.
1811
 *
1812
 * @constructor
1813
 */
1814
%SetProperty(Intl, 'v8BreakIterator', function() {
1815
    var locales = %_Arguments(0);
1816
    var options = %_Arguments(1);
1817

    
1818
    if (!this || this === Intl) {
1819
      // Constructor is called as a function.
1820
      return new Intl.v8BreakIterator(locales, options);
1821
    }
1822

    
1823
    return initializeBreakIterator(toObject(this), locales, options);
1824
  },
1825
  DONT_ENUM
1826
);
1827

    
1828

    
1829
/**
1830
 * BreakIterator resolvedOptions method.
1831
 */
1832
%SetProperty(Intl.v8BreakIterator.prototype, 'resolvedOptions', function() {
1833
    if (%_IsConstructCall()) {
1834
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1835
    }
1836

    
1837
    if (!this || typeof this !== 'object' ||
1838
        this.__initializedIntlObject !== 'breakiterator') {
1839
      throw new $TypeError('resolvedOptions method called on a non-object or ' +
1840
          'on a object that is not Intl.v8BreakIterator.');
1841
    }
1842

    
1843
    var segmenter = this;
1844
    var locale = getOptimalLanguageTag(segmenter.resolved.requestedLocale,
1845
                                       segmenter.resolved.locale);
1846

    
1847
    return {
1848
      locale: locale,
1849
      type: segmenter.resolved.type
1850
    };
1851
  },
1852
  DONT_ENUM
1853
);
1854
%FunctionSetName(Intl.v8BreakIterator.prototype.resolvedOptions,
1855
                 'resolvedOptions');
1856
%FunctionRemovePrototype(Intl.v8BreakIterator.prototype.resolvedOptions);
1857
%SetNativeFlag(Intl.v8BreakIterator.prototype.resolvedOptions);
1858

    
1859

    
1860
/**
1861
 * Returns the subset of the given locale list for which this locale list
1862
 * has a matching (possibly fallback) locale. Locales appear in the same
1863
 * order in the returned list as in the input list.
1864
 * Options are optional parameter.
1865
 */
1866
%SetProperty(Intl.v8BreakIterator, 'supportedLocalesOf', function(locales) {
1867
    if (%_IsConstructCall()) {
1868
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1869
    }
1870

    
1871
    return supportedLocalesOf('breakiterator', locales, %_Arguments(1));
1872
  },
1873
  DONT_ENUM
1874
);
1875
%FunctionSetName(Intl.v8BreakIterator.supportedLocalesOf, 'supportedLocalesOf');
1876
%FunctionRemovePrototype(Intl.v8BreakIterator.supportedLocalesOf);
1877
%SetNativeFlag(Intl.v8BreakIterator.supportedLocalesOf);
1878

    
1879

    
1880
/**
1881
 * Adopts text to segment using the iterator. Old text, if present,
1882
 * gets discarded.
1883
 */
1884
function adoptText(iterator, text) {
1885
  %BreakIteratorAdoptText(iterator.iterator, $String(text));
1886
}
1887

    
1888

    
1889
/**
1890
 * Returns index of the first break in the string and moves current pointer.
1891
 */
1892
function first(iterator) {
1893
  return %BreakIteratorFirst(iterator.iterator);
1894
}
1895

    
1896

    
1897
/**
1898
 * Returns the index of the next break and moves the pointer.
1899
 */
1900
function next(iterator) {
1901
  return %BreakIteratorNext(iterator.iterator);
1902
}
1903

    
1904

    
1905
/**
1906
 * Returns index of the current break.
1907
 */
1908
function current(iterator) {
1909
  return %BreakIteratorCurrent(iterator.iterator);
1910
}
1911

    
1912

    
1913
/**
1914
 * Returns type of the current break.
1915
 */
1916
function breakType(iterator) {
1917
  return %BreakIteratorBreakType(iterator.iterator);
1918
}
1919

    
1920

    
1921
addBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1);
1922
addBoundMethod(Intl.v8BreakIterator, 'first', first, 0);
1923
addBoundMethod(Intl.v8BreakIterator, 'next', next, 0);
1924
addBoundMethod(Intl.v8BreakIterator, 'current', current, 0);
1925
addBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0);
1926

    
1927
// Save references to Intl objects and methods we use, for added security.
1928
var savedObjects = {
1929
  'collator': Intl.Collator,
1930
  'numberformat': Intl.NumberFormat,
1931
  'dateformatall': Intl.DateTimeFormat,
1932
  'dateformatdate': Intl.DateTimeFormat,
1933
  'dateformattime': Intl.DateTimeFormat
1934
};
1935

    
1936

    
1937
// Default (created with undefined locales and options parameters) collator,
1938
// number and date format instances. They'll be created as needed.
1939
var defaultObjects = {
1940
  'collator': undefined,
1941
  'numberformat': undefined,
1942
  'dateformatall': undefined,
1943
  'dateformatdate': undefined,
1944
  'dateformattime': undefined,
1945
};
1946

    
1947

    
1948
/**
1949
 * Returns cached or newly created instance of a given service.
1950
 * We cache only default instances (where no locales or options are provided).
1951
 */
1952
function cachedOrNewService(service, locales, options, defaults) {
1953
  var useOptions = (defaults === undefined) ? options : defaults;
1954
  if (locales === undefined && options === undefined) {
1955
    if (defaultObjects[service] === undefined) {
1956
      defaultObjects[service] = new savedObjects[service](locales, useOptions);
1957
    }
1958
    return defaultObjects[service];
1959
  }
1960
  return new savedObjects[service](locales, useOptions);
1961
}
1962

    
1963

    
1964
/**
1965
 * Compares this and that, and returns less than 0, 0 or greater than 0 value.
1966
 * Overrides the built-in method.
1967
 */
1968
$Object.defineProperty($String.prototype, 'localeCompare', {
1969
  value: function(that) {
1970
    if (%_IsConstructCall()) {
1971
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
1972
    }
1973

    
1974
    if (IS_NULL_OR_UNDEFINED(this)) {
1975
      throw new $TypeError('Method invoked on undefined or null value.');
1976
    }
1977

    
1978
    var locales = %_Arguments(1);
1979
    var options = %_Arguments(2);
1980
    var collator = cachedOrNewService('collator', locales, options);
1981
    return compare(collator, this, that);
1982
  },
1983
  writable: true,
1984
  configurable: true,
1985
  enumerable: false
1986
});
1987
%FunctionSetName($String.prototype.localeCompare, 'localeCompare');
1988
%FunctionRemovePrototype($String.prototype.localeCompare);
1989
%SetNativeFlag($String.prototype.localeCompare);
1990

    
1991

    
1992
/**
1993
 * Formats a Number object (this) using locale and options values.
1994
 * If locale or options are omitted, defaults are used.
1995
 */
1996
$Object.defineProperty($Number.prototype, 'toLocaleString', {
1997
  value: function() {
1998
    if (%_IsConstructCall()) {
1999
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2000
    }
2001

    
2002
    if (!(this instanceof $Number) && typeof(this) !== 'number') {
2003
      throw new $TypeError('Method invoked on an object that is not Number.');
2004
    }
2005

    
2006
    var locales = %_Arguments(0);
2007
    var options = %_Arguments(1);
2008
    var numberFormat = cachedOrNewService('numberformat', locales, options);
2009
    return formatNumber(numberFormat, this);
2010
  },
2011
  writable: true,
2012
  configurable: true,
2013
  enumerable: false
2014
});
2015
%FunctionSetName($Number.prototype.toLocaleString, 'toLocaleString');
2016
%FunctionRemovePrototype($Number.prototype.toLocaleString);
2017
%SetNativeFlag($Number.prototype.toLocaleString);
2018

    
2019

    
2020
/**
2021
 * Returns actual formatted date or fails if date parameter is invalid.
2022
 */
2023
function toLocaleDateTime(date, locales, options, required, defaults, service) {
2024
  if (!(date instanceof $Date)) {
2025
    throw new $TypeError('Method invoked on an object that is not Date.');
2026
  }
2027

    
2028
  if ($isNaN(date)) {
2029
    return 'Invalid Date';
2030
  }
2031

    
2032
  var internalOptions = toDateTimeOptions(options, required, defaults);
2033

    
2034
  var dateFormat =
2035
      cachedOrNewService(service, locales, options, internalOptions);
2036

    
2037
  return formatDate(dateFormat, date);
2038
}
2039

    
2040

    
2041
/**
2042
 * Formats a Date object (this) using locale and options values.
2043
 * If locale or options are omitted, defaults are used - both date and time are
2044
 * present in the output.
2045
 */
2046
$Object.defineProperty($Date.prototype, 'toLocaleString', {
2047
  value: function() {
2048
    if (%_IsConstructCall()) {
2049
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2050
    }
2051

    
2052
    var locales = %_Arguments(0);
2053
    var options = %_Arguments(1);
2054
    return toLocaleDateTime(
2055
        this, locales, options, 'any', 'all', 'dateformatall');
2056
  },
2057
  writable: true,
2058
  configurable: true,
2059
  enumerable: false
2060
});
2061
%FunctionSetName($Date.prototype.toLocaleString, 'toLocaleString');
2062
%FunctionRemovePrototype($Date.prototype.toLocaleString);
2063
%SetNativeFlag($Date.prototype.toLocaleString);
2064

    
2065

    
2066
/**
2067
 * Formats a Date object (this) using locale and options values.
2068
 * If locale or options are omitted, defaults are used - only date is present
2069
 * in the output.
2070
 */
2071
$Object.defineProperty($Date.prototype, 'toLocaleDateString', {
2072
  value: function() {
2073
    if (%_IsConstructCall()) {
2074
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2075
    }
2076

    
2077
    var locales = %_Arguments(0);
2078
    var options = %_Arguments(1);
2079
    return toLocaleDateTime(
2080
        this, locales, options, 'date', 'date', 'dateformatdate');
2081
  },
2082
  writable: true,
2083
  configurable: true,
2084
  enumerable: false
2085
});
2086
%FunctionSetName($Date.prototype.toLocaleDateString, 'toLocaleDateString');
2087
%FunctionRemovePrototype($Date.prototype.toLocaleDateString);
2088
%SetNativeFlag($Date.prototype.toLocaleDateString);
2089

    
2090

    
2091
/**
2092
 * Formats a Date object (this) using locale and options values.
2093
 * If locale or options are omitted, defaults are used - only time is present
2094
 * in the output.
2095
 */
2096
$Object.defineProperty($Date.prototype, 'toLocaleTimeString', {
2097
  value: function() {
2098
    if (%_IsConstructCall()) {
2099
      throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR);
2100
    }
2101

    
2102
    var locales = %_Arguments(0);
2103
    var options = %_Arguments(1);
2104
    return toLocaleDateTime(
2105
        this, locales, options, 'time', 'time', 'dateformattime');
2106
  },
2107
  writable: true,
2108
  configurable: true,
2109
  enumerable: false
2110
});
2111
%FunctionSetName($Date.prototype.toLocaleTimeString, 'toLocaleTimeString');
2112
%FunctionRemovePrototype($Date.prototype.toLocaleTimeString);
2113
%SetNativeFlag($Date.prototype.toLocaleTimeString);
2114

    
2115
return Intl;
2116
}())});