diff options
Diffstat (limited to 'js/src/builtin/intl/ListFormat.cpp')
-rw-r--r-- | js/src/builtin/intl/ListFormat.cpp | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/js/src/builtin/intl/ListFormat.cpp b/js/src/builtin/intl/ListFormat.cpp new file mode 100644 index 0000000000..7d11d5acd6 --- /dev/null +++ b/js/src/builtin/intl/ListFormat.cpp @@ -0,0 +1,373 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "builtin/intl/ListFormat.h" + +#include "mozilla/Assertions.h" +#include "mozilla/intl/ListFormat.h" + +#include <stddef.h> + +#include "builtin/Array.h" +#include "builtin/intl/CommonFunctions.h" +#include "builtin/intl/FormatBuffer.h" +#include "gc/GCContext.h" +#include "js/Utility.h" +#include "js/Vector.h" +#include "vm/JSContext.h" +#include "vm/PlainObject.h" // js::PlainObject +#include "vm/StringType.h" +#include "vm/WellKnownAtom.h" // js_*_str + +#include "vm/JSObject-inl.h" +#include "vm/NativeObject-inl.h" +#include "vm/ObjectOperations-inl.h" + +using namespace js; + +const JSClassOps ListFormatObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + ListFormatObject::finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; +const JSClass ListFormatObject::class_ = { + "Intl.ListFormat", + JSCLASS_HAS_RESERVED_SLOTS(ListFormatObject::SLOT_COUNT) | + JSCLASS_HAS_CACHED_PROTO(JSProto_ListFormat) | + JSCLASS_FOREGROUND_FINALIZE, + &ListFormatObject::classOps_, &ListFormatObject::classSpec_}; + +const JSClass& ListFormatObject::protoClass_ = PlainObject::class_; + +static bool listFormat_toSource(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setString(cx->names().ListFormat); + return true; +} + +static const JSFunctionSpec listFormat_static_methods[] = { + JS_SELF_HOSTED_FN("supportedLocalesOf", + "Intl_ListFormat_supportedLocalesOf", 1, 0), + JS_FS_END}; + +static const JSFunctionSpec listFormat_methods[] = { + JS_SELF_HOSTED_FN("resolvedOptions", "Intl_ListFormat_resolvedOptions", 0, + 0), + JS_SELF_HOSTED_FN("format", "Intl_ListFormat_format", 1, 0), + JS_SELF_HOSTED_FN("formatToParts", "Intl_ListFormat_formatToParts", 1, 0), + JS_FN(js_toSource_str, listFormat_toSource, 0, 0), JS_FS_END}; + +static const JSPropertySpec listFormat_properties[] = { + JS_STRING_SYM_PS(toStringTag, "Intl.ListFormat", JSPROP_READONLY), + JS_PS_END}; + +static bool ListFormat(JSContext* cx, unsigned argc, Value* vp); + +const ClassSpec ListFormatObject::classSpec_ = { + GenericCreateConstructor<ListFormat, 0, gc::AllocKind::FUNCTION>, + GenericCreatePrototype<ListFormatObject>, + listFormat_static_methods, + nullptr, + listFormat_methods, + listFormat_properties, + nullptr, + ClassSpec::DontDefineConstructor}; + +/** + * Intl.ListFormat([ locales [, options]]) + */ +static bool ListFormat(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (!ThrowIfNotConstructing(cx, args, "Intl.ListFormat")) { + return false; + } + + // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ListFormat, + &proto)) { + return false; + } + + Rooted<ListFormatObject*> listFormat( + cx, NewObjectWithClassProto<ListFormatObject>(cx, proto)); + if (!listFormat) { + return false; + } + + HandleValue locales = args.get(0); + HandleValue options = args.get(1); + + // Step 3. + if (!intl::InitializeObject(cx, listFormat, cx->names().InitializeListFormat, + locales, options)) { + return false; + } + + args.rval().setObject(*listFormat); + return true; +} + +void js::ListFormatObject::finalize(JS::GCContext* gcx, JSObject* obj) { + MOZ_ASSERT(gcx->onMainThread()); + + mozilla::intl::ListFormat* lf = + obj->as<ListFormatObject>().getListFormatSlot(); + if (lf) { + intl::RemoveICUCellMemory(gcx, obj, ListFormatObject::EstimatedMemoryUse); + delete lf; + } +} + +/** + * Returns a new ListFormat with the locale and list formatting options + * of the given ListFormat. + */ +static mozilla::intl::ListFormat* NewListFormat( + JSContext* cx, Handle<ListFormatObject*> listFormat) { + RootedObject internals(cx, intl::GetInternalsObject(cx, listFormat)); + if (!internals) { + return nullptr; + } + + RootedValue value(cx); + + if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) { + return nullptr; + } + UniqueChars locale = intl::EncodeLocale(cx, value.toString()); + if (!locale) { + return nullptr; + } + + mozilla::intl::ListFormat::Options options; + + using ListFormatType = mozilla::intl::ListFormat::Type; + if (!GetProperty(cx, internals, internals, cx->names().type, &value)) { + return nullptr; + } + { + JSLinearString* strType = value.toString()->ensureLinear(cx); + if (!strType) { + return nullptr; + } + + if (StringEqualsLiteral(strType, "conjunction")) { + options.mType = ListFormatType::Conjunction; + } else if (StringEqualsLiteral(strType, "disjunction")) { + options.mType = ListFormatType::Disjunction; + } else { + MOZ_ASSERT(StringEqualsLiteral(strType, "unit")); + options.mType = ListFormatType::Unit; + } + } + + using ListFormatStyle = mozilla::intl::ListFormat::Style; + if (!GetProperty(cx, internals, internals, cx->names().style, &value)) { + return nullptr; + } + { + JSLinearString* strStyle = value.toString()->ensureLinear(cx); + if (!strStyle) { + return nullptr; + } + + if (StringEqualsLiteral(strStyle, "long")) { + options.mStyle = ListFormatStyle::Long; + } else if (StringEqualsLiteral(strStyle, "short")) { + options.mStyle = ListFormatStyle::Short; + } else { + MOZ_ASSERT(StringEqualsLiteral(strStyle, "narrow")); + options.mStyle = ListFormatStyle::Narrow; + } + } + + auto result = mozilla::intl::ListFormat::TryCreate( + mozilla::MakeStringSpan(locale.get()), options); + + if (result.isOk()) { + return result.unwrap().release(); + } + + js::intl::ReportInternalError(cx, result.unwrapErr()); + return nullptr; +} + +static mozilla::intl::ListFormat* GetOrCreateListFormat( + JSContext* cx, Handle<ListFormatObject*> listFormat) { + // Obtain a cached mozilla::intl::ListFormat object. + mozilla::intl::ListFormat* lf = listFormat->getListFormatSlot(); + if (lf) { + return lf; + } + + lf = NewListFormat(cx, listFormat); + if (!lf) { + return nullptr; + } + listFormat->setListFormatSlot(lf); + + intl::AddICUCellMemory(listFormat, ListFormatObject::EstimatedMemoryUse); + return lf; +} + +/** + * FormatList ( listFormat, list ) + */ +static bool FormatList(JSContext* cx, mozilla::intl::ListFormat* lf, + const mozilla::intl::ListFormat::StringList& list, + MutableHandleValue result) { + intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> formatBuffer(cx); + auto formatResult = lf->Format(list, formatBuffer); + if (formatResult.isErr()) { + js::intl::ReportInternalError(cx, formatResult.unwrapErr()); + return false; + } + + JSString* str = formatBuffer.toString(cx); + if (!str) { + return false; + } + result.setString(str); + return true; +} + +/** + * FormatListToParts ( listFormat, list ) + */ +static bool FormatListToParts(JSContext* cx, mozilla::intl::ListFormat* lf, + const mozilla::intl::ListFormat::StringList& list, + MutableHandleValue result) { + intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx); + mozilla::intl::ListFormat::PartVector parts; + auto formatResult = lf->FormatToParts(list, buffer, parts); + if (formatResult.isErr()) { + intl::ReportInternalError(cx, formatResult.unwrapErr()); + return false; + } + + RootedString overallResult(cx, buffer.toString(cx)); + if (!overallResult) { + return false; + } + + Rooted<ArrayObject*> partsArray( + cx, NewDenseFullyAllocatedArray(cx, parts.length())); + if (!partsArray) { + return false; + } + partsArray->ensureDenseInitializedLength(0, parts.length()); + + RootedObject singlePart(cx); + RootedValue val(cx); + + size_t index = 0; + size_t beginIndex = 0; + for (const mozilla::intl::ListFormat::Part& part : parts) { + singlePart = NewPlainObject(cx); + if (!singlePart) { + return false; + } + + if (part.first == mozilla::intl::ListFormat::PartType::Element) { + val = StringValue(cx->names().element); + } else { + val = StringValue(cx->names().literal); + } + + if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) { + return false; + } + + // There could be an empty string so the endIndex coule be equal to + // beginIndex. + MOZ_ASSERT(part.second >= beginIndex); + JSLinearString* partStr = NewDependentString(cx, overallResult, beginIndex, + part.second - beginIndex); + if (!partStr) { + return false; + } + val = StringValue(partStr); + if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) { + return false; + } + + beginIndex = part.second; + partsArray->initDenseElement(index++, ObjectValue(*singlePart)); + } + + MOZ_ASSERT(index == parts.length()); + MOZ_ASSERT(beginIndex == buffer.length()); + result.setObject(*partsArray); + + return true; +} + +bool js::intl_FormatList(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 3); + + Rooted<ListFormatObject*> listFormat( + cx, &args[0].toObject().as<ListFormatObject>()); + + bool formatToParts = args[2].toBoolean(); + + mozilla::intl::ListFormat* lf = GetOrCreateListFormat(cx, listFormat); + if (!lf) { + return false; + } + + // Collect all strings and their lengths. + // + // 'strings' takes the ownership of those strings, and 'list' will be passed + // to mozilla::intl::ListFormat as a Span. + Vector<UniqueTwoByteChars, mozilla::intl::DEFAULT_LIST_LENGTH> strings(cx); + mozilla::intl::ListFormat::StringList list; + + Rooted<ArrayObject*> listObj(cx, &args[1].toObject().as<ArrayObject>()); + RootedValue value(cx); + uint32_t listLen = listObj->length(); + for (uint32_t i = 0; i < listLen; i++) { + if (!GetElement(cx, listObj, listObj, i, &value)) { + return false; + } + + JSLinearString* linear = value.toString()->ensureLinear(cx); + if (!linear) { + return false; + } + + size_t linearLength = linear->length(); + + UniqueTwoByteChars chars = cx->make_pod_array<char16_t>(linearLength); + if (!chars) { + return false; + } + CopyChars(chars.get(), *linear); + + if (!strings.append(std::move(chars))) { + return false; + } + + if (!list.emplaceBack(strings[i].get(), linearLength)) { + return false; + } + } + + if (formatToParts) { + return FormatListToParts(cx, lf, list, args.rval()); + } + return FormatList(cx, lf, list, args.rval()); +} |