[INTL] Implement Intl.Collator.prototype.resolvedOptions ()
[WebKit-https.git] / Source / JavaScriptCore / runtime / IntlCollatorConstructor.cpp
1 /*
2  * Copyright (C) 2015 Andy VanWagoner (thetalecrafter@gmail.com)
3  * Copyright (C) 2015 Sukolsak Sakshuwong (sukolsak@gmail.com)
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 "IntlCollatorConstructor.h"
29
30 #if ENABLE(INTL)
31
32 #include "Error.h"
33 #include "IntlCollator.h"
34 #include "IntlCollatorPrototype.h"
35 #include "IntlObject.h"
36 #include "JSCJSValueInlines.h"
37 #include "JSCellInlines.h"
38 #include "Lookup.h"
39 #include "ObjectConstructor.h"
40 #include "SlotVisitorInlines.h"
41 #include "StructureInlines.h"
42 #include <unicode/ucol.h>
43
44 namespace JSC {
45
46 STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(IntlCollatorConstructor);
47
48 static EncodedJSValue JSC_HOST_CALL IntlCollatorConstructorFuncSupportedLocalesOf(ExecState*);
49
50 }
51
52 #include "IntlCollatorConstructor.lut.h"
53
54 namespace JSC {
55
56 const ClassInfo IntlCollatorConstructor::s_info = { "Function", &InternalFunction::s_info, &collatorConstructorTable, CREATE_METHOD_TABLE(IntlCollatorConstructor) };
57
58 /* Source for IntlCollatorConstructor.lut.h
59 @begin collatorConstructorTable
60   supportedLocalesOf             IntlCollatorConstructorFuncSupportedLocalesOf             DontEnum|Function 1
61 @end
62 */
63
64 static Vector<String> sortLocaleData(const String& locale, const String& key)
65 {
66     // 9.1 Internal slots of Service Constructors & 10.2.3 Internal slots (ECMA-402 2.0)
67     Vector<String> keyLocaleData;
68     if (key == "co") {
69         // 10.2.3 "The first element of [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co must be null for all locale values."
70         keyLocaleData.append(String());
71
72         UErrorCode status = U_ZERO_ERROR;
73         UEnumeration* enumeration = ucol_getKeywordValuesForLocale("collation", locale.utf8().data(), TRUE, &status);
74         if (U_SUCCESS(status)) {
75             const char* keywordValue;
76             int32_t length;
77             while ((keywordValue = uenum_next(enumeration, &length, &status)) && U_SUCCESS(status)) {
78                 String collation(keywordValue, length);
79
80                 // 10.2.3 "The values "standard" and "search" must not be used as elements in any [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co array."
81                 if (collation == "standard" || collation == "search")
82                     continue;
83
84                 // Map keyword values to BCP 47 equivalents.
85                 if (collation == "dictionary")
86                     collation = ASCIILiteral("dict");
87                 else if (collation == "gb2312han")
88                     collation = ASCIILiteral("gb2312");
89                 else if (collation == "phonebook")
90                     collation = ASCIILiteral("phonebk");
91                 else if (collation == "traditional")
92                     collation = ASCIILiteral("trad");
93
94                 keyLocaleData.append(collation);
95             }
96             uenum_close(enumeration);
97         }
98     } else if (key == "kn") {
99         keyLocaleData.append(ASCIILiteral("false"));
100         keyLocaleData.append(ASCIILiteral("true"));
101     } else
102         ASSERT_NOT_REACHED();
103     return keyLocaleData;
104 }
105
106 static Vector<String> searchLocaleData(const String&, const String& key)
107 {
108     // 9.1 Internal slots of Service Constructors & 10.2.3 Internal slots (ECMA-402 2.0)
109     Vector<String> keyLocaleData;
110     if (key == "co") {
111         // 10.2.3 "The first element of [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co must be null for all locale values."
112         keyLocaleData.append(String());
113     } else if (key == "kn") {
114         keyLocaleData.append(ASCIILiteral("false"));
115         keyLocaleData.append(ASCIILiteral("true"));
116     } else if (key == "sensitivity") {
117         // 10.2.3 "[[searchLocaleData]][locale] must have a sensitivity property with a String value equal to "base", "accent", "case", or "variant" for all locale values."
118         keyLocaleData.append("variant");
119     } else
120         ASSERT_NOT_REACHED();
121     return keyLocaleData;
122 }
123
124 static IntlCollator* initializeCollator(ExecState* exec, IntlCollator* collator, JSValue locales, JSValue optionsValue)
125 {
126     // 10.1.1 InitializeCollator (collator, locales, options) (ECMA-402 2.0)
127
128     // 1. If collator has an [[initializedIntlObject]] internal slot with value true, throw a TypeError exception.
129     // 2. Set collator.[[initializedIntlObject]] to true.
130
131     // 3. Let requestedLocales be CanonicalizeLocaleList(locales).
132     Vector<String> requestedLocales = canonicalizeLocaleList(exec, locales);
133     // 4. ReturnIfAbrupt(requestedLocales).
134     if (exec->hadException())
135         return nullptr;
136
137     // 5. If options is undefined, then
138     JSObject* options;
139     if (optionsValue.isUndefined()) {
140         // a. Let options be ObjectCreate(%ObjectPrototype%).
141         options = constructEmptyObject(exec);
142     } else { // 6. Else
143         // a. Let options be ToObject(options).
144         options = optionsValue.toObject(exec);
145         // b. ReturnIfAbrupt(options).
146         if (exec->hadException())
147             return nullptr;
148     }
149
150     // 7. Let u be GetOption(options, "usage", "string", «"sort", "search"», "sort").
151     const HashSet<String> usages({ ASCIILiteral("sort"), ASCIILiteral("search") });
152     String usage = getIntlStringOption(exec, options, exec->vm().propertyNames->usage, usages, "usage must be either \"sort\" or \"search\"", ASCIILiteral("sort"));
153     // 8. ReturnIfAbrupt(u).
154     if (exec->hadException())
155         return nullptr;
156     // 9. Set collator.[[usage]] to u.
157     collator->setUsage(usage);
158
159     // 10. If u is "sort", then
160     // a. Let localeData be the value of %Collator%.[[sortLocaleData]];
161     // 11. Else
162     // a. Let localeData be the value of %Collator%.[[searchLocaleData]].
163     Vector<String> (*localeData)(const String&, const String&);
164     if (usage == "sort")
165         localeData = sortLocaleData;
166     else
167         localeData = searchLocaleData;
168
169     // 12. Let opt be a new Record.
170     HashMap<String, String> opt;
171
172     // 13. Let matcher be GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
173     const HashSet<String> matchers({ ASCIILiteral("lookup"), ASCIILiteral("best fit") });
174     String matcher = getIntlStringOption(exec, options, exec->vm().propertyNames->localeMatcher, matchers, "localeMatcher must be either \"lookup\" or \"best fit\"", ASCIILiteral("best fit"));
175     // 14. ReturnIfAbrupt(matcher).
176     if (exec->hadException())
177         return nullptr;
178     // 15. Set opt.[[localeMatcher]] to matcher.
179     opt.set(ASCIILiteral("localeMatcher"), matcher);
180
181     // 16. For each row in Table 1, except the header row, do:
182     // a. Let key be the name given in the Key column of the row.
183     // b. Let prop be the name given in the Property column of the row.
184     // c. Let type be the string given in the Type column of the row.
185     // d. Let list be a List containing the Strings given in the Values column of the row, or undefined if no strings are given.
186     // e. Let value be GetOption(options, prop, type, list, undefined).
187     // f. ReturnIfAbrupt(value).
188     // g. If the string given in the Type column of the row is "boolean" and value is not undefined, then
189     //    i. Let value be ToString(value).
190     //    ii. ReturnIfAbrupt(value).
191     // h. Set opt.[[<key>]] to value.
192     {
193         String numericString;
194         bool usesFallback;
195         bool numeric = getIntlBooleanOption(exec, options, exec->vm().propertyNames->numeric, usesFallback);
196         if (exec->hadException())
197             return nullptr;
198         if (!usesFallback)
199             numericString = ASCIILiteral(numeric ? "true" : "false");
200         opt.set(ASCIILiteral("kn"), numericString);
201     }
202     {
203         const HashSet<String> caseFirsts({ ASCIILiteral("upper"), ASCIILiteral("lower"), ASCIILiteral("false") });
204         String caseFirst = getIntlStringOption(exec, options, exec->vm().propertyNames->caseFirst, caseFirsts, "caseFirst must be either \"upper\", \"lower\", or \"false\"", String());
205         if (exec->hadException())
206             return nullptr;
207         opt.set(ASCIILiteral("kf"), caseFirst);
208     }
209
210     // 17. Let relevantExtensionKeys be the value of %Collator%.[[relevantExtensionKeys]].
211     // FIXME: Implement kf (caseFirst).
212     const Vector<String> relevantExtensionKeys { ASCIILiteral("co"), ASCIILiteral("kn") };
213
214     // 18. Let r be ResolveLocale(%Collator%.[[availableLocales]], requestedLocales, opt, relevantExtensionKeys, localeData).
215     const HashSet<String>& availableLocales = exec->callee()->globalObject()->intlCollatorAvailableLocales();
216     HashMap<String, String> result = resolveLocale(availableLocales, requestedLocales, opt, relevantExtensionKeys, localeData);
217
218     // 19. Set collator.[[locale]] to the value of r.[[locale]].
219     collator->setLocale(result.get(ASCIILiteral("locale")));
220
221     // 20. Let k be 0.
222     // 21. Let lenValue be Get(relevantExtensionKeys, "length").
223     // 22. Let len be ToLength(lenValue).
224     // 23. Repeat while k < len:
225     // a. Let Pk be ToString(k).
226     // b. Let key be Get(relevantExtensionKeys, Pk).
227     // c. ReturnIfAbrupt(key).
228     // d. If key is "co", then
229     //    i. Let property be "collation".
230     //    ii. Let value be the value of r.[[co]].
231     //    iii. If value is null, let value be "default".
232     // e. Else use the row of Table 1 that contains the value of key in the Key column:
233     //    i. Let property be the name given in the Property column of the row.
234     //    ii. Let value be the value of r.[[<key>]].
235     //    iii. If the name given in the Type column of the row is "boolean", let value be the result of comparing value with "true".
236     // f. Set collator.[[<property>]] to value.
237     // g. Increase k by 1.
238     ASSERT(relevantExtensionKeys.size() == 2);
239     {
240         ASSERT(relevantExtensionKeys[0] == "co");
241         const String& value = result.get(ASCIILiteral("co"));
242         collator->setCollation(value.isNull() ? ASCIILiteral("default") : value);
243     }
244     {
245         ASSERT(relevantExtensionKeys[1] == "kn");
246         const String& value = result.get(ASCIILiteral("kn"));
247         collator->setNumeric(value == "true");
248     }
249
250     // 24. Let s be GetOption(options, "sensitivity", "string", «"base", "accent", "case", "variant"», undefined).
251     const HashSet<String> sensitivities({ ASCIILiteral("base"), ASCIILiteral("accent"), ASCIILiteral("case"), ASCIILiteral("variant") });
252     String sensitivity = getIntlStringOption(exec, options, exec->vm().propertyNames->sensitivity, sensitivities, "sensitivity must be either \"base\", \"accent\", \"case\", or \"variant\"", String());
253     // 25. ReturnIfAbrupt(s).
254     if (exec->hadException())
255         return nullptr;
256     // 26. If s is undefined, then
257     if (sensitivity.isNull()) {
258         // a. If u is "sort", then let s be "variant".
259         if (usage == "sort")
260             sensitivity = ASCIILiteral("variant");
261         else {
262             // b. Else
263             //    i. Let dataLocale be the value of r.[[dataLocale]].
264             //    ii. Let dataLocaleData be Get(localeData, dataLocale).
265             //    iii. Let s be Get(dataLocaleData, "sensitivity").
266             const String& dataLocale = result.get(ASCIILiteral("dataLocale"));
267             sensitivity = localeData(dataLocale, ASCIILiteral("sensitivity"))[0];
268         }
269     }
270     // 27. Set collator.[[sensitivity]] to s.
271     collator->setSensitivity(sensitivity);
272
273     // 28. Let ip be GetOption(options, "ignorePunctuation", "boolean", undefined, false).
274     bool usesFallback;
275     bool ignorePunctuation = getIntlBooleanOption(exec, options, exec->vm().propertyNames->ignorePunctuation, usesFallback);
276     if (usesFallback)
277         ignorePunctuation = false;
278     // 29. ReturnIfAbrupt(ip).
279     if (exec->hadException())
280         return nullptr;
281     // 30. Set collator.[[ignorePunctuation]] to ip.
282     collator->setIgnorePunctuation(ignorePunctuation);
283
284     // 31. Set collator.[[boundCompare]] to undefined.
285     // 32. Set collator.[[initializedCollator]] to true.
286     // 33. Return collator.
287     return collator;
288 }
289
290 IntlCollatorConstructor* IntlCollatorConstructor::create(VM& vm, Structure* structure, IntlCollatorPrototype* collatorPrototype, Structure* collatorStructure)
291 {
292     IntlCollatorConstructor* constructor = new (NotNull, allocateCell<IntlCollatorConstructor>(vm.heap)) IntlCollatorConstructor(vm, structure);
293     constructor->finishCreation(vm, collatorPrototype, collatorStructure);
294     return constructor;
295 }
296
297 Structure* IntlCollatorConstructor::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
298 {
299     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
300 }
301
302 IntlCollatorConstructor::IntlCollatorConstructor(VM& vm, Structure* structure)
303     : InternalFunction(vm, structure)
304 {
305 }
306
307 void IntlCollatorConstructor::finishCreation(VM& vm, IntlCollatorPrototype* collatorPrototype, Structure* collatorStructure)
308 {
309     Base::finishCreation(vm, ASCIILiteral("Collator"));
310     putDirectWithoutTransition(vm, vm.propertyNames->prototype, collatorPrototype, DontEnum | DontDelete | ReadOnly);
311     putDirectWithoutTransition(vm, vm.propertyNames->length, jsNumber(0), ReadOnly | DontEnum | DontDelete);
312     m_collatorStructure.set(vm, this, collatorStructure);
313 }
314
315 static EncodedJSValue JSC_HOST_CALL constructIntlCollator(ExecState* exec)
316 {
317     // 10.1.2 Intl.Collator ([locales [, options]]) (ECMA-402 2.0)
318     // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
319     JSValue newTarget = exec->newTarget();
320     if (newTarget.isUndefined())
321         newTarget = exec->callee();
322
323     // 2. Let collator be OrdinaryCreateFromConstructor(newTarget, %CollatorPrototype%).
324     VM& vm = exec->vm();
325     IntlCollator* collator = IntlCollator::create(vm, jsCast<IntlCollatorConstructor*>(exec->callee()));
326     if (collator && !jsDynamicCast<IntlCollatorConstructor*>(newTarget)) {
327         JSValue proto = asObject(newTarget)->getDirect(vm, vm.propertyNames->prototype);
328         asObject(collator)->setPrototypeWithCycleCheck(exec, proto);
329     }
330
331     // 3. ReturnIfAbrupt(collator).
332     ASSERT(collator);
333
334     // 4. Return InitializeCollator(collator, locales, options).
335     JSValue locales = exec->argument(0);
336     JSValue options = exec->argument(1);
337     return JSValue::encode(initializeCollator(exec, collator, locales, options));
338 }
339
340 static EncodedJSValue JSC_HOST_CALL callIntlCollator(ExecState* exec)
341 {
342     // 10.1.2 Intl.Collator ([locales [, options]]) (ECMA-402 2.0)
343     // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
344     // NewTarget is always undefined when called as a function.
345
346     // 2. Let collator be OrdinaryCreateFromConstructor(newTarget, %CollatorPrototype%).
347     VM& vm = exec->vm();
348     IntlCollator* collator = IntlCollator::create(vm, jsCast<IntlCollatorConstructor*>(exec->callee()));
349
350     // 3. ReturnIfAbrupt(collator).
351     ASSERT(collator);
352
353     // 4. Return InitializeCollator(collator, locales, options).
354     JSValue locales = exec->argument(0);
355     JSValue options = exec->argument(1);
356     return JSValue::encode(initializeCollator(exec, collator, locales, options));
357 }
358
359 ConstructType IntlCollatorConstructor::getConstructData(JSCell*, ConstructData& constructData)
360 {
361     constructData.native.function = constructIntlCollator;
362     return ConstructTypeHost;
363 }
364
365 CallType IntlCollatorConstructor::getCallData(JSCell*, CallData& callData)
366 {
367     callData.native.function = callIntlCollator;
368     return CallTypeHost;
369 }
370
371 bool IntlCollatorConstructor::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
372 {
373     return getStaticFunctionSlot<InternalFunction>(exec, collatorConstructorTable, jsCast<IntlCollatorConstructor*>(object), propertyName, slot);
374 }
375
376 EncodedJSValue JSC_HOST_CALL IntlCollatorConstructorFuncSupportedLocalesOf(ExecState* exec)
377 {
378     // 10.2.2 Intl.Collator.supportedLocalesOf(locales [, options]) (ECMA-402 2.0)
379
380     // 1. Let requestedLocales be CanonicalizeLocaleList(locales).
381     Vector<String> requestedLocales = canonicalizeLocaleList(exec, exec->argument(0));
382
383     // 2. ReturnIfAbrupt(requestedLocales).
384     if (exec->hadException())
385         return JSValue::encode(jsUndefined());
386
387     // 3. Return SupportedLocales(%Collator%.[[availableLocales]], requestedLocales, options).
388     JSGlobalObject* globalObject = exec->callee()->globalObject();
389     return JSValue::encode(supportedLocales(exec, globalObject->intlCollatorAvailableLocales(), requestedLocales, exec->argument(1)));
390 }
391
392 void IntlCollatorConstructor::visitChildren(JSCell* cell, SlotVisitor& visitor)
393 {
394     IntlCollatorConstructor* thisObject = jsCast<IntlCollatorConstructor*>(cell);
395     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
396
397     Base::visitChildren(thisObject, visitor);
398
399     visitor.append(&thisObject->m_collatorStructure);
400 }
401
402 } // namespace JSC
403
404 #endif // ENABLE(INTL)