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