escargot/src/intl/Intl.cpp
Seonghyun Kim a264ee2026 Implement Intl.NumberFormat.prototype.formatRangeToParts function
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-07-21 09:09:51 +09:00

2819 lines
97 KiB
C++

#if defined(ENABLE_ICU) && defined(ENABLE_INTL)
/*
* Copyright (C) 2015 Andy VanWagoner (thetalecrafter@gmail.com)
* Copyright (C) 2015 Sukolsak Sakshuwong (sukolsak@gmail.com)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) 2019-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include "Escargot.h"
#include "runtime/Context.h"
#include "runtime/ExecutionState.h"
#include "runtime/Value.h"
#include "runtime/Object.h"
#include "runtime/ArrayObject.h"
#include "runtime/VMInstance.h"
#include "Intl.h"
#include "IntlLocale.h"
namespace Escargot {
// Invalid tags starting with: https://github.com/tc39/ecma402/pull/289
static bool isValidTagInBCP47ButInvalidOnUTS35(const std::string& s)
{
if (s.rfind("no-nyn", 0) == 0) {
return true;
} else if (s.rfind("i-klingon", 0) == 0) {
return true;
} else if (s.rfind("zh-hak-CN", 0) == 0) {
return true;
} else if (s.rfind("sgn-ils", 0) == 0) {
return true;
}
return false;
}
static std::string grandfatheredLangTag(const std::string& locale)
{
// grandfathered = irregular / regular
HashMap<std::string, std::string> tagMap;
// Irregular.
tagMap["en-gb-oed"] = "en-GB-oed";
tagMap["i-ami"] = "ami";
tagMap["i-bnn"] = "bnn";
tagMap["i-default"] = "i-default";
tagMap["i-enochian"] = "i-enochian";
tagMap["i-hak"] = "hak";
tagMap["i-klingon"] = "tlh";
tagMap["i-lux"] = "lb";
tagMap["i-mingo"] = "i-mingo";
tagMap["i-navajo"] = "nv";
tagMap["i-pwn"] = "pwn";
tagMap["i-tao"] = "tao";
tagMap["i-tay"] = "tay";
tagMap["i-tsu"] = "tsu";
tagMap["sgn-be-fr"] = "sfb";
tagMap["sgn-be-nl"] = "vgt";
tagMap["sgn-ch-de"] = "sgg";
// Regular.
tagMap["art-lojban"] = "jbo";
tagMap["cel-gaulish"] = "xtg-x-cel-gaulish";
tagMap["no-bok"] = "nb";
tagMap["no-nyn"] = "nn";
tagMap["zh-guoyu"] = "zh";
tagMap["zh-hakka"] = "hak";
tagMap["zh-min"] = "zh-min";
tagMap["zh-min-nan"] = "nan";
tagMap["zh-xiang"] = "hsn";
auto tempLocale = locale;
std::transform(tempLocale.begin(), tempLocale.end(), tempLocale.begin(), tolower);
auto iter = tagMap.find(tempLocale);
if (iter != tagMap.end()) {
return iter->second;
}
return std::string();
}
static std::string intlPreferredLanguageTagStartWithA(const std::string& tag)
{
if (tag == "aam")
return "aas";
else if (tag == "adp")
return "dz";
else if (tag == "asd")
return "snz";
else if (tag == "aue")
return "ktz";
else if (tag == "ayx")
return "nun";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithB(const std::string& tag)
{
if (tag == "bgm")
return "bcg";
else if (tag == "bic")
return "bir";
else if (tag == "bjd")
return "drl";
else if (tag == "blg")
return "iba";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithC(const std::string& tag)
{
if (tag == "ccq")
return "rki";
else if (tag == "cjr")
return "mom";
else if (tag == "cka")
return "cmr";
else if (tag == "cmk")
return "xch";
else if (tag == "coy")
return "pij";
else if (tag == "cqu")
return "quh";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithD(const std::string& tag)
{
if (tag == "dit")
return "dif";
else if (tag == "drh")
return "khk";
else if (tag == "drr")
return "kzk";
else if (tag == "drw")
return "prs";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithG(const std::string& tag)
{
if (tag == "gav")
return "dev";
else if (tag == "gfx")
return "vaj";
else if (tag == "ggn")
return "gvr";
else if (tag == "gli")
return "kzk";
else if (tag == "gti")
return "nyc";
else if (tag == "guv")
return "duz";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithI(const std::string& tag)
{
if (tag == "ibi")
return "opa";
else if (tag == "ilw")
return "gal";
else if (tag == "in")
return "id";
else if (tag == "iw")
return "he";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithJ(const std::string& tag)
{
if (tag == "jeg")
return "oyb";
else if (tag == "ji")
return "yi";
else if (tag == "jw")
return "jv";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithK(const std::string& tag)
{
if (tag == "kgc")
return "tdf";
else if (tag == "kgh")
return "kml";
else if (tag == "koj")
return "kwv";
else if (tag == "krm")
return "bmf";
else if (tag == "ktr")
return "dtp";
else if (tag == "kvs")
return "gdj";
else if (tag == "kwq")
return "yam";
else if (tag == "kxe")
return "tvd";
else if (tag == "kxl")
return "kru";
else if (tag == "kzj")
return "dtp";
else if (tag == "kzt")
return "dtp";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithL(const std::string& tag)
{
if (tag == "lii")
return "raq";
else if (tag == "llo")
return "ngt";
else if (tag == "lmm")
return "rmx";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithM(const std::string& tag)
{
if (tag == "meg")
return "cir";
else if (tag == "mo")
return "ro";
else if (tag == "mst")
return "mry";
else if (tag == "mwj")
return "vaj";
else if (tag == "myd")
return "aog";
else if (tag == "myt")
return "mry";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithN(const std::string& tag)
{
if (tag == "nad")
return "xny";
else if (tag == "ncp")
return "kdz";
else if (tag == "nns")
return "nbr";
else if (tag == "nnx")
return "ngv";
else if (tag == "nts")
return "pij";
else if (tag == "nxu")
return "bpp";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithP(const std::string& tag)
{
if (tag == "pat")
return "kxr";
else if (tag == "pcr")
return "adx";
else if (tag == "pmc")
return "huw";
else if (tag == "pmu")
return "phr";
else if (tag == "ppa")
return "bfy";
else if (tag == "ppr")
return "lcq";
else if (tag == "pry")
return "prt";
else if (tag == "puz")
return "pub";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithT(const std::string& tag)
{
if (tag == "tdu")
return "dtp";
else if (tag == "thc")
return "tpo";
else if (tag == "thw")
return "ola";
else if (tag == "thx")
return "oyb";
else if (tag == "tie")
return "ras";
else if (tag == "tkk")
return "twm";
else if (tag == "tlw")
return "weo";
else if (tag == "tmp")
return "tyj";
else if (tag == "tne")
return "kak";
else if (tag == "tnf")
return "prs";
else if (tag == "tsf")
return "taj";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithX(const std::string& tag)
{
if (tag == "xba")
return "cax";
else if (tag == "xia")
return "acn";
else if (tag == "xkh")
return "waw";
else if (tag == "xrq")
return "dmw";
else
return "";
}
static std::string intlPreferredLanguageTagStartWithY(const std::string& tag)
{
if (tag == "ybd")
return "rki";
else if (tag == "yma")
return "lrr";
else if (tag == "ymt")
return "mtm";
else if (tag == "yos")
return "zom";
else if (tag == "yuu")
return "yug";
else
return "";
}
static std::string intlPreferredLanguageTag(const std::string& tag)
{
// languageAlias element in
// https://www.unicode.org/repos/cldr/trunk/common/supplemental/supplementalMetadata.xml
if (tag == "cmn")
return "zh";
else if (tag == "ces")
return "cs";
else if (tag == "heb")
return "he";
else if (tag == "xsj")
return "suj";
else if (tag == "aar")
return "aa";
// legacy
else if (tag == "tl")
return "fil";
switch (tag[0]) {
// 92 possible replacements
case 'a':
return intlPreferredLanguageTagStartWithA(tag);
case 'b':
return intlPreferredLanguageTagStartWithB(tag);
case 'c':
return intlPreferredLanguageTagStartWithC(tag);
case 'd':
return intlPreferredLanguageTagStartWithD(tag);
case 'g':
return intlPreferredLanguageTagStartWithG(tag);
case 'h': {
if (tag == "hrr")
return "jal";
return "";
}
case 'i':
return intlPreferredLanguageTagStartWithI(tag);
case 'j':
return intlPreferredLanguageTagStartWithJ(tag);
case 'k':
return intlPreferredLanguageTagStartWithK(tag);
case 'l':
return intlPreferredLanguageTagStartWithL(tag);
case 'm':
return intlPreferredLanguageTagStartWithM(tag);
case 'n':
return intlPreferredLanguageTagStartWithN(tag);
case 'o': {
if (tag == "oun")
return "vaj";
return "";
}
case 'p':
return intlPreferredLanguageTagStartWithP(tag);
case 's': {
if (tag == "sca")
return "hle";
else if (tag == "skk")
return "oyb";
else
return "";
}
case 't':
return intlPreferredLanguageTagStartWithT(tag);
case 'u': {
if (tag == "uok")
return "ema";
else
return "";
}
case 'x':
return intlPreferredLanguageTagStartWithX(tag);
case 'y':
return intlPreferredLanguageTagStartWithY(tag);
case 'z': {
if (tag == "zir")
return "scv";
return "";
}
default:
return "";
}
}
static std::string intlRedundantLanguageTag(const std::string& tag)
{
// non-iana
if (tag == "hy-arevela")
return "hy";
if (tag == "hy-arevmda")
return "hyw";
// 24 possible replacements
if (tag == "ja-Latn-hepburn-heploc")
return "ja-Latn-alalc97";
if (tag == "sgn-BR")
return "bzs";
if (tag == "sgn-CO")
return "csn";
if (tag == "sgn-DE")
return "gsg";
if (tag == "sgn-DK")
return "dsl";
if (tag == "sgn-ES")
return "ssp";
if (tag == "sgn-FR")
return "fsl";
if (tag == "sgn-GB")
return "bfi";
if (tag == "sgn-GR")
return "gss";
if (tag == "sgn-IE")
return "isg";
if (tag == "sgn-IT")
return "ise";
if (tag == "sgn-JP")
return "jsl";
if (tag == "sgn-MX")
return "mfs";
if (tag == "sgn-NI")
return "ncs";
if (tag == "sgn-NL")
return "dse";
if (tag == "sgn-NO")
return "nsl";
if (tag == "sgn-PT")
return "psr";
if (tag == "sgn-SE")
return "swl";
if (tag == "sgn-US")
return "ase";
if (tag == "sgn-ZA")
return "sfs";
if (tag == "zh-cmn-Hans")
return "cmn-Hans";
if (tag == "zh-cmn-Hant")
return "cmn-Hant";
return "";
}
std::string Intl::preferredLanguage(const std::string& language)
{
auto preferred = intlPreferredLanguageTag(language);
if (preferred != "") {
return preferred;
}
return language;
}
static std::string intlPreferredExtlangTagStartWithA(const std::string& tag)
{
if (tag == "aao")
return "ar";
else if (tag == "abh")
return "ar";
else if (tag == "abv")
return "ar";
else if (tag == "acm")
return "ar";
else if (tag == "acq")
return "ar";
else if (tag == "acw")
return "ar";
else if (tag == "acx")
return "ar";
else if (tag == "acy")
return "ar";
else if (tag == "adf")
return "ar";
else if (tag == "ads")
return "sgn";
else if (tag == "aeb")
return "ar";
else if (tag == "aec")
return "ar";
else if (tag == "aed")
return "sgn";
else if (tag == "aen")
return "sgn";
else if (tag == "afb")
return "ar";
else if (tag == "afg")
return "sgn";
else if (tag == "ajp")
return "ar";
else if (tag == "apc")
return "ar";
else if (tag == "apd")
return "ar";
else if (tag == "arb")
return "ar";
else if (tag == "arq")
return "ar";
else if (tag == "ars")
return "ar";
else if (tag == "ary")
return "ar";
else if (tag == "arz")
return "ar";
else if (tag == "ase")
return "sgn";
else if (tag == "asf")
return "sgn";
else if (tag == "asp")
return "sgn";
else if (tag == "asq")
return "sgn";
else if (tag == "asw")
return "sgn";
else if (tag == "auz")
return "ar";
else if (tag == "avl")
return "ar";
else if (tag == "ayh")
return "ar";
else if (tag == "ayl")
return "ar";
else if (tag == "ayn")
return "ar";
else if (tag == "ayp")
return "ar";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithB(const std::string& tag)
{
if (tag == "bbz")
return "ar";
else if (tag == "bfi")
return "sgn";
else if (tag == "bfk")
return "sgn";
else if (tag == "bjn")
return "ms";
else if (tag == "bog")
return "sgn";
else if (tag == "bqn")
return "sgn";
else if (tag == "bqy")
return "sgn";
else if (tag == "btj")
return "ms";
else if (tag == "bve")
return "ms";
else if (tag == "bvl")
return "sgn";
else if (tag == "bvu")
return "ms";
else if (tag == "bzs")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithC(const std::string& tag)
{
if (tag == "cdo")
return "zh";
else if (tag == "cds")
return "sgn";
else if (tag == "cjy")
return "zh";
else if (tag == "cmn")
return "zh";
else if (tag == "cnp")
return "zh";
else if (tag == "coa")
return "ms";
else if (tag == "cpx")
return "zh";
else if (tag == "csc")
return "sgn";
else if (tag == "csd")
return "sgn";
else if (tag == "cse")
return "sgn";
else if (tag == "csf")
return "sgn";
else if (tag == "csg")
return "sgn";
else if (tag == "csl")
return "sgn";
else if (tag == "csn")
return "sgn";
else if (tag == "csp")
return "zh";
else if (tag == "csq")
return "sgn";
else if (tag == "csr")
return "sgn";
else if (tag == "csx")
return "sgn";
else if (tag == "czh")
return "zh";
else if (tag == "czo")
return "zh";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithD(const std::string& tag)
{
if (tag == "doq")
return "sgn";
else if (tag == "dse")
return "sgn";
else if (tag == "dsl")
return "sgn";
else if (tag == "dup")
return "ms";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithE(const std::string& tag)
{
if (tag == "ecs")
return "sgn";
else if (tag == "ehs")
return "sgn";
else if (tag == "esl")
return "sgn";
else if (tag == "esn")
return "sgn";
else if (tag == "eso")
return "sgn";
else if (tag == "eth")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithF(const std::string& tag)
{
if (tag == "fcs")
return "sgn";
else if (tag == "fse")
return "sgn";
else if (tag == "fsl")
return "sgn";
else if (tag == "fss")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithG(const std::string& tag)
{
if (tag == "gan")
return "zh";
else if (tag == "gds")
return "sgn";
else if (tag == "gom")
return "kok";
else if (tag == "gse")
return "sgn";
else if (tag == "gsg")
return "sgn";
else if (tag == "gsm")
return "sgn";
else if (tag == "gss")
return "sgn";
else if (tag == "gus")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithH(const std::string& tag)
{
if (tag == "hab")
return "sgn";
else if (tag == "haf")
return "sgn";
else if (tag == "hak")
return "zh";
else if (tag == "hds")
return "sgn";
else if (tag == "hji")
return "ms";
else if (tag == "hks")
return "sgn";
else if (tag == "hos")
return "sgn";
else if (tag == "hps")
return "sgn";
else if (tag == "hsh")
return "sgn";
else if (tag == "hsl")
return "sgn";
else if (tag == "hsn")
return "zh";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithI(const std::string& tag)
{
if (tag == "icl")
return "sgn";
else if (tag == "iks")
return "sgn";
else if (tag == "ils")
return "sgn";
else if (tag == "inl")
return "sgn";
else if (tag == "ins")
return "sgn";
else if (tag == "ise")
return "sgn";
else if (tag == "isg")
return "sgn";
else if (tag == "isr")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithJ(const std::string& tag)
{
if (tag == "jak")
return "ms";
else if (tag == "jax")
return "ms";
else if (tag == "jcs")
return "sgn";
else if (tag == "jhs")
return "sgn";
else if (tag == "jks")
return "sgn";
else if (tag == "jls")
return "sgn";
else if (tag == "jos")
return "sgn";
else if (tag == "jsl")
return "sgn";
else if (tag == "jus")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithK(const std::string& tag)
{
if (tag == "kgi")
return "sgn";
else if (tag == "knn")
return "kok";
else if (tag == "kvb")
return "ms";
else if (tag == "kvk")
return "sgn";
else if (tag == "kvr")
return "ms";
else if (tag == "kxd")
return "ms";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithL(const std::string& tag)
{
if (tag == "lbs")
return "sgn";
else if (tag == "lce")
return "ms";
else if (tag == "lcf")
return "ms";
else if (tag == "liw")
return "ms";
else if (tag == "lls")
return "sgn";
else if (tag == "lsb")
return "sgn";
else if (tag == "lsg")
return "sgn";
else if (tag == "lsl")
return "sgn";
else if (tag == "lsn")
return "sgn";
else if (tag == "lso")
return "sgn";
else if (tag == "lsp")
return "sgn";
else if (tag == "lst")
return "sgn";
else if (tag == "lsv")
return "sgn";
else if (tag == "lsy")
return "sgn";
else if (tag == "ltg")
return "lv";
else if (tag == "lvs")
return "lv";
else if (tag == "lws")
return "sgn";
else if (tag == "lzh")
return "zh";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithM(const std::string& tag)
{
if (tag == "max")
return "ms";
else if (tag == "mdl")
return "sgn";
else if (tag == "meo")
return "ms";
else if (tag == "mfa")
return "ms";
else if (tag == "mfb")
return "ms";
else if (tag == "mfs")
return "sgn";
else if (tag == "min")
return "ms";
else if (tag == "mnp")
return "zh";
else if (tag == "mqg")
return "ms";
else if (tag == "mre")
return "sgn";
else if (tag == "msd")
return "sgn";
else if (tag == "msi")
return "ms";
else if (tag == "msr")
return "sgn";
else if (tag == "mui")
return "ms";
else if (tag == "mzc")
return "sgn";
else if (tag == "mzg")
return "sgn";
else if (tag == "mzy")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithN(const std::string& tag)
{
if (tag == "nan")
return "zh";
else if (tag == "nbs")
return "sgn";
else if (tag == "ncs")
return "sgn";
else if (tag == "nsi")
return "sgn";
else if (tag == "nsl")
return "sgn";
else if (tag == "nsp")
return "sgn";
else if (tag == "nsr")
return "sgn";
else if (tag == "nzs")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithO(const std::string& tag)
{
if (tag == "okl")
return "sgn";
else if (tag == "orn")
return "ms";
else if (tag == "ors")
return "ms";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithP(const std::string& tag)
{
if (tag == "pel")
return "ms";
else if (tag == "pga")
return "ar";
else if (tag == "pgz")
return "sgn";
else if (tag == "pks")
return "sgn";
else if (tag == "prl")
return "sgn";
else if (tag == "prz")
return "sgn";
else if (tag == "psc")
return "sgn";
else if (tag == "psd")
return "sgn";
else if (tag == "pse")
return "ms";
else if (tag == "psg")
return "sgn";
else if (tag == "psl")
return "sgn";
else if (tag == "pso")
return "sgn";
else if (tag == "psp")
return "sgn";
else if (tag == "psr")
return "sgn";
else if (tag == "pys")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithR(const std::string& tag)
{
if (tag == "rms")
return "sgn";
else if (tag == "rsi")
return "sgn";
else if (tag == "rsl")
return "sgn";
else if (tag == "rsm")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithS(const std::string& tag)
{
if (tag == "sdl")
return "sgn";
else if (tag == "sfb")
return "sgn";
else if (tag == "sfs")
return "sgn";
else if (tag == "sgg")
return "sgn";
else if (tag == "sgx")
return "sgn";
else if (tag == "shu")
return "ar";
else if (tag == "slf")
return "sgn";
else if (tag == "sls")
return "sgn";
else if (tag == "sqk")
return "sgn";
else if (tag == "sqs")
return "sgn";
else if (tag == "sqx")
return "sgn";
else if (tag == "ssh")
return "ar";
else if (tag == "ssp")
return "sgn";
else if (tag == "ssr")
return "sgn";
else if (tag == "svk")
return "sgn";
else if (tag == "swc")
return "sw";
else if (tag == "swh")
return "sw";
else if (tag == "swl")
return "sgn";
else if (tag == "syy")
return "sgn";
else if (tag == "szs")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithT(const std::string& tag)
{
if (tag == "tmw")
return "ms";
else if (tag == "tse")
return "sgn";
else if (tag == "tsm")
return "sgn";
else if (tag == "tsq")
return "sgn";
else if (tag == "tss")
return "sgn";
else if (tag == "tsy")
return "sgn";
else if (tag == "tza")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithU(const std::string& tag)
{
if (tag == "ugn")
return "sgn";
else if (tag == "ugy")
return "sgn";
else if (tag == "ukl")
return "sgn";
else if (tag == "uks")
return "sgn";
else if (tag == "urk")
return "ms";
else if (tag == "uzn")
return "uz";
else if (tag == "uzs")
return "uz";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithV(const std::string& tag)
{
if (tag == "vgt")
return "sgn";
else if (tag == "vkk")
return "ms";
else if (tag == "vkt")
return "ms";
else if (tag == "vsi")
return "sgn";
else if (tag == "vsl")
return "sgn";
else if (tag == "vsv")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithW(const std::string& tag)
{
if (tag == "wbs")
return "sgn";
else if (tag == "wuu")
return "zh";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithX(const std::string& tag)
{
if (tag == "xki")
return "sgn";
else if (tag == "xml")
return "sgn";
else if (tag == "xmm")
return "ms";
else if (tag == "xms")
return "sgn";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithY(const std::string& tag)
{
if (tag == "yds")
return "sgn";
else if (tag == "ygs")
return "sgn";
else if (tag == "yhs")
return "sgn";
else if (tag == "ysl")
return "sgn";
else if (tag == "ysm")
return "sgn";
else if (tag == "yue")
return "zh";
else
return "";
}
static std::string intlPreferredExtlangTagStartWithZ(const std::string& tag)
{
if (tag == "zib")
return "sgn";
else if (tag == "zlm")
return "ms";
else if (tag == "zmi")
return "ms";
else if (tag == "zsl")
return "sgn";
else if (tag == "zsm")
return "ms";
else
return "";
}
static std::string intlPreferredExtlangTag(const std::string& tag)
{
ASSERT(tag.length() == 3);
switch (tag[0]) {
case 'a':
return intlPreferredExtlangTagStartWithA(tag);
case 'b':
return intlPreferredExtlangTagStartWithB(tag);
case 'c':
return intlPreferredExtlangTagStartWithC(tag);
case 'd':
return intlPreferredExtlangTagStartWithD(tag);
case 'e':
return intlPreferredExtlangTagStartWithE(tag);
case 'f':
return intlPreferredExtlangTagStartWithF(tag);
case 'g':
return intlPreferredExtlangTagStartWithG(tag);
case 'h':
return intlPreferredExtlangTagStartWithH(tag);
case 'i':
return intlPreferredExtlangTagStartWithI(tag);
case 'j':
return intlPreferredExtlangTagStartWithJ(tag);
case 'k':
return intlPreferredExtlangTagStartWithK(tag);
case 'l':
return intlPreferredExtlangTagStartWithL(tag);
case 'm':
return intlPreferredExtlangTagStartWithM(tag);
case 'n':
return intlPreferredExtlangTagStartWithN(tag);
case 'o':
return intlPreferredExtlangTagStartWithO(tag);
case 'p':
return intlPreferredExtlangTagStartWithP(tag);
case 'r':
return intlPreferredExtlangTagStartWithR(tag);
case 's':
return intlPreferredExtlangTagStartWithS(tag);
case 't':
return intlPreferredExtlangTagStartWithT(tag);
case 'u':
return intlPreferredExtlangTagStartWithU(tag);
case 'v':
return intlPreferredExtlangTagStartWithV(tag);
case 'w':
return intlPreferredExtlangTagStartWithW(tag);
case 'x':
return intlPreferredExtlangTagStartWithX(tag);
case 'y':
return intlPreferredExtlangTagStartWithY(tag);
case 'z':
return intlPreferredExtlangTagStartWithZ(tag);
default:
return "";
}
}
static std::string intlPreferredRegionTag(const std::string& tag)
{
// 6 possible replacements
if (tag == "BU")
return "MM";
if (tag == "DD")
return "DE";
if (tag == "FX")
return "FR";
if (tag == "TP")
return "TL";
if (tag == "YD")
return "YE";
if (tag == "ZR")
return "CD";
return "";
}
static std::string preferredRegion(const std::string& region)
{
auto preferred = intlPreferredRegionTag(region);
if (preferred != "") {
return preferred;
}
return region;
}
static std::string privateUseLangTag(const std::vector<std::string>& parts, size_t startIndex)
{
size_t numParts = parts.size();
size_t currentIndex = startIndex;
// Check for privateuse.
// privateuse = "x" 1*("-" (2*8alphanum))
StringBuilder privateuse;
while (currentIndex < numParts) {
std::string singleton = parts[currentIndex];
unsigned singletonLength = singleton.length();
bool isValid = (singletonLength == 1 && (singleton == "x" || singleton == "X"));
if (!isValid)
break;
if (currentIndex != startIndex)
privateuse.appendChar('-');
++currentIndex;
unsigned numExtParts = 0;
privateuse.appendChar('x');
while (currentIndex < numParts) {
std::string extPart = parts[currentIndex];
unsigned extPartLength = extPart.length();
bool isValid = (extPartLength >= 1 && extPartLength <= 8 && isAllSpecialCharacters(extPart, isASCIIAlphanumeric));
if (!isValid)
break;
++currentIndex;
++numExtParts;
privateuse.appendChar('-');
std::transform(extPart.begin(), extPart.end(), extPart.begin(), tolower);
privateuse.appendString(String::fromUTF8(extPart.data(), extPart.length()));
}
// Requires at least one production.
if (!numExtParts)
return std::string();
}
// Leftovers makes it invalid.
if (currentIndex < numParts)
return std::string();
return privateuse.finalize()->toNonGCUTF8StringData();
}
static bool canonicalizeLanguageTagHelper(std::vector<std::string>& parts, const std::string& unicodeExtensionNameShouldIgnored, std::unordered_set<std::string>& subtags, const size_t numParts, size_t& currentIndex, Intl::CanonicalizedLangunageTag& result)
{
// Check for extension.
// extension = singleton 1*("-" (2*8alphanum))
// singleton = alphanum except x or X
subtags.clear();
while (currentIndex < numParts) {
std::string possibleSingleton = parts[currentIndex];
unsigned singletonLength = possibleSingleton.length();
bool isValidSingleton = (singletonLength == 1 && possibleSingleton != "x" && possibleSingleton != "X" && isASCIIAlphanumeric(possibleSingleton[0]));
if (!isValidSingleton) {
break;
}
// Cannot include duplicate singleton (case insensitive).
std::string singleton = possibleSingleton;
std::transform(possibleSingleton.begin(), possibleSingleton.end(), possibleSingleton.begin(), tolower);
auto iter = subtags.find(singleton);
if (iter != subtags.end()) {
return false;
}
subtags.insert(singleton);
Intl::CanonicalizedLangunageTag::ExtensionSingleton singletonValue;
singletonValue.key = possibleSingleton[0];
++currentIndex;
if (singletonValue.key == 'u') {
bool unicodeExtensionIgnored = false;
// "u" needs ordered key, value
// "u" doesn't allow single "true" as value
std::vector<std::pair<std::string, std::vector<std::string>>> values;
std::string key;
std::vector<std::string> value;
while (currentIndex < numParts) {
std::string extPart = parts[currentIndex];
unsigned extPartLength = extPart.length();
bool isValid = (extPartLength >= 2 && extPartLength <= 8 && isAllSpecialCharacters(extPart, isASCIIAlphanumeric));
if (!isValid) {
break;
}
++currentIndex;
std::transform(extPart.begin(), extPart.end(), extPart.begin(), tolower);
if (extPartLength == 2) {
if (key.length() || value.size()) {
if (key.length() && value.size() == 1 && value[0] == "true") {
value.clear();
}
values.push_back(std::make_pair(key, value));
key.clear();
value.clear();
}
key = extPart;
} else {
value.push_back(extPart);
}
}
bool unicodeExtensionIgnoredOnce = (key.length() && key == unicodeExtensionNameShouldIgnored);
unicodeExtensionIgnored |= unicodeExtensionIgnoredOnce;
if (!unicodeExtensionIgnoredOnce && (key.length() || value.size())) {
if (key.length() && value.size() == 1 && value[0] == "true") {
value.clear();
}
values.push_back(std::make_pair(key, value));
key.clear();
value.clear();
}
std::sort(values.begin(), values.end(),
[](const std::pair<std::string, std::vector<std::string>>& a, const std::pair<std::string, std::vector<std::string>>& b) -> bool {
return a.first < b.first;
});
// Attributes in Unicode extensions are reordered in US-ASCII order.
if (values.size() && !values.begin()->first.length()) {
std::sort(values.begin()->second.begin(), values.begin()->second.end());
}
std::string resultValue;
for (size_t i = 0; i < values.size(); i++) {
if (values[i].first.size()) {
if (resultValue.size()) {
resultValue += '-';
}
resultValue += values[i].first;
}
for (size_t j = 0; j < values[i].second.size(); j++) {
if (resultValue.size()) {
resultValue += '-';
}
resultValue += values[i].second[j];
}
}
singletonValue.value = std::move(resultValue);
std::vector<std::pair<std::string, std::string>> unicodeExtensionValue;
for (size_t i = 0; i < values.size(); i++) {
std::string singleUnicodeExtensionValue;
for (size_t j = 0; j < values[i].second.size(); j++) {
if (singleUnicodeExtensionValue.length()) {
singleUnicodeExtensionValue += '-';
}
singleUnicodeExtensionValue += values[i].second[j];
}
unicodeExtensionValue.push_back(std::make_pair(values[i].first, singleUnicodeExtensionValue));
}
result.unicodeExtension = std::move(unicodeExtensionValue);
if (unicodeExtensionIgnored && !result.unicodeExtension.size()) {
continue;
}
} else {
std::string single;
while (currentIndex < numParts) {
std::string extPart = parts[currentIndex];
unsigned extPartLength = extPart.length();
bool isValid = (extPartLength >= 2 && extPartLength <= 8 && isAllSpecialCharacters(extPart, isASCIIAlphanumeric));
if (!isValid) {
break;
}
++currentIndex;
std::transform(extPart.begin(), extPart.end(), extPart.begin(), tolower);
if (single.length()) {
single += '-';
}
single += extPart;
}
singletonValue.value = std::move(single);
}
result.extensions.push_back(singletonValue);
// Requires at least one production.
if (!result.extensions.back().value.size()) {
return false;
}
}
return true;
}
Intl::CanonicalizedLangunageTag Intl::canonicalizeLanguageTag(const std::string& locale, const std::string& unicodeExtensionNameShouldIgnored)
{
std::vector<std::string> parts = split(locale, '-');
Intl::CanonicalizedLangunageTag result;
// Follows the grammar at https://www.rfc-editor.org/rfc/bcp/bcp47.txt
// langtag = language ["-" script] ["-" region] *("-" variant) *("-" extension) ["-" privateuse]
size_t numParts = parts.size();
// Check for language.
// language = 2*3ALPHA ["-" extlang] / 4ALPHA (reserved) / 5*8ALPHA
size_t currentIndex = 0;
std::string language = parts[currentIndex];
unsigned languageLength = language.length();
bool canHaveExtlang = languageLength >= 2 && languageLength <= 3;
bool isValidLanguage = languageLength != 4 && languageLength >= 2 && languageLength <= 8 && isAllSpecialCharacters(language, isASCIIAlpha);
if (!isValidLanguage) {
return Intl::CanonicalizedLangunageTag();
}
++currentIndex;
StringBuilder canonical;
std::transform(language.begin(), language.end(), language.begin(), tolower);
result.language = language;
language = Intl::preferredLanguage(language);
// test legacy cases
// https://github.com/unicode-org/cldr/blob/master/common/supplemental/supplementalMetadata.xml
if (result.language == "sh") {
language = result.language = "sr";
if (!result.script.length()) {
result.script = "Latn";
}
} else if (result.language == "cnr") {
language = result.language = "sr";
if (!result.region.length()) {
result.region = "ME";
}
}
canonical.appendString(String::fromUTF8(language.data(), language.length()));
// Check for extlang.
// extlang = 3ALPHA *2("-" 3ALPHA)
if (canHaveExtlang) {
for (unsigned times = 0; times < 3 && currentIndex < numParts; ++times) {
std::string extlang = parts[currentIndex];
unsigned extlangLength = extlang.length();
if (extlangLength == 3 && isAllSpecialCharacters(extlang, isASCIIAlpha)) {
++currentIndex;
std::transform(extlang.begin(), extlang.end(), extlang.begin(), tolower);
result.extLang.push_back(extlang);
if (!times && intlPreferredExtlangTag(extlang) == language) {
canonical.clear();
canonical.appendString(String::fromUTF8(extlang.data(), extlang.length()));
continue;
}
canonical.appendString("-");
canonical.appendString(String::fromUTF8(extlang.data(), extlang.length()));
} else {
break;
}
}
}
// Check for script.
// script = 4ALPHA
bool isScriptAddToCanonical = false;
if (currentIndex < numParts) {
std::string script = parts[currentIndex];
unsigned scriptLength = script.length();
if (scriptLength == 4 && isAllSpecialCharacters(script, isASCIIAlpha)) {
++currentIndex;
canonical.appendString("-");
std::transform(script.begin(), script.end(), script.begin(), tolower);
result.script += (char)toupper(script[0]);
canonical.appendChar((char)toupper(script[0]));
script = script.substr(1, 3);
result.script += script;
canonical.appendString(String::fromUTF8(script.data(), script.length()));
isScriptAddToCanonical = true;
}
}
if (!isScriptAddToCanonical && result.script.length()) {
canonical.appendString("-");
canonical.appendString(String::fromUTF8(result.script.data(), result.script.length()));
}
// Check for region.
// region = 2ALPHA / 3DIGIT
bool isRegionAddToCanonical = false;
if (currentIndex < numParts) {
std::string region = parts[currentIndex];
unsigned regionLength = region.length();
bool isValidRegion = ((regionLength == 2 && isAllSpecialCharacters(region, isASCIIAlpha))
|| (regionLength == 3 && isAllSpecialCharacters(region, isASCIIDigit)));
if (isValidRegion) {
++currentIndex;
canonical.appendChar('-');
std::transform(region.begin(), region.end(), region.begin(), toupper);
result.region = region;
auto preffered = preferredRegion(region);
canonical.appendString(String::fromUTF8(preffered.data(), preffered.length()));
isRegionAddToCanonical = true;
}
}
if (!isRegionAddToCanonical && result.region.length()) {
canonical.appendString("-");
canonical.appendString(String::fromUTF8(result.region.data(), result.region.length()));
}
// Check for variant.
// variant = 5*8alphanum / (DIGIT 3alphanum)
std::unordered_set<std::string> subtags;
while (currentIndex < numParts) {
std::string variant = parts[currentIndex];
unsigned variantLength = variant.length();
bool isValidVariant = ((variantLength >= 5 && variantLength <= 8 && isAllSpecialCharacters(variant, isASCIIAlphanumeric))
|| (variantLength == 4 && isASCIIDigit(variant[0]) && isAllSpecialCharacters(variant.substr(1, 3), isASCIIAlphanumeric)));
if (!isValidVariant) {
break;
}
// Cannot include duplicate subtags (case insensitive).
std::string lowerVariant = variant;
std::transform(lowerVariant.begin(), lowerVariant.end(), lowerVariant.begin(), tolower);
auto iter = subtags.find(lowerVariant);
if (iter != subtags.end()) {
return Intl::CanonicalizedLangunageTag();
}
subtags.insert(lowerVariant);
++currentIndex;
result.variant.push_back(lowerVariant);
}
std::sort(result.variant.begin(), result.variant.end());
for (size_t i = 0; i < result.variant.size(); i++) {
canonical.appendChar('-');
canonical.appendString(result.variant[i].data(), result.variant[i].length());
}
// Check for extension.
// extension = singleton 1*("-" (2*8alphanum))
// singleton = alphanum except x or X
if (!canonicalizeLanguageTagHelper(parts, unicodeExtensionNameShouldIgnored, subtags, numParts, currentIndex, result)) {
return Intl::CanonicalizedLangunageTag();
}
// Add extensions to canonical sorted by singleton.
std::sort(result.extensions.begin(), result.extensions.end(),
[](const Intl::CanonicalizedLangunageTag::ExtensionSingleton& a, const Intl::CanonicalizedLangunageTag::ExtensionSingleton& b) -> bool {
return a.key < b.key;
});
size_t numExtenstions = result.extensions.size();
for (size_t i = 0; i < numExtenstions; ++i) {
canonical.appendChar('-');
canonical.appendChar(result.extensions[i].key);
canonical.appendChar('-');
canonical.appendString(String::fromUTF8(result.extensions[i].value.data(), result.extensions[i].value.length()));
}
// Check for privateuse.
if (currentIndex < numParts) {
std::string privateUse = privateUseLangTag(parts, currentIndex);
if (privateUse.length() == 0) {
return Intl::CanonicalizedLangunageTag();
}
canonical.appendChar('-');
canonical.appendString(String::fromUTF8(privateUse.data(), privateUse.length()));
result.privateUse = privateUse;
}
String* e = canonical.finalize();
auto estd = e->toNonGCUTF8StringData();
auto preferred = intlRedundantLanguageTag(estd);
if (preferred != "") {
e = String::fromUTF8(preferred.data(), preferred.length());
}
result.canonicalizedTag = e;
return result;
}
Intl::CanonicalizedLangunageTag Intl::isStructurallyValidLanguageTagAndCanonicalizeLanguageTag(const std::string& locale)
{
if (isValidTagInBCP47ButInvalidOnUTS35(locale)) {
return Intl::CanonicalizedLangunageTag();
}
std::string grandfather = grandfatheredLangTag(locale);
if (grandfather.length()) {
return canonicalizeLanguageTag(grandfather);
}
return canonicalizeLanguageTag(locale);
}
String* Intl::icuLocaleToBCP47Tag(String* string)
{
StringBuilder sb;
for (size_t i = 0; i < string->length(); i++) {
char16_t ch = string->charAt(i);
if (ch == '_')
ch = '-';
sb.appendChar(ch);
}
return sb.finalize();
}
std::string Intl::convertICUCalendarKeywordToBCP47KeywordIfNeeds(const std::string& icuCalendar)
{
if (icuCalendar == "gregorian") {
return "gregory";
} else if (icuCalendar == "ethiopic-amete-alem") {
return "ethioaa";
}
return icuCalendar;
}
std::string Intl::convertBCP47KeywordToICUCalendarKeywordIfNeeds(const std::string& keyword)
{
if (keyword == "gregory") {
return "gregorian";
} else if (keyword == "ethioaa") {
return "ethiopic-amete-alem";
}
return keyword;
}
std::string Intl::convertICUCollationKeywordToBCP47KeywordIfNeeds(const std::string& icuCollation)
{
if (icuCollation == "dictionary") {
return "dict";
} else if (icuCollation == "gb2312han") {
return "gb2312";
} else if (icuCollation == "phonebook") {
return "phonebk";
} else if (icuCollation == "traditional") {
return "trad";
}
return icuCollation;
}
static String* defaultLocale(ExecutionState& state)
{
String* localeString = String::fromUTF8(state.context()->vmInstance()->locale().data(), state.context()->vmInstance()->locale().length());
return Intl::icuLocaleToBCP47Tag(localeString);
}
static String* removeUnicodeLocaleExtension(ExecutionState& state, String* locale)
{
auto utf8 = locale->toUTF8StringData();
std::string stdUTF8(utf8.data(), utf8.length());
std::vector<std::string> parts = split(stdUTF8, '-');
StringBuilder builder;
size_t partsSize = parts.size();
if (partsSize > 0)
builder.appendString(String::fromUTF8(parts[0].data(), parts[0].size()));
for (size_t p = 1; p < partsSize; ++p) {
if (parts[p] == "u") {
// Skip the u- and anything that follows until another singleton.
// While the next part is part of the unicode extension, skip it.
while (p + 1 < partsSize && parts[p + 1].length() > 1)
++p;
} else {
builder.appendChar('-');
builder.appendString(String::fromUTF8(parts[p].data(), parts[p].size()));
}
}
return builder.finalize();
}
ValueVector Intl::canonicalizeLocaleList(ExecutionState& state, Value locales)
{
// http://www.ecma-international.org/ecma-402/1.0/index.html#sec-9.2.1
// If locales is undefined, then
if (locales.isUndefined()) {
// Return a new empty List.
return ValueVector();
}
// Let seen be a new empty List.
ValueVector seen;
Object* O;
// If Type(locales) is String or locales has an [[InitializedLocale]] internal slot, then
if (locales.isString() || (locales.isObject() && locales.asObject()->isIntlLocaleObject())) {
// Let O be CreateArrayFromList(« locales »).
ValueVector vv;
vv.push_back(locales);
O = Object::createArrayFromList(state, vv);
} else {
// Else
// Let O be ToObject(locales).
O = locales.toObject(state);
}
uint64_t len = O->length(state);
// Let k be 0.
// Repeat, while k < len
uint64_t k = 0;
while (k < len) {
// Let Pk be ToString(k).
ObjectPropertyName pk(state, Value(k));
ObjectHasPropertyResult pkResult = O->hasProperty(state, pk);
// Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
// If kPresent is true, then
if (pkResult.hasProperty()) {
// Let kValue be the result of calling the [[Get]] internal method of O with argument Pk.
Value kValue = pkResult.value(state, pk, O);
// If the type of kValue is not String or Object, then throw a TypeError exception.
if (!kValue.isString() && !kValue.isObject()) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Type of element of locales must be String or Object");
}
String* tag;
// If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then
if (kValue.isObject() && kValue.asObject()->isIntlLocaleObject()) {
// Let tag be kValue.[[Locale]].
tag = kValue.asObject()->asIntlLocaleObject()->locale();
} else {
// Else,
// Let tag be ToString(kValue).
tag = kValue.toString(state);
}
// If the result of calling the abstract operation IsStructurallyValidLanguageTag (defined in 6.2.2), passing tag as the argument,
// is false, then throw a RangeError exception.
// Let tag be the result of calling the abstract operation CanonicalizeLanguageTag (defined in 6.2.3), passing tag as the argument.
// If tag is not an element of seen, then append tag as the last element of seen.
auto canonicalizedTag = Intl::isStructurallyValidLanguageTagAndCanonicalizeLanguageTag(tag->toNonGCUTF8StringData());
if (!canonicalizedTag.canonicalizedTag) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "got Invalid locale");
}
tag = canonicalizedTag.canonicalizedTag.value();
bool has = false;
for (size_t i = 0; i < seen.size(); i++) {
if (seen[i].equalsTo(state, tag)) {
has = true;
break;
}
}
if (!has) {
seen.pushBack(tag);
}
// Increase k by 1.
k++;
} else {
// Increase k by 1.
int64_t nextIndex;
Object::nextIndexForward(state, O, k, len, nextIndex);
k = nextIndex;
}
}
// Return seen.
return seen;
}
static String* bestAvailableLocale(ExecutionState& state, const Vector<String*, GCUtil::gc_malloc_allocator<String*>>& availableLocales, Value locale)
{
// http://www.ecma-international.org/ecma-402/1.0/index.html#sec-9.2.2
// Let candidate be locale.
String* candidate = locale.toString(state);
// Repeat
while (candidate->length()) {
// If availableLocales contains an element equal to candidate, then return candidate.
bool contains = false;
for (size_t i = 0; i < availableLocales.size(); i++) {
if (availableLocales[i]->equals(candidate)) {
contains = true;
break;
}
}
if (contains)
return candidate;
// Let pos be the character index of the last occurrence of "-" (U+002D) within candidate. If that character does not occur, return undefined.
size_t pos = candidate->rfind(state.context()->staticStrings().asciiTable[(size_t)'-'].string(), candidate->length() - 1);
if (pos == SIZE_MAX)
return String::emptyString();
// If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, then decrease pos by 2.
if (pos >= 2 && candidate->charAt(pos - 2) == '-')
pos -= 2;
// d. Let candidate be the substring of candidate from position 0, inclusive, to position pos, exclusive.
candidate = candidate->substring(0, pos);
}
return String::emptyString();
}
static Intl::IntlMatcherResult lookupMatcher(ExecutionState& state, const Vector<String*, GCUtil::gc_malloc_allocator<String*>> availableLocales, const ValueVector& requestedLocales)
{
// http://www.ecma-international.org/ecma-402/1.0/index.html#sec-9.2.3
// Let i be 0.
size_t i = 0;
// Let len be the number of elements in requestedLocales.
size_t len = requestedLocales.size();
// Let availableLocale be undefined.
String* availableLocale = String::emptyString();
String* locale = String::emptyString();
String* noExtensionsLocale = String::emptyString();
// Repeat while i < len and availableLocale is undefined:
while (i < len && availableLocale->length() == 0) {
// Let locale be the element of requestedLocales at 0-origined list position i.
locale = requestedLocales[i].toString(state);
// Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences removed.
noExtensionsLocale = removeUnicodeLocaleExtension(state, locale);
// Let availableLocale be the result of calling the BestAvailableLocale abstract operation (defined in 9.2.2) with arguments availableLocales and noExtensionsLocale.
availableLocale = bestAvailableLocale(state, availableLocales, noExtensionsLocale);
// Increase i by 1.
i++;
}
// Let result be a new Record.
Intl::IntlMatcherResult result;
// If availableLocale is not undefined, then
if (availableLocale->length()) {
// Set result.[[locale]] to availableLocale.
result.locale = availableLocale;
// If locale and noExtensionsLocale are not the same String value, then
if (!locale->equals(noExtensionsLocale)) {
// Let extension be the String value consisting of the first substring of locale that is a Unicode locale extension sequence.
// Let extensionIndex be the character position of the initial "-" of the first Unicode locale extension sequence within locale.
// Set result.[[extension]] to extension.
// Set result.[[extensionIndex]] to extensionIndex.
bool unicodeExtensionExists = false;
if (locale->find("-x-") != SIZE_MAX) {
unicodeExtensionExists = true;
}
if (!unicodeExtensionExists) {
size_t extensionIndex = locale->find("-u-");
ASSERT(extensionIndex != SIZE_MAX);
size_t extensionLength = locale->length() - extensionIndex;
size_t end = extensionIndex + 3;
while (end < locale->length()) {
end = locale->find("-", end);
if (end == SIZE_MAX)
break;
if (end + 2 < locale->length() && locale->charAt(end + 2) == '-') {
extensionLength = end - extensionIndex;
break;
}
end++;
}
result.extension = locale->substring(extensionIndex, extensionIndex + extensionLength);
result.extensionIndex = extensionIndex;
}
}
} else {
// Set result.[[locale]] to the value returned by the DefaultLocale abstract operation (defined in 6.2.4).
result.locale = defaultLocale(state);
}
return result;
}
static Intl::IntlMatcherResult bestFitMatcher(ExecutionState& state, const Vector<String*, GCUtil::gc_malloc_allocator<String*>> availableLocales, const ValueVector& requestedLocales)
{
// TODO
return lookupMatcher(state, availableLocales, requestedLocales);
}
static Optional<String*> unicodeExtensionValue(ExecutionState& state, String* extension, const char* key)
{
// Let size be the number of elements in extension.
auto size = extension->length();
// Let searchValue be the concatenation of "-", key, and "-".
StringBuilder searchValueBuilder;
searchValueBuilder.appendChar('-');
searchValueBuilder.appendString(key, 2);
searchValueBuilder.appendChar('-');
String* searchValue = searchValueBuilder.finalize();
// Let pos be Call(%StringProto_indexOf%, extension, « searchValue »).
size_t pos = extension->find(searchValue);
// If pos ≠ -1, then
if (pos != SIZE_MAX) {
// Let start be pos + 4.
size_t start = pos + 4;
// Let end be start.
size_t end = start;
// Let k be start.
size_t k = start;
// Let done be false.
bool done = false;
// Repeat, while done is false
while (!done) {
// Let e be Call(%StringProto_indexOf%, extension, « "-", k »).
size_t e = extension->find("-", k);
size_t len;
// If e = -1,
if (e == SIZE_MAX) {
// let len be size - k;
len = size - k;
} else {
// else let len be e - k.
len = e - k;
}
// If len = 2, then
if (len == 2) {
// Let done be true.
done = true;
} else if (e == SIZE_MAX) {
// Else if e = -1, then
// Let end be size.
end = size;
// Let done be true.
done = true;
} else {
// Else,
// Let end be e.
end = e;
// Let k be e + 1.
k = e + 1;
}
}
// Return the String value equal to the substring of extension
// consisting of the code units at indices start (inclusive) through end (exclusive).
return extension->substring(start, end);
}
// Let searchValue be the concatenation of "-" and key.
searchValueBuilder.appendChar('-');
searchValueBuilder.appendString(key, 2);
searchValue = searchValueBuilder.finalize();
// Let pos be Call(%StringProto_indexOf%, extension, « searchValue »).
pos = extension->find(searchValue);
// If pos ≠ -1 and pos + 3 = size, then
if (pos != SIZE_MAX && pos + 3 == size) {
return String::emptyString();
}
return nullptr;
}
static bool stringVectorContains(const std::vector<std::string>& v, const std::string& key)
{
for (size_t j = 0; j < v.size(); j++) {
if (v[j] == key) {
return true;
}
}
return false;
}
StringMap Intl::resolveLocale(ExecutionState& state, const Vector<String*, GCUtil::gc_malloc_allocator<String*>>& availableLocales, const ValueVector& requestedLocales, const StringMap& options, const char* const relevantExtensionKeys[], size_t relevantExtensionKeyCount, LocaleDataImplFunction localeData)
{
// https://www.ecma-international.org/ecma-402/6.0/index.html#sec-resolvelocale
// Let matcher be the value of options.[[localeMatcher]].
auto iter = options.find("localeMatcher");
Value matcher;
if (iter != options.end()) {
matcher = iter->second;
}
Intl::IntlMatcherResult r;
// If matcher is "lookup", then
if (matcher.equalsTo(state, state.context()->staticStrings().lazyLookup().string())) {
// Let r be LookupMatcher(availableLocales, requestedLocales).
r = lookupMatcher(state, availableLocales, requestedLocales);
} else {
// Else
// Let r be BestFitMatcher(availableLocales, requestedLocales).
r = bestFitMatcher(state, availableLocales, requestedLocales);
}
// Let foundLocale be the value of r.[[locale]].
String* foundLocale = r.locale;
// Let result be a new Record.
StringMap result;
// Set result.[[dataLocale]] to foundLocale.
result.insert(std::make_pair("dataLocale", foundLocale));
// Let supportedExtension be "-u".
String* supportedExtension = state.context()->staticStrings().lazyDashU().string();
// For each element key of relevantExtensionKeys in List order, do
size_t i = 0;
size_t len = relevantExtensionKeyCount;
while (i < len) {
const char* key = relevantExtensionKeys[i];
// Let foundLocaleData be localeData.[[<foundLocale>]].
// Let keyLocaleData be foundLocaleData.[[<key>]].
std::vector<std::string> keyLocaleData = localeData(foundLocale, i);
// Let value be keyLocaleData[0].
std::string value = keyLocaleData[0];
// Let supportedExtensionAddition be "".
String* supportedExtensionAddition = String::emptyString();
// If r has an [[extension]] field, then
if (r.extension->length()) {
// Let requestedValue be UnicodeExtensionValue(r.[[extension]], key).
Optional<String*> requestedValue = unicodeExtensionValue(state, r.extension, key);
// If requestedValue is not undefined, then
if (requestedValue) {
// If requestedValue is not the empty String, then
if (requestedValue.value()->length()) {
// If keyLocaleData contains requestedValue, then
if (stringVectorContains(keyLocaleData, requestedValue.value()->toNonGCUTF8StringData())) {
// Let value be requestedValue.
value = requestedValue.value()->toNonGCUTF8StringData();
// Let supportedExtensionAddition be the concatenation of "-", key, "-", and value.
StringBuilder supportedExtensionAdditionBuilder;
supportedExtensionAdditionBuilder.appendChar('-');
supportedExtensionAdditionBuilder.appendString(key, 2);
supportedExtensionAdditionBuilder.appendChar('-');
supportedExtensionAdditionBuilder.appendString(value.data(), value.length());
supportedExtensionAddition = supportedExtensionAdditionBuilder.finalize();
}
} else if (stringVectorContains(keyLocaleData, "true")) {
// Else if keyLocaleData contains "true", then
// Let value be "true".
value = "true";
// Let supportedExtensionAddition be the concatenation of "-" and key.
StringBuilder supportedExtensionAdditionBuilder;
supportedExtensionAdditionBuilder.appendChar('-');
supportedExtensionAdditionBuilder.appendString(key, 2);
supportedExtensionAddition = supportedExtensionAdditionBuilder.finalize();
}
}
}
// If options has a field [[<key>]], then
if (options.find(key) != options.end()) {
// Let optionsValue be options.[[<key>]].
auto optionsValue = options.at(key);
// Assert: Type(optionsValue) is either String, Undefined, or Null.
// If keyLocaleData contains optionsValue, then
if (stringVectorContains(keyLocaleData, optionsValue->toNonGCUTF8StringData())) {
// If SameValue(optionsValue, value) is false, then
if (!optionsValue->equals(value.data(), value.length())) {
// Let value be optionsValue.
value = optionsValue->toNonGCUTF8StringData();
// Let supportedExtensionAddition be "".
supportedExtensionAddition = String::emptyString();
}
}
}
// Set result.[[<key>]] to value.
result.insert(std::make_pair(key, String::fromUTF8(value.data(), value.length())));
// Append supportedExtensionAddition to supportedExtension.
StringBuilder sb;
sb.appendString(supportedExtension);
sb.appendString(supportedExtensionAddition);
supportedExtension = sb.finalize();
i++;
}
// If the length of supportedExtension is greater than 2, then
if (supportedExtension->length() > 2) {
// Let privateIndex be Call(%StringProto_indexOf%, foundLocale, « "-x-" »).
size_t privateIndex = foundLocale->find("-x-");
// If privateIndex = -1, then
if (privateIndex != SIZE_MAX) {
// Let foundLocale be the concatenation of foundLocale and supportedExtension.
StringBuilder sb;
sb.appendString(foundLocale);
sb.appendString(supportedExtension);
foundLocale = sb.finalize();
} else {
size_t len = foundLocale->length();
// Let preExtension be the substring of foundLocale from position 0, inclusive, to position extensionIndex, exclusive.
String* preExtension = foundLocale->substring(0, len > r.extensionIndex ? r.extensionIndex : len);
// Let postExtension be the substring of foundLocale from position extensionIndex to the end of the string.
String* postExtension = String::emptyString();
if (r.extensionIndex < len) {
postExtension = foundLocale->substring(r.extensionIndex, len);
}
// Let foundLocale be the concatenation of preExtension, supportedExtension, and postExtension.
StringBuilder sb;
sb.appendString(preExtension);
sb.appendString(supportedExtension);
sb.appendString(postExtension);
foundLocale = sb.finalize();
}
// Let foundLocale be CanonicalizeLanguageTag(foundLocale).
// Assert: IsStructurallyValidLanguageTag(foundLocale) is true.
foundLocale = Intl::isStructurallyValidLanguageTagAndCanonicalizeLanguageTag(foundLocale->toNonGCUTF8StringData()).canonicalizedTag.value();
}
// Set result.[[locale]] to foundLocale.
result.insert(std::make_pair("locale", foundLocale));
// Return result.
return result;
}
static ValueVector lookupSupportedLocales(ExecutionState& state, const Vector<String*, GCUtil::gc_malloc_allocator<String*>>& availableLocales, const ValueVector& requestedLocales)
{
// http://www.ecma-international.org/ecma-402/1.0/index.html#sec-9.2.6
// Let len be the number of elements in requestedLocales.
size_t len = requestedLocales.size();
// Let subset be a new empty List.
ValueVector subset;
// Let k be 0.
size_t k = 0;
// Repeat while k < len
while (k < len) {
// Let locale be the element of requestedLocales at 0-origined list position k.
Value locale = requestedLocales[k];
// Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences removed.
String* noExtensionsLocale = removeUnicodeLocaleExtension(state, locale.toString(state));
// Let availableLocale be the result of calling the BestAvailableLocale abstract operation (defined in 9.2.2) with arguments availableLocales and noExtensionsLocale.
String* availableLocale = bestAvailableLocale(state, availableLocales, noExtensionsLocale);
// If availableLocale is not undefined, then append locale to the end of subset.
if (availableLocale->length()) {
subset.pushBack(locale);
}
// Increment k by 1.
k++;
}
return subset;
}
static ValueVector bestfitSupportedLocales(ExecutionState& state, const Vector<String*, GCUtil::gc_malloc_allocator<String*>>& availableLocales, const ValueVector& requestedLocales)
{
// http://www.ecma-international.org/ecma-402/1.0/index.html#sec-9.2.7
// TODO
return lookupSupportedLocales(state, availableLocales, requestedLocales);
}
Value Intl::supportedLocales(ExecutionState& state, const Vector<String*, GCUtil::gc_malloc_allocator<String*>>& availableLocales, const ValueVector& requestedLocales, Value options)
{
// http://www.ecma-international.org/ecma-402/1.0/index.html#sec-9.2.8
Value matcher;
// If options is not undefined, then
if (!options.isUndefined()) {
// Let options be ToObject(options).
options = options.toObject(state);
// Let matcher be the result of calling the [[Get]] internal method of options with argument "localeMatcher".
matcher = options.asObject()->get(state, ObjectPropertyName(state.context()->staticStrings().lazyLocaleMatcher())).value(state, options.asObject());
// If matcher is not undefined, then
if (!matcher.isUndefined()) {
// Let matcher be ToString(matcher).
matcher = matcher.toString(state);
// If matcher is not "lookup" or "best fit", then throw a RangeError exception.
if (!(matcher.asString()->equals("lookup") || matcher.asString()->equals("best fit"))) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "got invalid value on options.localeMatcher");
}
}
}
// If matcher is undefined or "best fit", then
ValueVector subset;
if (matcher.isUndefined() || matcher.equalsTo(state, state.context()->staticStrings().lazyBestFit().string())) {
// Let subset be the result of calling the BestFitSupportedLocales abstract operation (defined in 9.2.7) with arguments availableLocales and requestedLocales.
subset = bestfitSupportedLocales(state, availableLocales, requestedLocales);
} else {
// Let subset be the result of calling the LookupSupportedLocales abstract operation (defined in 9.2.6) with arguments availableLocales and requestedLocales.
subset = lookupSupportedLocales(state, availableLocales, requestedLocales);
}
// Return CreateArrayFromList(supportedLocales).
return Object::createArrayFromList(state, subset);
}
Value Intl::getOption(ExecutionState& state, Object* options, Value property, Intl::OptionValueType type, Value* values, size_t valuesLength, const Value& fallback)
{
// http://www.ecma-international.org/ecma-402/1.0/index.html#sec-9.2.9
// Let value be the result of calling the [[Get]] internal method of options with argument property.
Value value = options->get(state, ObjectPropertyName(state, property)).value(state, options);
// If value is not undefined, then
if (!value.isUndefined()) {
// Assert: type is "boolean" or "string".
// If type is "boolean", then let value be ToBoolean(value).
if (type == Intl::OptionValueType::BooleanValue) {
value = Value(value.toBoolean());
}
// If type is "string", then let value be ToString(value).
if (type == Intl::OptionValueType::StringValue) {
value = Value(value.toString(state));
}
// If values is not undefined, then
if (valuesLength) {
// If values does not contain an element equal to value, then throw a RangeError exception.
bool contains = false;
for (size_t i = 0; i < valuesLength; i++) {
if (values[i].equalsTo(state, value)) {
contains = true;
}
}
if (!contains) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "got invalid value");
}
}
// Return value.
return value;
} else {
// Else return fallback.
return fallback;
}
}
template <typename T>
T getNumberOptionResultFactory(double value)
{
return T(value);
}
template <>
Value getNumberOptionResultFactory(double value)
{
return Value(Value::DoubleToIntConvertibleTestNeeds, value);
}
template <typename T>
T Intl::getNumberOption(ExecutionState& state, Optional<Object*> options, String* property, double minimum, double maximum, const T& fallback)
{
// http://www.ecma-international.org/ecma-402/1.0/index.html#sec-9.2.10
if (!options) {
return fallback;
}
// Let value be the result of calling the [[Get]] internal method of options with argument property.
Value value = options->get(state, ObjectPropertyName(state, property)).value(state, options.value());
// If value is not undefined, then
if (!value.isUndefined()) {
// Let value be ToNumber(value).
double doubleValue = value.toNumber(state);
// If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
if (std::isnan(doubleValue) || doubleValue < minimum || maximum < doubleValue) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Got invalid number option value");
}
// Return floor(value).
return getNumberOptionResultFactory<T>(floor(doubleValue));
} else {
// Else return fallback.
return fallback;
}
}
template Value Intl::getNumberOption(ExecutionState& state, Optional<Object*> options, String* property, double minimum, double maximum, const Value& fallback);
template double Intl::getNumberOption(ExecutionState& state, Optional<Object*> options, String* property, double minimum, double maximum, const double& fallback);
template int Intl::getNumberOption(ExecutionState& state, Optional<Object*> options, String* property, double minimum, double maximum, const int& fallback);
static std::vector<std::string> initAvailableNumberingSystems()
{
std::vector<std::string> availableNumberingSystems;
UErrorCode status = U_ZERO_ERROR;
UEnumeration* numberingSystemNames = unumsys_openAvailableNames(&status);
ASSERT(U_SUCCESS(status));
int32_t resultLength;
// Numbering system names are always ASCII, so use char[].
while (const char* result = uenum_next(numberingSystemNames, &resultLength, &status)) {
ASSERT(U_SUCCESS(status));
auto numsys = unumsys_openByName(result, &status);
ASSERT(U_SUCCESS(status));
if (!unumsys_isAlgorithmic(numsys)) {
availableNumberingSystems.push_back(std::string(result, resultLength));
}
unumsys_close(numsys);
}
uenum_close(numberingSystemNames);
return availableNumberingSystems;
}
std::vector<std::string> Intl::numberingSystemsForLocale(String* locale)
{
static std::vector<std::string> availableNumberingSystems = initAvailableNumberingSystems();
UErrorCode status = U_ZERO_ERROR;
UNumberingSystem* defaultSystem = unumsys_open(locale->toUTF8StringData().data(), &status);
ASSERT(U_SUCCESS(status));
std::string defaultSystemName(unumsys_getName(defaultSystem));
unumsys_close(defaultSystem);
std::vector<std::string> numberingSystems = availableNumberingSystems;
auto iter = std::find(numberingSystems.begin(), numberingSystems.end(), defaultSystemName);
if (iter != numberingSystems.end()) {
numberingSystems.erase(iter);
}
numberingSystems.insert(numberingSystems.begin(), defaultSystemName);
return numberingSystems;
}
String* Intl::getLocaleForStringLocaleConvertCase(ExecutionState& state, Value locales)
{
// Let requestedLocales be ? CanonicalizeLocaleList(locales).
ValueVector requestedLocales = Intl::canonicalizeLocaleList(state, locales);
// If requestedLocales is not an empty List, then
// a. Let requestedLocale be requestedLocales[0].
// Else Let requestedLocale be DefaultLocale().
String* requestedLocale = requestedLocales.size() > 0 ? requestedLocales[0].toString(state) : defaultLocale(state);
// Let noExtensionsLocale be the String value that is requestedLocale with all Unicode locale extension sequences (6.2.1) removed.
String* noExtensionsLocale = removeUnicodeLocaleExtension(state, requestedLocale);
// Let availableLocales be a List with language tags that includes the languages for which the Unicode Character Database contains language sensitive case mappings. Implementations may add additional language tags if they support case mapping for additional locales.
const auto& availableLocales = state.context()->vmInstance()->caseMappingAvailableLocales();
// Let locale be BestAvailableLocale(availableLocales, noExtensionsLocale).
String* locale = bestAvailableLocale(state, availableLocales, noExtensionsLocale);
return locale;
}
static bool is38Alphanum(const std::string& str)
{
if (str.length() >= 3 && str.length() <= 8) {
return isAllSpecialCharacters(str, isASCIIAlphanumeric);
}
return false;
}
static bool is38AlphanumList(const std::string& str, bool allowUnderScore = false)
{
std::size_t found = str.find("-");
if (found == std::string::npos) {
if (allowUnderScore) {
found = str.find("_");
if (found != std::string::npos) {
return is38Alphanum(str.substr(0, found)) && is38AlphanumList(str.substr(found + 1), allowUnderScore);
}
}
return is38Alphanum(str);
}
return is38Alphanum(str.substr(0, found)) && is38AlphanumList(str.substr(found + 1), allowUnderScore);
}
bool Intl::isValidUnicodeLocaleIdentifierTypeNonterminalOrTypeSequence(String* value)
{
return is38AlphanumList(value->toNonGCUTF8StringData());
}
bool Intl::isValidUnicodeLocaleIdentifier(String* value)
{
return is38AlphanumList(value->toNonGCUTF8StringData(), true);
}
void Intl::convertICUNumberFieldToEcmaNumberField(std::vector<NumberFieldItem>& fields, double x, const UTF16StringDataNonGCStd& resultString)
{
// we need to divide integer field
// because icu returns result as
// 1.234 $
// "1.234" <- integer field
// but we want
// "1", "234"
std::vector<NumberFieldItem> integerFields;
for (size_t i = 0; i < fields.size(); i++) {
NumberFieldItem item = fields[i];
if (item.type == UNUM_INTEGER_FIELD) {
fields.erase(fields.begin() + i);
i--;
std::vector<std::pair<int32_t, int32_t>> apertures;
for (size_t j = 0; j < fields.size(); j++) {
if (fields[j].start >= item.start && fields[j].end <= item.end) {
apertures.push_back(std::make_pair(fields[j].start, fields[j].end));
}
}
if (apertures.size()) {
NumberFieldItem newItem;
newItem.type = item.type;
int32_t lastStart = item.start;
for (size_t j = 0; j < apertures.size(); j++) {
newItem.start = lastStart;
newItem.end = apertures[j].first;
integerFields.push_back(newItem);
lastStart = apertures[j].second;
}
if (lastStart != item.end) {
newItem.start = lastStart;
newItem.end = item.end;
integerFields.push_back(newItem);
}
} else {
integerFields.push_back(item);
}
}
}
fields.insert(fields.end(), integerFields.begin(), integerFields.end());
// add literal field if needs
if (!std::isnan(x) && !std::isinf(x)) {
std::vector<NumberFieldItem> literalFields;
for (size_t i = 0; i < resultString.length(); i++) {
bool found = false;
for (size_t j = 0; j < fields.size(); j++) {
if ((size_t)fields[j].start <= i && i < (size_t)fields[j].end) {
found = true;
break;
}
}
if (!found) {
size_t end = i + 1;
while (end != resultString.length()) {
bool found = false;
for (size_t j = 0; j < fields.size(); j++) {
if ((size_t)fields[j].start <= end && end < (size_t)fields[j].end) {
found = true;
break;
}
}
if (found) {
break;
}
end++;
}
NumberFieldItem newItem;
newItem.type = -1;
newItem.start = i;
newItem.end = end;
i = end - 1;
literalFields.push_back(newItem);
}
}
fields.insert(fields.end(), literalFields.begin(), literalFields.end());
}
std::sort(fields.begin(), fields.end(),
[](const NumberFieldItem& a, const NumberFieldItem& b) -> bool {
return a.start < b.start;
});
}
String* Intl::icuNumberFieldToString(ExecutionState& state, int32_t fieldName, double d, String* style)
{
if (fieldName == -1) {
return state.context()->staticStrings().lazyLiteral().string();
}
switch ((UNumberFormatFields)fieldName) {
case UNUM_INTEGER_FIELD:
if (std::isnan(d)) {
return state.context()->staticStrings().lazySmallLetterNaN().string();
}
if (std::isinf(d)) {
return state.context()->staticStrings().lazySmallLetterInfinity().string();
}
return state.context()->staticStrings().lazyInteger().string();
case UNUM_GROUPING_SEPARATOR_FIELD:
return state.context()->staticStrings().lazyGroup().string();
case UNUM_DECIMAL_SEPARATOR_FIELD:
return state.context()->staticStrings().lazyDecimal().string();
case UNUM_FRACTION_FIELD:
return state.context()->staticStrings().lazyFraction().string();
case UNUM_SIGN_FIELD:
return std::signbit(d) ? state.context()->staticStrings().lazyMinusSign().string() : state.context()->staticStrings().lazyPlusSign().string();
case UNUM_PERCENT_FIELD:
if (style->equals("unit")) {
return state.context()->staticStrings().lazyUnit().string();
} else {
return state.context()->staticStrings().lazyPercentSign().string();
}
case UNUM_CURRENCY_FIELD:
return state.context()->staticStrings().lazyCurrency().string();
case UNUM_EXPONENT_SYMBOL_FIELD:
return state.context()->staticStrings().lazyExponentSeparator().string();
case UNUM_EXPONENT_SIGN_FIELD:
return state.context()->staticStrings().lazyExponentMinusSign().string();
case UNUM_EXPONENT_FIELD:
return state.context()->staticStrings().lazyExponentInteger().string();
#ifndef UNUM_MEASURE_UNIT_FIELD
#define UNUM_MEASURE_UNIT_FIELD (UNUM_SIGN_FIELD + 1)
#endif
#ifndef UNUM_COMPACT_FIELD
#define UNUM_COMPACT_FIELD (UNUM_SIGN_FIELD + 2)
#endif
case UNUM_MEASURE_UNIT_FIELD:
return state.context()->staticStrings().lazyUnit().string();
case UNUM_COMPACT_FIELD:
return state.context()->staticStrings().lazyCompact().string();
#ifndef UNUM_APPROXIMATELY_SIGN_FIELD
#define UNUM_APPROXIMATELY_SIGN_FIELD (UNUM_COMPACT_FIELD + 1)
#endif
case UNUM_APPROXIMATELY_SIGN_FIELD:
return state.context()->staticStrings().lazyApproximatelySign().string();
default:
return String::fromASCII("unknown");
}
}
Value Intl::defaultNumberOption(ExecutionState& state, Value value, double minimum, double maximum, double fallback)
{
// If value is not undefined, then
if (!value.isUndefined()) {
// Let value be ToNumber(value).
double doubleValue = value.toNumber(state);
// If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
if (std::isnan(doubleValue) || doubleValue < minimum || maximum < doubleValue) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Got invalid number option value");
}
// Return floor(value).
return Value(Value::DoubleToIntConvertibleTestNeeds, floor(doubleValue));
} else {
// Else, return fallback.
return Value(Value::DoubleToIntConvertibleTestNeeds, fallback);
}
}
Value Intl::defaultNumberOption(ExecutionState& state, Value value, double minimum, double maximum, Value fallback)
{
// If value is not undefined, then
if (!value.isUndefined()) {
// Let value be ToNumber(value).
double doubleValue = value.toNumber(state);
// If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
if (std::isnan(doubleValue) || doubleValue < minimum || maximum < doubleValue) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Got invalid number option value");
}
// Return floor(value).
return Value(Value::DoubleToIntConvertibleTestNeeds, floor(doubleValue));
} else {
// Else, return fallback.
return fallback;
}
}
Intl::SetNumberFormatDigitOptionsResult Intl::setNumberFormatDigitOptions(ExecutionState& state, Object* options,
double mnfdDefault, double mxfdDefault, String* notation)
{
Intl::SetNumberFormatDigitOptionsResult result;
// Let mnid be ? GetNumberOption(options, "minimumIntegerDigits,", 1, 21, 1).
double mnid = Intl::getNumberOption(state, options, state.context()->staticStrings().lazyMinimumIntegerDigits().string(), 1, 21, 1);
// Let mnfd be ? Get(options, "minimumFractionDigits").
Value mnfd;
auto g = options->get(state, ObjectPropertyName(state.context()->staticStrings().lazyMinimumFractionDigits()));
if (g.hasValue()) {
mnfd = g.value(state, options);
}
// Let mxfd be ? Get(options, "maximumFractionDigits").
Value mxfd;
g = options->get(state, ObjectPropertyName(state.context()->staticStrings().lazyMaximumFractionDigits()));
if (g.hasValue()) {
mxfd = g.value(state, options);
}
// Let mnsd be ? Get(options, "minimumSignificantDigits").
Value mnsd;
g = options->get(state, ObjectPropertyName(state.context()->staticStrings().lazyMinimumSignificantDigits()));
if (g.hasValue()) {
mnsd = g.value(state, options);
}
// Let mxsd be ? Get(options, "maximumSignificantDigits").
Value mxsd;
g = options->get(state, ObjectPropertyName(state.context()->staticStrings().lazyMaximumSignificantDigits()));
if (g.hasValue()) {
mxsd = g.value(state, options);
}
// Set intlObj.[[MinimumIntegerDigits]] to mnid.
result.minimumIntegerDigits = Value(Value::DoubleToIntConvertibleTestNeeds, mnid);
// Let roundingIncrement be ? GetNumberOption(options, "roundingIncrement", 1, 5000, 1).
double roundingIncrement = Intl::getNumberOption(state, options, state.context()->staticStrings().lazyRoundingIncrement().string(), 1, 5000, 1);
// If roundingIncrement is not in « 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000 », throw a RangeError exception.
if (roundingIncrement != 1 && roundingIncrement != 2 && roundingIncrement != 5 && roundingIncrement != 10 && roundingIncrement != 20 && roundingIncrement != 25 && roundingIncrement != 50 && roundingIncrement != 100 && roundingIncrement != 200 && roundingIncrement != 250 && roundingIncrement != 500 && roundingIncrement != 1000 && roundingIncrement != 2000 && roundingIncrement != 2500 && roundingIncrement != 5000) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Got invalid roundingIncrement value");
}
// Let roundingMode be ? GetOption(options, "roundingMode", string, « "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven" », "halfExpand").
Value roundingModeValues[] = { state.context()->staticStrings().ceil.string(), state.context()->staticStrings().floor.string(),
state.context()->staticStrings().lazyExpand().string(), state.context()->staticStrings().trunc.string(),
state.context()->staticStrings().lazyHalfCeil().string(), state.context()->staticStrings().lazyHalfFloor().string(),
state.context()->staticStrings().lazyHalfExpand().string(), state.context()->staticStrings().lazyHalfTrunc().string(),
state.context()->staticStrings().lazyHalfEven().string() };
Value roundingMode = Intl::getOption(state, options, state.context()->staticStrings().lazyRoundingMode().string(), Intl::StringValue, roundingModeValues, 9, roundingModeValues[6]);
// Let roundingPriority be ? GetOption(options, "roundingPriority", string, « "auto", "morePrecision", "lessPrecision" », "auto").
Value roundingPriorityValues[] = { state.context()->staticStrings().lazyAuto().string(), state.context()->staticStrings().lazyMorePrecision().string(),
state.context()->staticStrings().lazyLessPrecision().string() };
Value roundingPriority = Intl::getOption(state, options, state.context()->staticStrings().lazyRoundingPriority().string(), Intl::StringValue, roundingPriorityValues, 3, roundingPriorityValues[0]);
// Let trailingZeroDisplay be ? GetOption(options, "trailingZeroDisplay", string, « "auto", "stripIfInteger" », "auto").
Value trailingZeroDisplayValues[] = { state.context()->staticStrings().lazyAuto().string(), state.context()->staticStrings().lazyStripIfInteger().string() };
Value trailingZeroDisplay = Intl::getOption(state, options, state.context()->staticStrings().lazyTrailingZeroDisplay().string(), Intl::StringValue, trailingZeroDisplayValues, 2, trailingZeroDisplayValues[0]);
// NOTE: All fields required by SetNumberFormatDigitOptions have now been read from options. The remainder of this AO interprets the options and may throw exceptions.
// If roundingIncrement is not 1, set mxfdDefault to mnfdDefault.
if (roundingIncrement != 1) {
mxfdDefault = mnfdDefault;
}
// Set intlObj.[[RoundingIncrement]] to roundingIncrement.
result.roundingIncrement = roundingIncrement;
// Set intlObj.[[RoundingMode]] to roundingMode.
result.roundingMode = roundingMode;
// Set intlObj.[[TrailingZeroDisplay]] to trailingZeroDisplay.
result.trailingZeroDisplay = trailingZeroDisplay;
// If mnsd is undefined and mxsd is undefined, let hasSd be false. Otherwise, let hasSd be true.
bool hasSd;
if (mnsd.isUndefined() && mxsd.isUndefined()) {
hasSd = false;
} else {
hasSd = true;
}
// NOTE: IMO mxsd is typo. mxfd is more reasonable
// If mnfd is undefined and mxsd is undefined, let hasFd be false. Otherwise, let hasFd be true.
bool hasFd;
if (mnfd.isUndefined() && mxfd.isUndefined()) {
hasFd = false;
} else {
hasFd = true;
}
// Let needSd be true.
bool needSd = true;
// Let needFd be true.
bool needFd = true;
// If roundingPriority is "auto", then
if (roundingPriority.asString()->equals("auto")) {
// a. Set needSd to hasSd.
needSd = hasSd;
// b. If needSd is true, or hasFd is false and notation is "compact", then
if (needSd || (!hasFd && notation->equals("compact"))) {
// i. Set needFd to false.
needFd = false;
}
}
// If needSd is true, then
if (needSd) {
// a. If hasSd is true, then
if (hasSd) {
// i. Set intlObj.[[MinimumSignificantDigits]] to ? DefaultNumberOption(mnsd, 1, 21, 1).
result.minimumSignificantDigits = Intl::defaultNumberOption(state, mnsd, 1, 21, 1);
// ii. Set intlObj.[[maximumSignificantDigits]] to ? DefaultNumberOption(mxsd, intlObj.[[MinimumSignificantDigits]], 21, 21).
result.maximumSignificantDigits = Intl::defaultNumberOption(state, mxsd, result.minimumSignificantDigits.asNumber(), 21, 21);
} else {
// Else,
// Set intlObj.[[MinimumSignificantDigits]] to 1.
result.minimumSignificantDigits = Value(1);
// Set intlObj.[[maximumSignificantDigits]] to 21.
result.maximumSignificantDigits = Value(21);
}
}
// If needFd is true, then
if (needFd) {
// If hasFd is true, then
if (hasFd) {
// Set mnfd to ? DefaultNumberOption(mnfd, 0, 100, undefined).
mnfd = Intl::defaultNumberOption(state, mnfd, 0, 100, Value());
// Set mxfd to ? DefaultNumberOption(mxfd, 0, 100, undefined).
mxfd = Intl::defaultNumberOption(state, mxfd, 0, 100, Value());
// If mnfd is undefined, set mnfd to min(mnfdDefault, mxfd).
if (mnfd.isUndefined()) {
mnfd = Value(Value::DoubleToIntConvertibleTestNeeds, std::min(mnfdDefault, mxfd.asNumber()));
} else if (mxfd.isUndefined()) {
// Else if mxfd is undefined, set mxfd to max(mxfdDefault, mnfd).
mxfd = Value(Value::DoubleToIntConvertibleTestNeeds, std::max(mxfdDefault, mnfd.asNumber()));
} else if (mnfd.asNumber() > mxfd.asNumber()) {
// Else if mnfd is greater than mxfd, throw a RangeError exception.
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Got invalid mnfd, mxfd value");
}
// Set intlObj.[[MinimumFractionDigits]] to mnfd.
result.minimumFractionDigits = mnfd;
// Set intlObj.[[MaximumFractionDigits]] to mxfd.
result.maximumFractionDigits = mxfd;
} else {
// Else,
// Set intlObj.[[MinimumFractionDigits]] to mnfdDefault.
result.minimumFractionDigits = Value(Value::DoubleToIntConvertibleTestNeeds, mnfdDefault);
// Set intlObj.[[MaximumFractionDigits]] to mxfdDefault.
result.maximumFractionDigits = Value(Value::DoubleToIntConvertibleTestNeeds, mxfdDefault);
}
}
// If needSd is false and needFd is false, then
if (!needSd && !needFd) {
// Set intlObj.[[MinimumFractionDigits]] to 0.
result.minimumFractionDigits = Value(0);
// Set intlObj.[[MaximumFractionDigits]] to 0.
result.maximumFractionDigits = Value(0);
// Set intlObj.[[MinimumSignificantDigits]] to 1.
result.minimumSignificantDigits = Value(1);
// Set intlObj.[[MaximumSignificantDigits]] to 2.
result.maximumSignificantDigits = Value(2);
// Set intlObj.[[RoundingType]] to more-precision.
result.roundingType = RoundingType::MorePrecision;
// Set intlObj.[[ComputedRoundingPriority]] to "morePrecision".
result.computedRoundingPriority = RoundingPriority::MorePrecision;
} else if (roundingPriority.asString()->equals("morePrecision")) {
// Else if roundingPriority is "morePrecision", then
// Set intlObj.[[RoundingType]] to more-precision.
result.roundingType = RoundingType::MorePrecision;
// Set intlObj.[[ComputedRoundingPriority]] to "morePrecision".
result.computedRoundingPriority = RoundingPriority::MorePrecision;
} else if (roundingPriority.asString()->equals("lessPrecision")) {
// Else if roundingPriority is "lessPrecision", then
// Set intlObj.[[RoundingType]] to less-precision.
result.roundingType = RoundingType::LessPrecision;
// Set intlObj.[[ComputedRoundingPriority]] to "lessPrecision".
result.computedRoundingPriority = RoundingPriority::LessPrecision;
} else if (hasSd) {
// Else if hasSd is true, then
// Set intlObj.[[RoundingType]] to significant-digits.
result.roundingType = RoundingType::SignificantDigits;
// Set intlObj.[[ComputedRoundingPriority]] to "auto".
result.computedRoundingPriority = RoundingPriority::Auto;
} else {
// Else,
// Set intlObj.[[RoundingType]] to fraction-digits.
result.roundingType = RoundingType::FractionDigits;
// Set intlObj.[[ComputedRoundingPriority]] to "auto".
result.computedRoundingPriority = RoundingPriority::Auto;
}
// If roundingIncrement is not 1, then
if (roundingIncrement != 1) {
// If intlObj.[[RoundingType]] is not fraction-digits, throw a TypeError exception.
if (result.roundingType != RoundingType::FractionDigits) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "If roundingIncrement is 1, roundingType should be `fraction-digits`");
}
// If intlObj.[[MaximumFractionDigits]] is not intlObj.[[MinimumFractionDigits]], throw a RangeError exception.
if (result.maximumFractionDigits != result.minimumFractionDigits) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "If roundingIncrement is 1, maximumFractionDigits and minimumFractionDigits should be same");
}
}
return result;
}
} // namespace Escargot
#endif