5ff9c0bc2fb732ba2913a472cd0262cda75fcb51
[WebKit-https.git] / Source / JavaScriptCore / runtime / IntlPluralRules.cpp
1 /*
2  * Copyright (C) 2018 Andy VanWagoner (andy@vanwagoner.family)
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "IntlPluralRules.h"
28
29 #if ENABLE(INTL)
30
31 #include "Error.h"
32 #include "IntlObject.h"
33 #include "IntlPluralRulesConstructor.h"
34 #include "JSCInlines.h"
35 #include "ObjectConstructor.h"
36
37 namespace JSC {
38
39 const ClassInfo IntlPluralRules::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlPluralRules) };
40
41 void IntlPluralRules::UPluralRulesDeleter::operator()(UPluralRules* pluralRules) const
42 {
43     if (pluralRules)
44         uplrules_close(pluralRules);
45 }
46
47 void IntlPluralRules::UNumberFormatDeleter::operator()(UNumberFormat* numberFormat) const
48 {
49     if (numberFormat)
50         unum_close(numberFormat);
51 }
52
53 struct UEnumerationDeleter {
54     void operator()(UEnumeration* enumeration) const
55     {
56         if (enumeration)
57             uenum_close(enumeration);
58     }
59 };
60
61 IntlPluralRules* IntlPluralRules::create(VM& vm, Structure* structure)
62 {
63     IntlPluralRules* format = new (NotNull, allocateCell<IntlPluralRules>(vm.heap)) IntlPluralRules(vm, structure);
64     format->finishCreation(vm);
65     return format;
66 }
67
68 Structure* IntlPluralRules::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
69 {
70     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
71 }
72
73 IntlPluralRules::IntlPluralRules(VM& vm, Structure* structure)
74     : JSDestructibleObject(vm, structure)
75 {
76 }
77
78 void IntlPluralRules::finishCreation(VM& vm)
79 {
80     Base::finishCreation(vm);
81     ASSERT(inherits(vm, info()));
82 }
83
84 void IntlPluralRules::destroy(JSCell* cell)
85 {
86     static_cast<IntlPluralRules*>(cell)->IntlPluralRules::~IntlPluralRules();
87 }
88
89 void IntlPluralRules::visitChildren(JSCell* cell, SlotVisitor& visitor)
90 {
91     IntlPluralRules* thisObject = jsCast<IntlPluralRules*>(cell);
92     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
93
94     Base::visitChildren(thisObject, visitor);
95 }
96
97 namespace IntlPRInternal {
98 static Vector<String> localeData(const String&, size_t)
99 {
100     Vector<String> data;
101     return data;
102 }
103 }
104
105 void IntlPluralRules::initializePluralRules(ExecState& exec, JSValue locales, JSValue optionsValue)
106 {
107     VM& vm = exec.vm();
108     auto scope = DECLARE_THROW_SCOPE(vm);
109
110     // 13.1.1 InitializePluralRules (pluralRules, locales, options)
111     // https://tc39.github.io/ecma402/#sec-initializepluralrules
112     Vector<String> requestedLocales = canonicalizeLocaleList(exec, locales);
113     RETURN_IF_EXCEPTION(scope, void());
114
115     JSObject* options;
116     if (optionsValue.isUndefined())
117         options = constructEmptyObject(&exec, exec.lexicalGlobalObject()->nullPrototypeObjectStructure());
118     else {
119         options = optionsValue.toObject(&exec);
120         RETURN_IF_EXCEPTION(scope, void());
121     }
122
123     HashMap<String, String> localeOpt;
124     String localeMatcher = intlStringOption(exec, options, vm.propertyNames->localeMatcher, { "lookup", "best fit" }, "localeMatcher must be either \"lookup\" or \"best fit\"", "best fit");
125     RETURN_IF_EXCEPTION(scope, void());
126     localeOpt.add(vm.propertyNames->localeMatcher.string(), localeMatcher);
127
128     const HashSet<String> availableLocales = exec.jsCallee()->globalObject(vm)->intlNumberFormatAvailableLocales();
129     HashMap<String, String> resolved = resolveLocale(exec, availableLocales, requestedLocales, localeOpt, nullptr, 0, IntlPRInternal::localeData);
130     m_locale = resolved.get(vm.propertyNames->locale.string());
131     if (m_locale.isEmpty()) {
132         throwTypeError(&exec, scope, "failed to initialize PluralRules due to invalid locale"_s);
133         return;
134     }
135
136     String typeString = intlStringOption(exec, options, Identifier::fromString(&vm, "type"), { "cardinal", "ordinal" }, "type must be \"cardinal\" or \"ordinal\"", "cardinal");
137     RETURN_IF_EXCEPTION(scope, void());
138     m_type = typeString == "ordinal" ? UPLURAL_TYPE_ORDINAL : UPLURAL_TYPE_CARDINAL;
139
140     unsigned minimumIntegerDigits = intlNumberOption(exec, options, Identifier::fromString(&vm, "minimumIntegerDigits"), 1, 21, 1);
141     RETURN_IF_EXCEPTION(scope, void());
142     m_minimumIntegerDigits = minimumIntegerDigits;
143
144     unsigned minimumFractionDigitsDefault = 0;
145     unsigned minimumFractionDigits = intlNumberOption(exec, options, Identifier::fromString(&vm, "minimumFractionDigits"), 0, 20, minimumFractionDigitsDefault);
146     RETURN_IF_EXCEPTION(scope, void());
147     m_minimumFractionDigits = minimumFractionDigits;
148
149     unsigned maximumFractionDigitsDefault = std::max(minimumFractionDigits, 3u);
150     unsigned maximumFractionDigits = intlNumberOption(exec, options, Identifier::fromString(&vm, "maximumFractionDigits"), minimumFractionDigits, 20, maximumFractionDigitsDefault);
151     RETURN_IF_EXCEPTION(scope, void());
152     m_maximumFractionDigits = maximumFractionDigits;
153
154     JSValue minimumSignificantDigitsValue = options->get(&exec, Identifier::fromString(&vm, "minimumSignificantDigits"));
155     RETURN_IF_EXCEPTION(scope, void());
156
157     JSValue maximumSignificantDigitsValue = options->get(&exec, Identifier::fromString(&vm, "maximumSignificantDigits"));
158     RETURN_IF_EXCEPTION(scope, void());
159
160     if (!minimumSignificantDigitsValue.isUndefined() || !maximumSignificantDigitsValue.isUndefined()) {
161         unsigned minimumSignificantDigits = intlNumberOption(exec, options, Identifier::fromString(&vm, "minimumSignificantDigits"), 1, 21, 1);
162         RETURN_IF_EXCEPTION(scope, void());
163         unsigned maximumSignificantDigits = intlNumberOption(exec, options, Identifier::fromString(&vm, "maximumSignificantDigits"), minimumSignificantDigits, 21, 21);
164         RETURN_IF_EXCEPTION(scope, void());
165         m_minimumSignificantDigits = minimumSignificantDigits;
166         m_maximumSignificantDigits = maximumSignificantDigits;
167     }
168
169     UErrorCode status = U_ZERO_ERROR;
170     m_numberFormat = std::unique_ptr<UNumberFormat, UNumberFormatDeleter>(unum_open(UNUM_DECIMAL, nullptr, 0, m_locale.utf8().data(), nullptr, &status));
171     if (U_FAILURE(status)) {
172         throwTypeError(&exec, scope, "failed to initialize PluralRules"_s);
173         return;
174     }
175
176     if (m_minimumSignificantDigits) {
177         unum_setAttribute(m_numberFormat.get(), UNUM_SIGNIFICANT_DIGITS_USED, true);
178         unum_setAttribute(m_numberFormat.get(), UNUM_MIN_SIGNIFICANT_DIGITS, m_minimumSignificantDigits.value());
179         unum_setAttribute(m_numberFormat.get(), UNUM_MAX_SIGNIFICANT_DIGITS, m_maximumSignificantDigits.value());
180     } else {
181         unum_setAttribute(m_numberFormat.get(), UNUM_MIN_INTEGER_DIGITS, m_minimumIntegerDigits);
182         unum_setAttribute(m_numberFormat.get(), UNUM_MIN_FRACTION_DIGITS, m_minimumFractionDigits);
183         unum_setAttribute(m_numberFormat.get(), UNUM_MAX_FRACTION_DIGITS, m_maximumFractionDigits);
184     }
185
186     status = U_ZERO_ERROR;
187     m_pluralRules = std::unique_ptr<UPluralRules, UPluralRulesDeleter>(uplrules_openForType(m_locale.utf8().data(), m_type, &status));
188     if (U_FAILURE(status)) {
189         throwTypeError(&exec, scope, "failed to initialize PluralRules"_s);
190         return;
191     }
192
193     m_initializedPluralRules = true;
194 }
195
196 JSObject* IntlPluralRules::resolvedOptions(ExecState& exec)
197 {
198     VM& vm = exec.vm();
199     auto scope = DECLARE_THROW_SCOPE(vm);
200
201     // 13.4.4 Intl.PluralRules.prototype.resolvedOptions ()
202     // https://tc39.github.io/ecma402/#sec-intl.pluralrules.prototype.resolvedoptions
203     if (!m_initializedPluralRules)
204         return throwTypeError(&exec, scope, "Intl.PluralRules.prototype.resolvedOptions called on value that's not an object initialized as a PluralRules"_s);
205
206     JSObject* options = constructEmptyObject(&exec);
207     options->putDirect(vm, vm.propertyNames->locale, jsNontrivialString(&exec, m_locale));
208     options->putDirect(vm, Identifier::fromString(&vm, "type"), jsNontrivialString(&exec, m_type == UPLURAL_TYPE_ORDINAL ? "ordinal"_s : "cardinal"_s));
209     options->putDirect(vm, Identifier::fromString(&vm, "minimumIntegerDigits"), jsNumber(m_minimumIntegerDigits));
210     options->putDirect(vm, Identifier::fromString(&vm, "minimumFractionDigits"), jsNumber(m_minimumFractionDigits));
211     options->putDirect(vm, Identifier::fromString(&vm, "maximumFractionDigits"), jsNumber(m_maximumFractionDigits));
212     if (m_minimumSignificantDigits) {
213         options->putDirect(vm, Identifier::fromString(&vm, "minimumSignificantDigits"), jsNumber(m_minimumSignificantDigits.value()));
214         options->putDirect(vm, Identifier::fromString(&vm, "maximumSignificantDigits"), jsNumber(m_maximumSignificantDigits.value()));
215     }
216
217 #if HAVE(ICU_PLURALRULES_KEYWORDS)
218     JSGlobalObject* globalObject = exec.jsCallee()->globalObject(vm);
219     JSArray* categories = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
220     if (!categories)
221         return throwOutOfMemoryError(&exec, scope);
222
223     UErrorCode status = U_ZERO_ERROR;
224     auto keywords = std::unique_ptr<UEnumeration, UEnumerationDeleter>(uplrules_getKeywords(m_pluralRules.get(), &status));
225     ASSERT(U_SUCCESS(status));
226     int32_t resultLength;
227
228     // Category names are always ASCII, so use char[].
229     unsigned index = 0;
230     while (const char* result = uenum_next(keywords.get(), &resultLength, &status)) {
231         ASSERT(U_SUCCESS(status));
232         categories->putDirectIndex(&exec, index++, jsNontrivialString(&exec, String(result, resultLength)));
233         RETURN_IF_EXCEPTION(scope, { });
234     }
235     options->putDirect(vm, Identifier::fromString(&vm, "pluralCategories"), categories);
236 #endif
237
238     RELEASE_AND_RETURN(scope, options);
239 }
240
241 JSValue IntlPluralRules::select(ExecState& exec, double value)
242 {
243     VM& vm = exec.vm();
244     auto scope = DECLARE_THROW_SCOPE(vm);
245
246     // 13.1.4 ResolvePlural (pluralRules, n)
247     // https://tc39.github.io/ecma402/#sec-resolveplural
248     if (!m_initializedPluralRules)
249         return throwTypeError(&exec, scope, "Intl.PluralRules.prototype.select called on value that's not an object initialized as a PluralRules"_s);
250
251     if (!std::isfinite(value))
252         return jsNontrivialString(&exec, "other"_s);
253
254 #if HAVE(ICU_PLURALRULES_WITH_FORMAT)
255     UErrorCode status = U_ZERO_ERROR;
256     Vector<UChar, 8> result(8);
257     auto length = uplrules_selectWithFormat(m_pluralRules.get(), value, m_numberFormat.get(), result.data(), result.size(), &status);
258     if (U_FAILURE(status))
259         return throwTypeError(&exec, scope, "failed to select plural value"_s);
260 #else
261     UErrorCode status = U_ZERO_ERROR;
262     Vector<UChar, 32> buffer(32);
263     auto length = unum_formatDouble(m_numberFormat.get(), value, buffer.data(), buffer.size(), nullptr, &status);
264     if (status == U_BUFFER_OVERFLOW_ERROR) {
265         buffer.grow(length);
266         status = U_ZERO_ERROR;
267         unum_formatDouble(m_numberFormat.get(), value, buffer.data(), length, nullptr, &status);
268     }
269     if (U_FAILURE(status))
270         return throwTypeError(&exec, scope, "failed to select plural value"_s);
271
272     double formatted = unum_parseDouble(m_numberFormat.get(), buffer.data(), length, nullptr, &status);
273     if (U_FAILURE(status))
274         return throwTypeError(&exec, scope, "failed to select plural value"_s);
275
276     // Can only be 'zero', 'one', 'two', 'few', 'many' or 'other'
277     status = U_ZERO_ERROR;
278     Vector<UChar, 8> result(8);
279     length = uplrules_select(m_pluralRules.get(), formatted, result.data(), result.size(), &status);
280     if (U_FAILURE(status))
281         return throwTypeError(&exec, scope, "failed to select plural value"_s);
282 #endif
283
284     return jsString(&exec, String(result.data(), length));
285 }
286
287 } // namespace JSC
288
289 #endif // ENABLE(INTL)