From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/builtin/intl/Segmenter.cpp | 988 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 988 insertions(+) create mode 100644 js/src/builtin/intl/Segmenter.cpp (limited to 'js/src/builtin/intl/Segmenter.cpp') diff --git a/js/src/builtin/intl/Segmenter.cpp b/js/src/builtin/intl/Segmenter.cpp new file mode 100644 index 0000000000..b7c82bb135 --- /dev/null +++ b/js/src/builtin/intl/Segmenter.cpp @@ -0,0 +1,988 @@ +/* -*- 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/. */ + +/* Intl.Segmenter implementation. */ + +#include "builtin/intl/Segmenter.h" + +#include "mozilla/Assertions.h" +#include "mozilla/IntegerTypeTraits.h" +#include "mozilla/Range.h" +#include "mozilla/UniquePtr.h" + +#if defined(MOZ_ICU4X) +# include "mozilla/intl/ICU4XGeckoDataProvider.h" +# include "ICU4XGraphemeClusterSegmenter.h" +# include "ICU4XSentenceSegmenter.h" +# include "ICU4XWordSegmenter.h" +#endif + +#include "jspubtd.h" +#include "NamespaceImports.h" + +#include "builtin/Array.h" +#include "builtin/intl/CommonFunctions.h" +#include "gc/AllocKind.h" +#include "gc/GCContext.h" +#include "js/CallArgs.h" +#include "js/PropertyDescriptor.h" +#include "js/PropertySpec.h" +#include "js/RootingAPI.h" +#include "js/StableStringChars.h" +#include "js/TypeDecls.h" +#include "js/Value.h" +#include "util/Unicode.h" +#include "vm/ArrayObject.h" +#include "vm/GlobalObject.h" +#include "vm/JSContext.h" +#include "vm/PlainObject.h" +#include "vm/WellKnownAtom.h" + +#include "vm/JSObject-inl.h" +#include "vm/NativeObject-inl.h" + +using namespace js; + +const JSClassOps SegmenterObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + SegmenterObject::finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +const JSClass SegmenterObject::class_ = { + "Intl.Segmenter", + JSCLASS_HAS_RESERVED_SLOTS(SegmenterObject::SLOT_COUNT) | + JSCLASS_HAS_CACHED_PROTO(JSProto_Segmenter) | + JSCLASS_FOREGROUND_FINALIZE, + &SegmenterObject::classOps_, + &SegmenterObject::classSpec_, +}; + +const JSClass& SegmenterObject::protoClass_ = PlainObject::class_; + +static bool segmenter_toSource(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setString(cx->names().Segmenter); + return true; +} + +static const JSFunctionSpec segmenter_static_methods[] = { + JS_SELF_HOSTED_FN("supportedLocalesOf", "Intl_Segmenter_supportedLocalesOf", + 1, 0), + JS_FS_END, +}; + +static const JSFunctionSpec segmenter_methods[] = { + JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Segmenter_resolvedOptions", 0, + 0), + JS_SELF_HOSTED_FN("segment", "Intl_Segmenter_segment", 1, 0), + JS_FN("toSource", segmenter_toSource, 0, 0), + JS_FS_END, +}; + +static const JSPropertySpec segmenter_properties[] = { + JS_STRING_SYM_PS(toStringTag, "Intl.Segmenter", JSPROP_READONLY), + JS_PS_END, +}; + +static bool Segmenter(JSContext* cx, unsigned argc, Value* vp); + +const ClassSpec SegmenterObject::classSpec_ = { + GenericCreateConstructor, + GenericCreatePrototype, + segmenter_static_methods, + nullptr, + segmenter_methods, + segmenter_properties, + nullptr, + ClassSpec::DontDefineConstructor, +}; + +/** + * Intl.Segmenter ([ locales [ , options ]]) + */ +static bool Segmenter(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (!ThrowIfNotConstructing(cx, args, "Intl.Segmenter")) { + return false; + } + + // Steps 2-3 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + Rooted proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Segmenter, + &proto)) { + return false; + } + + Rooted segmenter(cx); + segmenter = NewObjectWithClassProto(cx, proto); + if (!segmenter) { + return false; + } + + HandleValue locales = args.get(0); + HandleValue options = args.get(1); + + // Steps 4-13. + if (!intl::InitializeObject(cx, segmenter, cx->names().InitializeSegmenter, + locales, options)) { + return false; + } + + // Step 14. + args.rval().setObject(*segmenter); + return true; +} + +const JSClassOps SegmentsObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + SegmentsObject::finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +const JSClass SegmentsObject::class_ = { + "Intl.Segments", + JSCLASS_HAS_RESERVED_SLOTS(SegmentsObject::SLOT_COUNT) | + JSCLASS_FOREGROUND_FINALIZE, + &SegmentsObject::classOps_, +}; + +static const JSFunctionSpec segments_methods[] = { + JS_SELF_HOSTED_FN("containing", "Intl_Segments_containing", 1, 0), + JS_SELF_HOSTED_SYM_FN(iterator, "Intl_Segments_iterator", 0, 0), + JS_FS_END, +}; + +bool GlobalObject::initSegmentsProto(JSContext* cx, + Handle global) { + Rooted proto( + cx, GlobalObject::createBlankPrototype(cx, global)); + if (!proto) { + return false; + } + + if (!JS_DefineFunctions(cx, proto, segments_methods)) { + return false; + } + + global->initBuiltinProto(ProtoKind::SegmentsProto, proto); + return true; +} + +const JSClassOps SegmentIteratorObject::classOps_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + SegmentIteratorObject::finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +const JSClass SegmentIteratorObject::class_ = { + "Intl.SegmentIterator", + JSCLASS_HAS_RESERVED_SLOTS(SegmentIteratorObject::SLOT_COUNT) | + JSCLASS_FOREGROUND_FINALIZE, + &SegmentIteratorObject::classOps_, +}; + +static const JSFunctionSpec segment_iterator_methods[] = { + JS_SELF_HOSTED_FN("next", "Intl_SegmentIterator_next", 0, 0), + JS_FS_END, +}; + +static const JSPropertySpec segment_iterator_properties[] = { + JS_STRING_SYM_PS(toStringTag, "Segmenter String Iterator", JSPROP_READONLY), + JS_PS_END, +}; + +bool GlobalObject::initSegmentIteratorProto(JSContext* cx, + Handle global) { + Rooted iteratorProto( + cx, GlobalObject::getOrCreateIteratorPrototype(cx, global)); + if (!iteratorProto) { + return false; + } + + Rooted proto( + cx, GlobalObject::createBlankPrototypeInheriting( + cx, iteratorProto)); + if (!proto) { + return false; + } + + if (!JS_DefineFunctions(cx, proto, segment_iterator_methods)) { + return false; + } + + if (!JS_DefineProperties(cx, proto, segment_iterator_properties)) { + return false; + } + + global->initBuiltinProto(ProtoKind::SegmentIteratorProto, proto); + return true; +} + +struct Boundaries { + // Start index of this segmentation boundary. + int32_t startIndex = 0; + + // End index of this segmentation boundary. + int32_t endIndex = 0; + + // |true| if the segment is word-like. (Only used for word segmentation.) + bool isWordLike = false; +}; + +/** + * Find the segmentation boundary for the string character whose position is + * |index|. The end position of the last segment boundary is |previousIndex|. + */ +template +static Boundaries FindBoundaryFrom(const T& iter, int32_t previousIndex, + int32_t index) { + MOZ_ASSERT(previousIndex <= index, + "previous index must not exceed the search index"); + + int32_t previous = previousIndex; + while (true) { + // Find the next possible break index. + int32_t next = iter.next(); + + // If |next| is larger than the search index, we've found our segment end + // index. + if (next > index) { + return {previous, next, iter.isWordLike()}; + } + + // Otherwise store |next| as the start index of the next segment, + previous = next; + } +} + +// TODO: Consider switching to the ICU4X C++ headers when the C++ headers +// are in better shape: https://github.com/rust-diplomat/diplomat/issues/280 + +template +class SegmenterBreakIteratorType { + typename Interface::BreakIterator* impl_; + + public: + explicit SegmenterBreakIteratorType(void* impl) + : impl_(static_cast(impl)) { + MOZ_ASSERT(impl); + } + + int32_t next() const { return Interface::next(impl_); } + + bool isWordLike() const { return Interface::isWordLike(impl_); } +}; + +#if defined(MOZ_ICU4X) +// Each SegmenterBreakIterator interface contains the following definitions: +// +// - BreakIterator: Type of the ICU4X break iterator. +// - Segmenter: Type of the ICU4X segmenter. +// - Char: Character type, either `JS::Latin1Char` or `char16_t`. +// - create: Static method to create a new instance of `BreakIterator`. +// - destroy: Static method to destroy an instance of `BreakIterator`. +// - next: Static method to fetch the next break iteration index. +// - isWordLike: Static method to determine if the current segment is word-like. +// +// +// Each Segmenter interface contains the following definitions: +// +// - Segmenter: Type of the ICU4X segmenter. +// - BreakIteratorLatin1: SegmenterBreakIterator interface to Latin1 strings. +// - BreakIteratorTwoByte: SegmenterBreakIterator interface to TwoByte strings. +// - create: Static method to create a new instance of `Segmenter`. +// - destroy: Static method to destroy an instance of `Segmenter`. + +struct GraphemeClusterSegmenterBreakIteratorLatin1 { + using BreakIterator = capi::ICU4XGraphemeClusterBreakIteratorLatin1; + using Segmenter = capi::ICU4XGraphemeClusterSegmenter; + using Char = JS::Latin1Char; + + static constexpr auto& create = + capi::ICU4XGraphemeClusterSegmenter_segment_latin1; + static constexpr auto& destroy = + capi::ICU4XGraphemeClusterBreakIteratorLatin1_destroy; + static constexpr auto& next = + capi::ICU4XGraphemeClusterBreakIteratorLatin1_next; + + static bool isWordLike(const BreakIterator*) { return false; } +}; + +struct GraphemeClusterSegmenterBreakIteratorTwoByte { + using BreakIterator = capi::ICU4XGraphemeClusterBreakIteratorUtf16; + using Segmenter = capi::ICU4XGraphemeClusterSegmenter; + using Char = char16_t; + + static constexpr auto& create = + capi::ICU4XGraphemeClusterSegmenter_segment_utf16; + static constexpr auto& destroy = + capi::ICU4XGraphemeClusterBreakIteratorUtf16_destroy; + static constexpr auto& next = + capi::ICU4XGraphemeClusterBreakIteratorUtf16_next; + + static bool isWordLike(const BreakIterator*) { return false; } +}; + +struct GraphemeClusterSegmenter { + using Segmenter = capi::ICU4XGraphemeClusterSegmenter; + using BreakIteratorLatin1 = + SegmenterBreakIteratorType; + using BreakIteratorTwoByte = + SegmenterBreakIteratorType; + + static constexpr auto& create = capi::ICU4XGraphemeClusterSegmenter_create; + static constexpr auto& destroy = capi::ICU4XGraphemeClusterSegmenter_destroy; +}; + +struct WordSegmenterBreakIteratorLatin1 { + using BreakIterator = capi::ICU4XWordBreakIteratorLatin1; + using Segmenter = capi::ICU4XWordSegmenter; + using Char = JS::Latin1Char; + + static constexpr auto& create = capi::ICU4XWordSegmenter_segment_latin1; + static constexpr auto& destroy = capi::ICU4XWordBreakIteratorLatin1_destroy; + static constexpr auto& next = capi::ICU4XWordBreakIteratorLatin1_next; + static constexpr auto& isWordLike = + capi::ICU4XWordBreakIteratorLatin1_is_word_like; +}; + +struct WordSegmenterBreakIteratorTwoByte { + using BreakIterator = capi::ICU4XWordBreakIteratorUtf16; + using Segmenter = capi::ICU4XWordSegmenter; + using Char = char16_t; + + static constexpr auto& create = capi::ICU4XWordSegmenter_segment_utf16; + static constexpr auto& destroy = capi::ICU4XWordBreakIteratorUtf16_destroy; + static constexpr auto& next = capi::ICU4XWordBreakIteratorUtf16_next; + static constexpr auto& isWordLike = + capi::ICU4XWordBreakIteratorUtf16_is_word_like; +}; + +struct WordSegmenter { + using Segmenter = capi::ICU4XWordSegmenter; + using BreakIteratorLatin1 = + SegmenterBreakIteratorType; + using BreakIteratorTwoByte = + SegmenterBreakIteratorType; + + static constexpr auto& create = capi::ICU4XWordSegmenter_create_auto; + static constexpr auto& destroy = capi::ICU4XWordSegmenter_destroy; +}; + +struct SentenceSegmenterBreakIteratorLatin1 { + using BreakIterator = capi::ICU4XSentenceBreakIteratorLatin1; + using Segmenter = capi::ICU4XSentenceSegmenter; + using Char = JS::Latin1Char; + + static constexpr auto& create = capi::ICU4XSentenceSegmenter_segment_latin1; + static constexpr auto& destroy = + capi::ICU4XSentenceBreakIteratorLatin1_destroy; + static constexpr auto& next = capi::ICU4XSentenceBreakIteratorLatin1_next; + + static bool isWordLike(const BreakIterator*) { return false; } +}; + +struct SentenceSegmenterBreakIteratorTwoByte { + using BreakIterator = capi::ICU4XSentenceBreakIteratorUtf16; + using Segmenter = capi::ICU4XSentenceSegmenter; + using Char = char16_t; + + static constexpr auto& create = capi::ICU4XSentenceSegmenter_segment_utf16; + static constexpr auto& destroy = + capi::ICU4XSentenceBreakIteratorUtf16_destroy; + static constexpr auto& next = capi::ICU4XSentenceBreakIteratorUtf16_next; + + static bool isWordLike(const BreakIterator*) { return false; } +}; + +struct SentenceSegmenter { + using Segmenter = capi::ICU4XSentenceSegmenter; + using BreakIteratorLatin1 = + SegmenterBreakIteratorType; + using BreakIteratorTwoByte = + SegmenterBreakIteratorType; + + static constexpr auto& create = capi::ICU4XSentenceSegmenter_create; + static constexpr auto& destroy = capi::ICU4XSentenceSegmenter_destroy; +}; +#endif + +/** + * Create a new ICU4X segmenter instance. + */ +template +static typename Interface::Segmenter* CreateSegmenter(JSContext* cx) { + auto result = Interface::create(mozilla::intl::GetDataProvider()); + if (!result.is_ok) { + intl::ReportInternalError(cx); + return nullptr; + } + return result.ok; +} + +static bool EnsureInternalsResolved(JSContext* cx, + Handle segmenter) { + if (segmenter->getLocale()) { + return true; + } + + Rooted value(cx); + + Rooted internals(cx, intl::GetInternalsObject(cx, segmenter)); + if (!internals) { + return false; + } + + if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) { + return false; + } + Rooted locale(cx, value.toString()); + + if (!GetProperty(cx, internals, internals, cx->names().granularity, &value)) { + return false; + } + + SegmenterGranularity granularity; + { + JSLinearString* linear = value.toString()->ensureLinear(cx); + if (!linear) { + return false; + } + + if (StringEqualsLiteral(linear, "grapheme")) { + granularity = SegmenterGranularity::Grapheme; + } else if (StringEqualsLiteral(linear, "word")) { + granularity = SegmenterGranularity::Word; + } else { + MOZ_ASSERT(StringEqualsLiteral(linear, "sentence")); + granularity = SegmenterGranularity::Sentence; + } + } + +#if defined(MOZ_ICU4X) + switch (granularity) { + case SegmenterGranularity::Grapheme: { + auto* seg = CreateSegmenter(cx); + if (!seg) { + return false; + } + segmenter->setSegmenter(seg); + break; + } + case SegmenterGranularity::Word: { + auto* seg = CreateSegmenter(cx); + if (!seg) { + return false; + } + segmenter->setSegmenter(seg); + break; + } + case SegmenterGranularity::Sentence: { + auto* seg = CreateSegmenter(cx); + if (!seg) { + return false; + } + segmenter->setSegmenter(seg); + break; + } + } +#endif + + segmenter->setLocale(locale); + segmenter->setGranularity(granularity); + + return true; +} + +/** + * Destroy an ICU4X segmenter instance. + */ +template +static void DestroySegmenter(void* seg) { + auto* segmenter = static_cast(seg); + Interface::destroy(segmenter); +} + +void SegmenterObject::finalize(JS::GCContext* gcx, JSObject* obj) { + MOZ_ASSERT(gcx->onMainThread()); + + auto& segmenter = obj->as(); + if (void* seg = segmenter.getSegmenter()) { +#if defined(MOZ_ICU4X) + switch (segmenter.getGranularity()) { + case SegmenterGranularity::Grapheme: { + DestroySegmenter(seg); + break; + } + case SegmenterGranularity::Word: { + DestroySegmenter(seg); + break; + } + case SegmenterGranularity::Sentence: { + DestroySegmenter(seg); + break; + } + } +#else + MOZ_CRASH("ICU4X disabled"); +#endif + } +} + +/** + * Destroy an ICU4X break iterator instance. + */ +template +static void DestroyBreakIterator(void* brk) { + auto* breakIterator = static_cast(brk); + Interface::destroy(breakIterator); +} + +/** + * Destroy the ICU4X break iterator attached to |segments|. + */ +template +static void DestroyBreakIterator(const T* segments) { +#if defined(MOZ_ICU4X) + void* brk = segments->getBreakIterator(); + MOZ_ASSERT(brk); + + bool isLatin1 = segments->getString()->hasLatin1Chars(); + + switch (segments->getGranularity()) { + case SegmenterGranularity::Grapheme: { + if (isLatin1) { + DestroyBreakIterator(brk); + } else { + DestroyBreakIterator(brk); + } + break; + } + case SegmenterGranularity::Word: { + if (isLatin1) { + DestroyBreakIterator(brk); + } else { + DestroyBreakIterator(brk); + } + break; + } + case SegmenterGranularity::Sentence: { + if (isLatin1) { + DestroyBreakIterator(brk); + } else { + DestroyBreakIterator(brk); + } + break; + } + } +#else + MOZ_CRASH("ICU4X disabled"); +#endif +} + +void SegmentsObject::finalize(JS::GCContext* gcx, JSObject* obj) { + MOZ_ASSERT(gcx->onMainThread()); + + auto* segments = &obj->as(); + bool isLatin1 = segments->getString()->hasLatin1Chars(); + + if (void* chars = segments->getStringChars()) { + size_t length = segments->getString()->length(); + if (isLatin1) { + intl::RemoveICUCellMemory(gcx, segments, length * sizeof(JS::Latin1Char)); + } else { + intl::RemoveICUCellMemory(gcx, segments, length * sizeof(char16_t)); + } + js_free(chars); + } + + if (segments->getBreakIterator()) { + DestroyBreakIterator(segments); + } +} + +void SegmentIteratorObject::finalize(JS::GCContext* gcx, JSObject* obj) { + MOZ_ASSERT(gcx->onMainThread()); + + auto* iterator = &obj->as(); + bool isLatin1 = iterator->getString()->hasLatin1Chars(); + + if (void* chars = iterator->getStringChars()) { + size_t length = iterator->getString()->length(); + if (isLatin1) { + intl::RemoveICUCellMemory(gcx, iterator, length * sizeof(JS::Latin1Char)); + } else { + intl::RemoveICUCellMemory(gcx, iterator, length * sizeof(char16_t)); + } + js_free(chars); + } + + if (iterator->getBreakIterator()) { + DestroyBreakIterator(iterator); + } +} + +template +static Boundaries FindBoundaryFrom(Handle segments, int32_t index) { + MOZ_ASSERT(0 <= index && uint32_t(index) < segments->getString()->length()); + + Iterator iter(segments->getBreakIterator()); + return FindBoundaryFrom(iter, segments->getIndex(), index); +} + +template +static Boundaries GraphemeBoundaries(Handle segments, int32_t index) { +#if defined(MOZ_ICU4X) + if (segments->getString()->hasLatin1Chars()) { + return FindBoundaryFrom( + segments, index); + } + return FindBoundaryFrom( + segments, index); +#else + MOZ_CRASH("ICU4X disabled"); +#endif +} + +template +static Boundaries WordBoundaries(Handle segments, int32_t index) { +#if defined(MOZ_ICU4X) + if (segments->getString()->hasLatin1Chars()) { + return FindBoundaryFrom(segments, + index); + } + return FindBoundaryFrom(segments, index); +#else + MOZ_CRASH("ICU4X disabled"); +#endif +} + +template +static Boundaries SentenceBoundaries(Handle segments, int32_t index) { +#if defined(MOZ_ICU4X) + if (segments->getString()->hasLatin1Chars()) { + return FindBoundaryFrom(segments, + index); + } + return FindBoundaryFrom(segments, + index); +#else + MOZ_CRASH("ICU4X disabled"); +#endif +} + +/** + * Ensure the string characters have been copied into |segments| in preparation + * for passing the string characters to ICU4X. + */ +template +static bool EnsureStringChars(JSContext* cx, Handle segments) { + if (segments->hasStringChars()) { + return true; + } + + Rooted string(cx, segments->getString()->ensureLinear(cx)); + if (!string) { + return false; + } + + size_t length = string->length(); + + JS::AutoCheckCannotGC nogc; + if (string->hasLatin1Chars()) { + auto chars = DuplicateString(cx, string->latin1Chars(nogc), length); + if (!chars) { + return false; + } + segments->setLatin1Chars(chars.release()); + + intl::AddICUCellMemory(segments, length * sizeof(JS::Latin1Char)); + } else { + auto chars = DuplicateString(cx, string->twoByteChars(nogc), length); + if (!chars) { + return false; + } + segments->setTwoByteChars(chars.release()); + + intl::AddICUCellMemory(segments, length * sizeof(char16_t)); + } + return true; +} + +/** + * Create a new ICU4X break iterator instance. + */ +template +static auto* CreateBreakIterator(Handle segments) { + void* segmenter = segments->getSegmenter()->getSegmenter(); + MOZ_ASSERT(segmenter); + + void* chars = segments->getStringChars(); + MOZ_ASSERT(chars); + + size_t length = segments->getString()->length(); + + using Unsigned = typename mozilla::UnsignedStdintTypeForSize::Type; + + auto* seg = static_cast(segmenter); + auto* ch = static_cast(chars); + return Interface::create(seg, ch, length); +} + +/** + * Ensure |segments| has a break iterator whose current segment index is at most + * |index|. + */ +template +static bool EnsureBreakIterator(JSContext* cx, Handle segments, + int32_t index) { + if (segments->getBreakIterator()) { + // Reuse the break iterator if its current segment index is at most |index|. + if (index >= segments->getIndex()) { + return true; + } + + // Reverse iteration not supported. Destroy the previous break iterator and + // start from fresh. + DestroyBreakIterator(segments.get()); + + // Reset internal state. + segments->setBreakIterator(nullptr); + segments->setIndex(0); + } + + // Ensure the string characters can be passed to ICU4X. + if (!EnsureStringChars(cx, segments)) { + return false; + } + +#if defined(MOZ_ICU4X) + bool isLatin1 = segments->getString()->hasLatin1Chars(); + + // Create a new break iterator based on the granularity and character type. + void* brk; + switch (segments->getGranularity()) { + case SegmenterGranularity::Grapheme: { + if (isLatin1) { + brk = CreateBreakIterator( + segments); + } else { + brk = CreateBreakIterator( + segments); + } + break; + } + case SegmenterGranularity::Word: { + if (isLatin1) { + brk = CreateBreakIterator(segments); + } else { + brk = CreateBreakIterator(segments); + } + break; + } + case SegmenterGranularity::Sentence: { + if (isLatin1) { + brk = + CreateBreakIterator(segments); + } else { + brk = CreateBreakIterator( + segments); + } + break; + } + } + + MOZ_RELEASE_ASSERT(brk); + segments->setBreakIterator(brk); + + MOZ_ASSERT(segments->getIndex() == 0, "index is initially zero"); + + return true; +#else + MOZ_CRASH("ICU4X disabled"); +#endif +} + +/** + * Create the boundaries result array for self-hosted code. + */ +static ArrayObject* CreateBoundaries(JSContext* cx, Boundaries boundaries, + SegmenterGranularity granularity) { + auto [startIndex, endIndex, isWordLike] = boundaries; + + auto* result = NewDenseFullyAllocatedArray(cx, 3); + if (!result) { + return nullptr; + } + result->setDenseInitializedLength(3); + result->initDenseElement(0, Int32Value(startIndex)); + result->initDenseElement(1, Int32Value(endIndex)); + if (granularity == SegmenterGranularity::Word) { + result->initDenseElement(2, BooleanValue(isWordLike)); + } else { + result->initDenseElement(2, UndefinedValue()); + } + return result; +} + +template +static ArrayObject* FindSegmentBoundaries(JSContext* cx, Handle segments, + int32_t index) { + // Ensure break iteration can start at |index|. + if (!EnsureBreakIterator(cx, segments, index)) { + return nullptr; + } + + // Find the actual segment boundaries. + Boundaries boundaries{}; + switch (segments->getGranularity()) { + case SegmenterGranularity::Grapheme: { + boundaries = GraphemeBoundaries(segments, index); + break; + } + case SegmenterGranularity::Word: { + boundaries = WordBoundaries(segments, index); + break; + } + case SegmenterGranularity::Sentence: { + boundaries = SentenceBoundaries(segments, index); + break; + } + } + + // Remember the end index of the current boundary segment. + segments->setIndex(boundaries.endIndex); + + return CreateBoundaries(cx, boundaries, segments->getGranularity()); +} + +bool js::intl_CreateSegmentsObject(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + + Rooted segmenter(cx, + &args[0].toObject().as()); + Rooted string(cx, args[1].toString()); + + // Ensure the internal properties are resolved. + if (!EnsureInternalsResolved(cx, segmenter)) { + return false; + } + + Rooted proto( + cx, GlobalObject::getOrCreateSegmentsPrototype(cx, cx->global())); + if (!proto) { + return false; + } + + auto* segments = NewObjectWithGivenProto(cx, proto); + if (!segments) { + return false; + } + + segments->setSegmenter(segmenter); + segments->setGranularity(segmenter->getGranularity()); + segments->setString(string); + segments->setIndex(0); + + args.rval().setObject(*segments); + return true; +} + +bool js::intl_CreateSegmentIterator(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + + Rooted segments(cx, + &args[0].toObject().as()); + + Rooted proto( + cx, GlobalObject::getOrCreateSegmentIteratorPrototype(cx, cx->global())); + if (!proto) { + return false; + } + + auto* iterator = NewObjectWithGivenProto(cx, proto); + if (!iterator) { + return false; + } + + iterator->setSegmenter(segments->getSegmenter()); + iterator->setGranularity(segments->getGranularity()); + iterator->setString(segments->getString()); + iterator->setIndex(0); + + args.rval().setObject(*iterator); + return true; +} + +bool js::intl_FindSegmentBoundaries(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + + Rooted segments(cx, + &args[0].toObject().as()); + + int32_t index = args[1].toInt32(); + MOZ_ASSERT(index >= 0); + MOZ_ASSERT(uint32_t(index) < segments->getString()->length()); + + auto* result = FindSegmentBoundaries( + cx, static_cast>(segments), index); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +bool js::intl_FindNextSegmentBoundaries(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + + Rooted iterator( + cx, &args[0].toObject().as()); + + int32_t index = iterator->getIndex(); + MOZ_ASSERT(index >= 0); + MOZ_ASSERT(uint32_t(index) < iterator->getString()->length()); + + auto* result = FindSegmentBoundaries( + cx, static_cast>(iterator), index); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} -- cgit v1.2.3