| /* |
| * Copyright (C) 2016 Yusuke Suzuki <yusuke.suzuki@sslab.ics.keio.ac.jp> |
| * Copyright (C) 2016 Apple Inc. All rights reserved. |
| * |
| * 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. |
| */ |
| |
| #pragma once |
| |
| #include "BuiltinNames.h" |
| #include "IntlObject.h" |
| #include "JSBoundFunction.h" |
| #include "JSObject.h" |
| #include "ObjectConstructor.h" |
| #include <unicode/ucol.h> |
| |
| namespace JSC { |
| |
| template<typename StringType> |
| static constexpr uint32_t computeTwoCharacters16Code(const StringType& string) |
| { |
| return static_cast<uint16_t>(string.characterAt(0)) | (static_cast<uint32_t>(static_cast<uint16_t>(string.characterAt(1))) << 16); |
| } |
| |
| template<typename Predicate> String bestAvailableLocale(const String& locale, Predicate predicate) |
| { |
| // BestAvailableLocale (availableLocales, locale) |
| // https://tc39.github.io/ecma402/#sec-bestavailablelocale |
| |
| String candidate = locale; |
| while (!candidate.isEmpty()) { |
| if (predicate(candidate)) |
| return candidate; |
| |
| size_t pos = candidate.reverseFind('-'); |
| if (pos == notFound) |
| return String(); |
| |
| if (pos >= 2 && candidate[pos - 2] == '-') |
| pos -= 2; |
| |
| candidate = candidate.substring(0, pos); |
| } |
| |
| return String(); |
| } |
| |
| template<typename Constructor, typename Factory> |
| JSValue constructIntlInstanceWithWorkaroundForLegacyIntlConstructor(JSGlobalObject* globalObject, JSValue thisValue, Constructor* callee, Factory factory) |
| { |
| // FIXME: Workaround to provide compatibility with ECMA-402 1.0 call/apply patterns. |
| // https://bugs.webkit.org/show_bug.cgi?id=153679 |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| auto* instance = factory(vm); |
| RETURN_IF_EXCEPTION(scope, JSValue()); |
| |
| if (thisValue.isObject()) { |
| JSObject* thisObject = asObject(thisValue); |
| ASSERT(!callee->template inherits<JSBoundFunction>(vm)); |
| JSValue prototype = callee->getDirect(vm, vm.propertyNames->prototype); // Passed constructors always have `prototype` which cannot be deleted. |
| ASSERT(prototype); |
| bool hasInstance = JSObject::defaultHasInstance(globalObject, thisObject, prototype); |
| RETURN_IF_EXCEPTION(scope, JSValue()); |
| if (hasInstance) { |
| PropertyDescriptor descriptor(instance, PropertyAttribute::ReadOnly | PropertyAttribute::DontEnum | PropertyAttribute::DontDelete); |
| scope.release(); |
| thisObject->methodTable(vm)->defineOwnProperty(thisObject, globalObject, vm.propertyNames->builtinNames().intlLegacyConstructedSymbol(), descriptor, true); |
| return thisObject; |
| } |
| } |
| return instance; |
| } |
| |
| template<typename InstanceType> |
| InstanceType* unwrapForLegacyIntlConstructor(JSGlobalObject* globalObject, JSValue thisValue, JSObject* constructor) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSObject* thisObject = jsDynamicCast<JSObject*>(vm, thisValue); |
| if (UNLIKELY(!thisObject)) |
| return nullptr; |
| |
| auto* instance = jsDynamicCast<InstanceType*>(vm, thisObject); |
| if (LIKELY(instance)) |
| return instance; |
| |
| ASSERT(!constructor->template inherits<JSBoundFunction>(vm)); |
| JSValue prototype = constructor->getDirect(vm, vm.propertyNames->prototype); // Passed constructors always have `prototype` which cannot be deleted. |
| ASSERT(prototype); |
| bool hasInstance = JSObject::defaultHasInstance(globalObject, thisObject, prototype); |
| RETURN_IF_EXCEPTION(scope, nullptr); |
| if (!hasInstance) |
| return nullptr; |
| |
| JSValue value = thisObject->get(globalObject, vm.propertyNames->builtinNames().intlLegacyConstructedSymbol()); |
| RETURN_IF_EXCEPTION(scope, nullptr); |
| return jsDynamicCast<InstanceType*>(vm, value); |
| } |
| |
| template<typename ResultType> |
| ResultType intlOption(JSGlobalObject* globalObject, JSObject* options, PropertyName property, std::initializer_list<std::pair<ASCIILiteral, ResultType>> values, ASCIILiteral notFoundMessage, ResultType fallback) |
| { |
| // GetOption (options, property, type="string", values, fallback) |
| // https://tc39.github.io/ecma402/#sec-getoption |
| |
| ASSERT(values.size() > 0); |
| |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (!options) |
| return fallback; |
| |
| JSValue value = options->get(globalObject, property); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| if (!value.isUndefined()) { |
| String stringValue = value.toWTFString(globalObject); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| for (const auto& entry : values) { |
| if (entry.first == stringValue) |
| return entry.second; |
| } |
| throwException(globalObject, scope, createRangeError(globalObject, notFoundMessage)); |
| return { }; |
| } |
| |
| return fallback; |
| } |
| |
| template<typename ResultType> |
| ResultType intlStringOrBooleanOption(JSGlobalObject* globalObject, JSObject* options, PropertyName property, ResultType trueValue, ResultType falsyValue, std::initializer_list<std::pair<ASCIILiteral, ResultType>> values, ASCIILiteral notFoundMessage, ResultType fallback) |
| { |
| // https://tc39.es/proposal-intl-numberformat-v3/out/negotiation/diff.html#sec-getstringorbooleanoption |
| |
| ASSERT(values.size() > 0); |
| |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (!options) |
| return fallback; |
| |
| JSValue value = options->get(globalObject, property); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| if (!value.isUndefined()) { |
| if (value.isBoolean() && value.asBoolean()) |
| return trueValue; |
| |
| bool valueBoolean = value.toBoolean(globalObject); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| if (!valueBoolean) |
| return falsyValue; |
| |
| String stringValue = value.toWTFString(globalObject); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| for (const auto& entry : values) { |
| if (entry.first == stringValue) |
| return entry.second; |
| } |
| throwException(globalObject, scope, createRangeError(globalObject, notFoundMessage)); |
| return { }; |
| } |
| |
| return fallback; |
| } |
| |
| ALWAYS_INLINE bool canUseASCIIUCADUCETComparison(UChar character) |
| { |
| return isASCII(character) && ducetLevel1Weights[character]; |
| } |
| |
| template<typename CharacterType1, typename CharacterType2> |
| UCollationResult compareASCIIWithUCADUCETLevel3(const CharacterType1* characters1, const CharacterType2* characters2, unsigned length) |
| { |
| for (unsigned position = 0; position < length; ++position) { |
| auto lhs = characters1[position]; |
| auto rhs = characters2[position]; |
| uint8_t leftWeight = ducetLevel3Weights[lhs]; |
| uint8_t rightWeight = ducetLevel3Weights[rhs]; |
| if (leftWeight == rightWeight) |
| continue; |
| return leftWeight > rightWeight ? UCOL_GREATER : UCOL_LESS; |
| } |
| return UCOL_EQUAL; |
| } |
| |
| template<typename CharacterType1, typename CharacterType2> |
| inline UCollationResult compareASCIIWithUCADUCET(const CharacterType1* characters1, unsigned length1, const CharacterType2* characters2, unsigned length2) |
| { |
| bool notSameCharacters = false; |
| unsigned commonLength = std::min(length1, length2); |
| for (unsigned position = 0; position < commonLength; ++position) { |
| auto lhs = characters1[position]; |
| auto rhs = characters2[position]; |
| ASSERT(canUseASCIIUCADUCETComparison(lhs)); |
| ASSERT(canUseASCIIUCADUCETComparison(rhs)); |
| if (lhs == rhs) |
| continue; |
| notSameCharacters = true; |
| uint8_t leftWeight = ducetLevel1Weights[lhs]; |
| uint8_t rightWeight = ducetLevel1Weights[rhs]; |
| if (leftWeight == rightWeight) |
| continue; |
| return leftWeight > rightWeight ? UCOL_GREATER : UCOL_LESS; |
| } |
| |
| if (length1 == length2) { |
| if (notSameCharacters) |
| return compareASCIIWithUCADUCETLevel3(characters1, characters2, length1); |
| return UCOL_EQUAL; |
| } |
| return length1 > length2 ? UCOL_GREATER : UCOL_LESS; |
| } |
| |
| // https://tc39.es/ecma402/#sec-getoptionsobject |
| inline JSObject* intlGetOptionsObject(JSGlobalObject* globalObject, JSValue options) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| if (options.isUndefined()) |
| return nullptr; |
| if (LIKELY(options.isObject())) |
| return asObject(options); |
| throwTypeError(globalObject, scope, "options argument is not an object or undefined"_s); |
| return nullptr; |
| } |
| |
| // https://tc39.es/ecma402/#sec-coerceoptionstoobject |
| inline JSObject* intlCoerceOptionsToObject(JSGlobalObject* globalObject, JSValue optionsValue) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| if (optionsValue.isUndefined()) |
| return nullptr; |
| JSObject* options = optionsValue.toObject(globalObject); |
| RETURN_IF_EXCEPTION(scope, nullptr); |
| return options; |
| } |
| |
| template<typename Container> |
| JSArray* createArrayFromStringVector(JSGlobalObject* globalObject, const Container& elements) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSArray* result = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), elements.size()); |
| if (!result) { |
| throwOutOfMemoryError(globalObject, scope); |
| return nullptr; |
| } |
| for (unsigned index = 0; index < elements.size(); ++index) { |
| result->putDirectIndex(globalObject, index, jsString(vm, elements[index])); |
| RETURN_IF_EXCEPTION(scope, { }); |
| } |
| return result; |
| } |
| |
| template<typename Container> |
| JSArray* createArrayFromIntVector(JSGlobalObject* globalObject, const Container& elements) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSArray* result = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), elements.size()); |
| if (!result) { |
| throwOutOfMemoryError(globalObject, scope); |
| return nullptr; |
| } |
| for (unsigned index = 0; index < elements.size(); ++index) { |
| result->putDirectIndex(globalObject, index, jsNumber(elements[index])); |
| RETURN_IF_EXCEPTION(scope, { }); |
| } |
| return result; |
| } |
| |
| } // namespace JSC |