diff options
3 files changed, 263 insertions, 255 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp index b03d74fa32..0ae0de1d88 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp @@ -4,7 +4,12 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include <AK/StringBuilder.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/Intl/ListFormat.h> +#include <LibJS/Runtime/IteratorOperations.h> +#include <LibUnicode/Locale.h> namespace JS::Intl { @@ -68,4 +73,250 @@ StringView ListFormat::style_string() const } } +// 13.1.1 DeconstructPattern ( pattern, placeables ), https://tc39.es/ecma402/#sec-deconstructpattern +Vector<PatternPartition> deconstruct_pattern(StringView pattern, Placeables placeables) +{ + // 1. Let patternParts be PartitionPattern(pattern). + auto pattern_parts = partition_pattern(pattern); + + // 2. Let result be a new empty List. + Vector<PatternPartition> result {}; + + // 3. For each Record { [[Type]], [[Value]] } patternPart of patternParts, do + for (auto const& pattern_part : pattern_parts) { + // a. Let part be patternPart.[[Type]]. + auto part = pattern_part.type; + + // b. If part is "literal", then + if (part == "literal"sv) { + // i. Append Record { [[Type]]: "literal", [[Value]]: patternPart.[[Value]] } to result. + result.append({ part, pattern_part.value }); + } + // c. Else, + else { + // i. Assert: placeables has a field [[<part>]]. + // ii. Let subst be placeables.[[<part>]]. + auto subst = placeables.get(part); + VERIFY(subst.has_value()); + + subst.release_value().visit( + // iii. If Type(subst) is List, then + [&](Vector<PatternPartition>& partition) { + // 1. For each element s of subst, do + for (auto& element : partition) { + // a. Append s to result. + result.append(move(element)); + } + }, + // iv. Else, + [&](PatternPartition& partition) { + // 1. Append subst to result. + result.append(move(partition)); + }); + } + } + + // 4. Return result. + return result; +} + +// 13.1.2 CreatePartsFromList ( listFormat, list ), https://tc39.es/ecma402/#sec-createpartsfromlist +Vector<PatternPartition> create_parts_from_list(ListFormat const& list_format, Vector<String> const& list) +{ + auto list_patterns = Unicode::get_locale_list_patterns(list_format.locale(), list_format.type_string(), list_format.style_string()); + if (!list_patterns.has_value()) + return {}; + + // 1. Let size be the number of elements of list. + auto size = list.size(); + + // 2. If size is 0, then + if (size == 0) { + // a. Return a new empty List. + return {}; + } + + // 3. If size is 2, then + if (size == 2) { + // a. Let n be an index into listFormat.[[Templates]] based on listFormat.[[Locale]], list[0], and list[1]. + // b. Let pattern be listFormat.[[Templates]][n].[[Pair]]. + auto pattern = list_patterns->pair; + + // c. Let first be a new Record { [[Type]]: "element", [[Value]]: list[0] }. + PatternPartition first { "element"sv, list[0] }; + + // d. Let second be a new Record { [[Type]]: "element", [[Value]]: list[1] }. + PatternPartition second { "element"sv, list[1] }; + + // e. Let placeables be a new Record { [[0]]: first, [[1]]: second }. + Placeables placeables; + placeables.set("0"sv, move(first)); + placeables.set("1"sv, move(second)); + + // f. Return DeconstructPattern(pattern, placeables). + return deconstruct_pattern(pattern, move(placeables)); + } + + // 4. Let last be a new Record { [[Type]]: "element", [[Value]]: list[size - 1] }. + PatternPartition last { "element"sv, list[size - 1] }; + + // 5. Let parts be « last ». + Vector<PatternPartition> parts { move(last) }; + + // The spec does not say to do this, but because size_t is unsigned, we need to take care not to wrap around 0. + if (size == 1) + return parts; + + // 6. Let i be size - 2. + size_t i = size - 2; + + // 7. Repeat, while i ≥ 0, + do { + // a. Let head be a new Record { [[Type]]: "element", [[Value]]: list[i] }. + PatternPartition head { "element"sv, list[i] }; + + // b. Let n be an implementation-defined index into listFormat.[[Templates]] based on listFormat.[[Locale]], head, and parts. + StringView pattern; + + // c. If i is 0, then + if (i == 0) { + // i. Let pattern be listFormat.[[Templates]][n].[[Start]]. + pattern = list_patterns->start; + } + // d. Else if i is less than size - 2, then + else if (i < (size - 2)) { + // i. Let pattern be listFormat.[[Templates]][n].[[Middle]]. + pattern = list_patterns->middle; + } + // e. Else, + else { + // i. Let pattern be listFormat.[[Templates]][n].[[End]]. + pattern = list_patterns->end; + } + + // f. Let placeables be a new Record { [[0]]: head, [[1]]: parts }. + Placeables placeables; + placeables.set("0"sv, move(head)); + placeables.set("1"sv, move(parts)); + + // g. Set parts to DeconstructPattern(pattern, placeables). + parts = deconstruct_pattern(pattern, move(placeables)); + + // h. Decrement i by 1. + } while (i-- != 0); + + // 8. Return parts. + return parts; +} + +// 13.1.3 FormatList ( listFormat, list ) +String format_list(ListFormat const& list_format, Vector<String> const& list) +{ + // 1. Let parts be CreatePartsFromList(listFormat, list). + auto parts = create_parts_from_list(list_format, list); + + // 2. Let result be an empty String. + StringBuilder result; + + // 3. For each Record { [[Type]], [[Value]] } part in parts, do + for (auto const& part : parts) { + // a. Set result to the string-concatenation of result and part.[[Value]]. + result.append(part.value); + } + + // 4. Return result. + return result.build(); +} + +// 13.1.4 FormatListToParts ( listFormat, list ), https://tc39.es/ecma402/#sec-formatlisttoparts +Array* format_list_to_parts(GlobalObject& global_object, ListFormat const& list_format, Vector<String> const& list) +{ + auto& vm = global_object.vm(); + + // 1. Let parts be CreatePartsFromList(listFormat, list). + auto parts = create_parts_from_list(list_format, list); + + // 2. Let result be ArrayCreate(0). + auto result = Array::create(global_object, 0); + + // 3. Let n be 0. + size_t n = 0; + + // 4. For each Record { [[Type]], [[Value]] } part in parts, do + for (auto const& part : parts) { + // a. Let O be OrdinaryObjectCreate(%Object.prototype%). + auto* object = Object::create(global_object, global_object.object_prototype()); + + // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]). + object->create_data_property_or_throw(vm.names.type, js_string(vm, part.type)); + + // c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]). + object->create_data_property_or_throw(vm.names.value, js_string(vm, part.value)); + + // d. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), O). + result->create_data_property_or_throw(n, object); + + // e. Increment n by 1. + ++n; + } + + // 5. Return result. + return result; +} + +// 13.1.5 StringListFromIterable ( iterable ), https://tc39.es/ecma402/#sec-createstringlistfromiterable +Vector<String> string_list_from_iterable(GlobalObject& global_object, Value iterable) +{ + auto& vm = global_object.vm(); + + // 1. If iterable is undefined, then + if (iterable.is_undefined()) { + // a. Return a new empty List. + return {}; + } + + // 2. Let iteratorRecord be ? GetIterator(iterable). + auto* iterator_record = get_iterator(global_object, iterable); + if (vm.exception()) + return {}; + + // 3. Let list be a new empty List. + Vector<String> list; + + // 4. Let next be true. + Object* next = nullptr; + + // 5. Repeat, while next is not false, + do { + // a. Set next to ? IteratorStep(iteratorRecord). + next = iterator_step(global_object, *iterator_record); + if (vm.exception()) + return {}; + + // b. If next is not false, then + if (next != nullptr) { + // i. Let nextValue be ? IteratorValue(next). + auto next_value = iterator_value(global_object, *next); + if (vm.exception()) + return {}; + + // ii. If Type(nextValue) is not String, then + if (!next_value.is_string()) { + // 1. Let error be ThrowCompletion(a newly created TypeError object). + vm.throw_exception<TypeError>(global_object, ErrorType::NotAString, next_value); + + // 2. Return ? IteratorClose(iteratorRecord, error). + iterator_close(*iterator_record); + return {}; + } + + // iii. Append nextValue to the end of the List list. + list.append(next_value.as_string().string()); + } + } while (next != nullptr); + + // 6. Return list. + return list; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.h index 4224ba49ec..64a2654672 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.h @@ -6,8 +6,12 @@ #pragma once +#include <AK/HashMap.h> #include <AK/String.h> #include <AK/StringView.h> +#include <AK/Variant.h> +#include <AK/Vector.h> +#include <LibJS/Runtime/Intl/AbstractOperations.h> #include <LibJS/Runtime/Object.h> namespace JS::Intl { @@ -50,4 +54,12 @@ private: Style m_style { Style::Invalid }; // [[Style]] }; +using Placeables = HashMap<StringView, Variant<PatternPartition, Vector<PatternPartition>>>; + +Vector<PatternPartition> deconstruct_pattern(StringView pattern, Placeables placeables); +Vector<PatternPartition> create_parts_from_list(ListFormat const& list_format, Vector<String> const& list); +String format_list(ListFormat const& list_format, Vector<String> const& list); +Array* format_list_to_parts(GlobalObject& global_object, ListFormat const& list_format, Vector<String> const& list); +Vector<String> string_list_from_iterable(GlobalObject& global_object, Value iterable); + } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.cpp index fad3eedd89..bf209ac624 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/ListFormatPrototype.cpp @@ -4,18 +4,11 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/HashMap.h> -#include <AK/StringBuilder.h> #include <AK/TypeCasts.h> -#include <AK/Variant.h> -#include <AK/Vector.h> #include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/GlobalObject.h> -#include <LibJS/Runtime/Intl/AbstractOperations.h> #include <LibJS/Runtime/Intl/ListFormat.h> #include <LibJS/Runtime/Intl/ListFormatPrototype.h> -#include <LibJS/Runtime/IteratorOperations.h> -#include <LibUnicode/Locale.h> namespace JS::Intl { @@ -35,254 +28,6 @@ static ListFormat* typed_this(GlobalObject& global_object) return static_cast<ListFormat*>(this_object); } -using Placeables = HashMap<StringView, Variant<PatternPartition, Vector<PatternPartition>>>; - -// 13.1.1 DeconstructPattern ( pattern, placeables ), https://tc39.es/ecma402/#sec-deconstructpattern -static Vector<PatternPartition> deconstruct_pattern(StringView pattern, Placeables placeables) -{ - // 1. Let patternParts be PartitionPattern(pattern). - auto pattern_parts = partition_pattern(pattern); - - // 2. Let result be a new empty List. - Vector<PatternPartition> result {}; - - // 3. For each Record { [[Type]], [[Value]] } patternPart of patternParts, do - for (auto const& pattern_part : pattern_parts) { - // a. Let part be patternPart.[[Type]]. - auto part = pattern_part.type; - - // b. If part is "literal", then - if (part == "literal"sv) { - // i. Append Record { [[Type]]: "literal", [[Value]]: patternPart.[[Value]] } to result. - result.append({ part, pattern_part.value }); - } - // c. Else, - else { - // i. Assert: placeables has a field [[<part>]]. - // ii. Let subst be placeables.[[<part>]]. - auto subst = placeables.get(part); - VERIFY(subst.has_value()); - - subst.release_value().visit( - // iii. If Type(subst) is List, then - [&](Vector<PatternPartition>& partition) { - // 1. For each element s of subst, do - for (auto& element : partition) { - // a. Append s to result. - result.append(move(element)); - } - }, - // iv. Else, - [&](PatternPartition& partition) { - // 1. Append subst to result. - result.append(move(partition)); - }); - } - } - - // 4. Return result. - return result; -} - -// 13.1.2 CreatePartsFromList ( listFormat, list ), https://tc39.es/ecma402/#sec-createpartsfromlist -static Vector<PatternPartition> create_parts_from_list(ListFormat const& list_format, Vector<String> const& list) -{ - auto list_patterns = Unicode::get_locale_list_patterns(list_format.locale(), list_format.type_string(), list_format.style_string()); - if (!list_patterns.has_value()) - return {}; - - // 1. Let size be the number of elements of list. - auto size = list.size(); - - // 2. If size is 0, then - if (size == 0) { - // a. Return a new empty List. - return {}; - } - - // 3. If size is 2, then - if (size == 2) { - // a. Let n be an index into listFormat.[[Templates]] based on listFormat.[[Locale]], list[0], and list[1]. - // b. Let pattern be listFormat.[[Templates]][n].[[Pair]]. - auto pattern = list_patterns->pair; - - // c. Let first be a new Record { [[Type]]: "element", [[Value]]: list[0] }. - PatternPartition first { "element"sv, list[0] }; - - // d. Let second be a new Record { [[Type]]: "element", [[Value]]: list[1] }. - PatternPartition second { "element"sv, list[1] }; - - // e. Let placeables be a new Record { [[0]]: first, [[1]]: second }. - Placeables placeables; - placeables.set("0"sv, move(first)); - placeables.set("1"sv, move(second)); - - // f. Return DeconstructPattern(pattern, placeables). - return deconstruct_pattern(pattern, move(placeables)); - } - - // 4. Let last be a new Record { [[Type]]: "element", [[Value]]: list[size - 1] }. - PatternPartition last { "element"sv, list[size - 1] }; - - // 5. Let parts be « last ». - Vector<PatternPartition> parts { move(last) }; - - // The spec does not say to do this, but because size_t is unsigned, we need to take care not to wrap around 0. - if (size == 1) - return parts; - - // 6. Let i be size - 2. - size_t i = size - 2; - - // 7. Repeat, while i ≥ 0, - do { - // a. Let head be a new Record { [[Type]]: "element", [[Value]]: list[i] }. - PatternPartition head { "element"sv, list[i] }; - - // b. Let n be an implementation-defined index into listFormat.[[Templates]] based on listFormat.[[Locale]], head, and parts. - StringView pattern; - - // c. If i is 0, then - if (i == 0) { - // i. Let pattern be listFormat.[[Templates]][n].[[Start]]. - pattern = list_patterns->start; - } - // d. Else if i is less than size - 2, then - else if (i < (size - 2)) { - // i. Let pattern be listFormat.[[Templates]][n].[[Middle]]. - pattern = list_patterns->middle; - } - // e. Else, - else { - // i. Let pattern be listFormat.[[Templates]][n].[[End]]. - pattern = list_patterns->end; - } - - // f. Let placeables be a new Record { [[0]]: head, [[1]]: parts }. - Placeables placeables; - placeables.set("0"sv, move(head)); - placeables.set("1"sv, move(parts)); - - // g. Set parts to DeconstructPattern(pattern, placeables). - parts = deconstruct_pattern(pattern, move(placeables)); - - // h. Decrement i by 1. - } while (i-- != 0); - - // 8. Return parts. - return parts; -} - -// 13.1.3 FormatList ( listFormat, list ) -static String format_list(ListFormat const& list_format, Vector<String> const& list) -{ - // 1. Let parts be CreatePartsFromList(listFormat, list). - auto parts = create_parts_from_list(list_format, list); - - // 2. Let result be an empty String. - StringBuilder result; - - // 3. For each Record { [[Type]], [[Value]] } part in parts, do - for (auto const& part : parts) { - // a. Set result to the string-concatenation of result and part.[[Value]]. - result.append(part.value); - } - - // 4. Return result. - return result.build(); -} - -// 13.1.4 FormatListToParts ( listFormat, list ), https://tc39.es/ecma402/#sec-formatlisttoparts -static Array* format_list_to_parts(GlobalObject& global_object, ListFormat const& list_format, Vector<String> const& list) -{ - auto& vm = global_object.vm(); - - // 1. Let parts be CreatePartsFromList(listFormat, list). - auto parts = create_parts_from_list(list_format, list); - - // 2. Let result be ArrayCreate(0). - auto result = Array::create(global_object, 0); - - // 3. Let n be 0. - size_t n = 0; - - // 4. For each Record { [[Type]], [[Value]] } part in parts, do - for (auto const& part : parts) { - // a. Let O be OrdinaryObjectCreate(%Object.prototype%). - auto* object = Object::create(global_object, global_object.object_prototype()); - - // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]). - object->create_data_property_or_throw(vm.names.type, js_string(vm, part.type)); - - // c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]). - object->create_data_property_or_throw(vm.names.value, js_string(vm, part.value)); - - // d. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), O). - result->create_data_property_or_throw(n, object); - - // e. Increment n by 1. - ++n; - } - - // 5. Return result. - return result; -} - -// 13.1.5 StringListFromIterable ( iterable ), https://tc39.es/ecma402/#sec-createstringlistfromiterable -static Vector<String> string_list_from_iterable(GlobalObject& global_object, Value iterable) -{ - auto& vm = global_object.vm(); - - // 1. If iterable is undefined, then - if (iterable.is_undefined()) { - // a. Return a new empty List. - return {}; - } - - // 2. Let iteratorRecord be ? GetIterator(iterable). - auto* iterator_record = get_iterator(global_object, iterable); - if (vm.exception()) - return {}; - - // 3. Let list be a new empty List. - Vector<String> list; - - // 4. Let next be true. - Object* next = nullptr; - - // 5. Repeat, while next is not false, - do { - // a. Set next to ? IteratorStep(iteratorRecord). - next = iterator_step(global_object, *iterator_record); - if (vm.exception()) - return {}; - - // b. If next is not false, then - if (next != nullptr) { - // i. Let nextValue be ? IteratorValue(next). - auto next_value = iterator_value(global_object, *next); - if (vm.exception()) - return {}; - - // ii. If Type(nextValue) is not String, then - if (!next_value.is_string()) { - // 1. Let error be ThrowCompletion(a newly created TypeError object). - vm.throw_exception<TypeError>(global_object, ErrorType::NotAString, next_value); - - // 2. Return ? IteratorClose(iteratorRecord, error). - iterator_close(*iterator_record); - return {}; - } - - // iii. Append nextValue to the end of the List list. - list.append(next_value.as_string().string()); - } - } while (next != nullptr); - - // 6. Return list. - return list; -} - // 13.4 Properties of the Intl.ListFormat Prototype Object, https://tc39.es/ecma402/#sec-properties-of-intl-listformat-prototype-object ListFormatPrototype::ListFormatPrototype(GlobalObject& global_object) : Object(*global_object.object_prototype()) |