Changeset 286255 in webkit


Ignore:
Timestamp:
Nov 29, 2021 12:29:00 PM (8 months ago)
Author:
ysuzuki@apple.com
Message:

[JSC] Add Intl.NumberFormat.formatRangeToParts
JSTests:

https://bugs.webkit.org/show_bug.cgi?id=233539

Reviewed by Ross Kirsling.

  • stress/intl-numberformat-format-large.js:

(nf.formatRangeToParts.shouldBe.JSON.stringify.nf.formatRangeToParts):
(nf.formatRangeToParts.shouldBe.JSON.stringify.nf2.formatRangeToParts):

  • stress/intl-numberformat-format-range-v3.js:

(nf.formatRange.nf.formatRangeToParts.methods.forEach):
(nf.formatRange.string_appeared_here.forEach): Deleted.

  • stress/intl-numberformat-format-string-v3.js:

(nf.formatRangeToParts.shouldBe.JSON.stringify.nf.formatRangeToParts):
(nf.formatRangeToParts.shouldBe.JSON.stringify.nf2.formatRangeToParts):

  • stress/intl-numberformat-format-string.js:

(nf.formatRangeToParts.shouldBe.JSON.stringify.nf.formatRangeToParts):
(nf.formatRangeToParts.shouldBe.JSON.stringify.nf2.formatRangeToParts):

Source/JavaScriptCore:

https://bugs.webkit.org/show_bug.cgi?id=233540

Reviewed by Ross Kirsling.

This patch implements Intl.NumberFormat#formatRangeToParts if ICU is 69 or greater.
It also cleans up / optimizes existing Intl.NumberFormat#formatToParts implementation.

We first collect all fields generated by ICU. And then, flattening nested fields into
non-overlapping sequence of parts via flattenFields.

  • runtime/IntlNumberFormat.cpp:

(JSC::flattenFields):
(JSC::numberFieldsPracticallyEqual):
(JSC::IntlNumberFormat::formatRangeToPartsInternal):
(JSC::IntlNumberFormat::formatRangeToParts const):
(JSC::IntlNumberFormat::formatToPartsInternal):
(JSC::IntlNumberFormat::formatToParts const):

  • runtime/IntlNumberFormat.h:
  • runtime/IntlNumberFormatPrototype.cpp:

(JSC::IntlNumberFormatPrototype::finishCreation):
(JSC::JSC_DEFINE_HOST_FUNCTION):

  • runtime/IntlRelativeTimeFormat.cpp:

(JSC::IntlRelativeTimeFormat::formatToParts const):

Location:
trunk
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r286251 r286255  
     12021-11-29  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] Add Intl.NumberFormat.formatRangeToParts
     4        https://bugs.webkit.org/show_bug.cgi?id=233539
     5
     6        Reviewed by Ross Kirsling.
     7
     8        * stress/intl-numberformat-format-large.js:
     9        (nf.formatRangeToParts.shouldBe.JSON.stringify.nf.formatRangeToParts):
     10        (nf.formatRangeToParts.shouldBe.JSON.stringify.nf2.formatRangeToParts):
     11        * stress/intl-numberformat-format-range-v3.js:
     12        (nf.formatRange.nf.formatRangeToParts.methods.forEach):
     13        (nf.formatRange.string_appeared_here.forEach): Deleted.
     14        * stress/intl-numberformat-format-string-v3.js:
     15        (nf.formatRangeToParts.shouldBe.JSON.stringify.nf.formatRangeToParts):
     16        (nf.formatRangeToParts.shouldBe.JSON.stringify.nf2.formatRangeToParts):
     17        * stress/intl-numberformat-format-string.js:
     18        (nf.formatRangeToParts.shouldBe.JSON.stringify.nf.formatRangeToParts):
     19        (nf.formatRangeToParts.shouldBe.JSON.stringify.nf2.formatRangeToParts):
     20
    1212021-11-29  Yusuke Suzuki  <ysuzuki@apple.com>
    222
  • trunk/JSTests/stress/intl-numberformat-format-large.js

    r285418 r286255  
    1414    shouldBe(nf2.formatRange(start, end), `987,654,321,987,654,321~987,654,321,987,654,322`);
    1515}
     16if (nf.formatRangeToParts) {
     17    shouldBe(JSON.stringify(nf.formatRangeToParts(0, 30000)), `[{"type":"integer","value":"0","source":"startRange"},{"type":"literal","value":"–","source":"shared"},{"type":"integer","value":"30","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"}]`);
     18    shouldBe(JSON.stringify(nf.formatRangeToParts(start, end)), `[{"type":"integer","value":"987","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"654","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"321","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"987","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"654","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"321","source":"startRange"},{"type":"literal","value":"–","source":"shared"},{"type":"integer","value":"987","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"654","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"321","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"987","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"654","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"322","source":"endRange"}]`);
     19    shouldBe(JSON.stringify(nf2.formatRangeToParts(start, end)), `[{"type":"integer","value":"987","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"654","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"321","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"987","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"654","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"321","source":"startRange"},{"type":"literal","value":"~","source":"shared"},{"type":"integer","value":"987","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"654","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"321","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"987","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"654","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"322","source":"endRange"}]`);
     20}
  • trunk/JSTests/stress/intl-numberformat-format-range-v3.js

    r285418 r286255  
    5151
    5252const nf = new Intl.NumberFormat("en", {signDisplay: "exceptZero"});
    53 if (nf.formatRange) {
    54     ['formatRange'].forEach(function(method) {
     53if (nf.formatRange || nf.formatRangeToParts) {
     54    let methods = [];
     55    if (nf.formatRange)
     56        methods.push("formatRange");
     57    if (nf.formatRangeToParts)
     58        methods.push("formatRangeToParts");
     59    methods.forEach(function(method) {
    5560        shouldBe("function", typeof nf[method]);
    5661
     
    128133                const formatted_x_Y = nf[method](x, Y);
    129134                const formatted_X_Y = nf[method](X, Y);
    130                 shouldBe(formatted_x_y, formatted_X_y);
    131                 shouldBe(formatted_x_y, formatted_x_Y);
    132                 shouldBe(formatted_x_y, formatted_X_Y);
    133 
     135                shouldBe(JSON.stringify(formatted_x_y), JSON.stringify(formatted_X_y));
     136                shouldBe(JSON.stringify(formatted_x_y), JSON.stringify(formatted_x_Y));
     137                shouldBe(JSON.stringify(formatted_x_y), JSON.stringify(formatted_X_Y));
    134138            });
    135139    });
  • trunk/JSTests/stress/intl-numberformat-format-string-v3.js

    r285418 r286255  
    4242    shouldBe(nf2.formatRange(string, string2), `987,654,321,987,654,321~987,654,321,987,654,322`);
    4343}
     44if (nf.formatRangeToParts) {
     45    shouldBe(JSON.stringify(nf.formatRangeToParts(string, string2)), `[{"type":"integer","value":"987","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"654","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"321","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"987","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"654","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"321","source":"startRange"},{"type":"literal","value":"–","source":"shared"},{"type":"integer","value":"987","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"654","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"321","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"987","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"654","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"322","source":"endRange"}]`);
     46    shouldBe(JSON.stringify(nf2.formatRangeToParts(string, string2)), `[{"type":"integer","value":"987","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"654","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"321","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"987","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"654","source":"startRange"},{"type":"group","value":",","source":"startRange"},{"type":"integer","value":"321","source":"startRange"},{"type":"literal","value":"~","source":"shared"},{"type":"integer","value":"987","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"654","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"321","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"987","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"654","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"322","source":"endRange"}]`);
     47}
  • trunk/JSTests/stress/intl-numberformat-format-string.js

    r285418 r286255  
    1717    shouldBe(nf2.formatRange("-0", "1000000000000000000000000000000000000000000000"), `-0 ~ 1,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000`);
    1818}
     19if (nf.formatRangeToParts) {
     20    shouldBe(JSON.stringify(nf.formatRangeToParts("-54.321", "+54.321")), `[{"type":"minusSign","value":"-","source":"startRange"},{"type":"integer","value":"54","source":"startRange"},{"type":"decimal","value":".","source":"startRange"},{"type":"fraction","value":"321","source":"startRange"},{"type":"literal","value":" – ","source":"shared"},{"type":"integer","value":"54","source":"endRange"},{"type":"decimal","value":".","source":"endRange"},{"type":"fraction","value":"321","source":"endRange"}]`);
     21    shouldBe(JSON.stringify(nf.formatRangeToParts("-54.321", "20000000000000000000000000000000000000000")), `[{"type":"minusSign","value":"-","source":"startRange"},{"type":"integer","value":"54","source":"startRange"},{"type":"decimal","value":".","source":"startRange"},{"type":"fraction","value":"321","source":"startRange"},{"type":"literal","value":" – ","source":"shared"},{"type":"integer","value":"20","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"}]`);
     22    shouldBe(JSON.stringify(nf.formatRangeToParts("-0", "0")), `[{"type":"minusSign","value":"-","source":"startRange"},{"type":"integer","value":"0","source":"startRange"},{"type":"literal","value":" – ","source":"shared"},{"type":"integer","value":"0","source":"endRange"}]`);
     23    shouldBe(JSON.stringify(nf.formatRangeToParts("-0", "1000000000000000000000000000000000000000000000")), `[{"type":"minusSign","value":"-","source":"startRange"},{"type":"integer","value":"0","source":"startRange"},{"type":"literal","value":" – ","source":"shared"},{"type":"integer","value":"1","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"}]`);
     24    shouldBe(JSON.stringify(nf2.formatRangeToParts("-54.321", "+54.321")), `[{"type":"minusSign","value":"-","source":"startRange"},{"type":"integer","value":"54","source":"startRange"},{"type":"decimal","value":".","source":"startRange"},{"type":"fraction","value":"321","source":"startRange"},{"type":"literal","value":" ~ ","source":"shared"},{"type":"integer","value":"54","source":"endRange"},{"type":"decimal","value":".","source":"endRange"},{"type":"fraction","value":"321","source":"endRange"}]`);
     25    shouldBe(JSON.stringify(nf2.formatRangeToParts("-54.321", "20000000000000000000000000000000000000000")), `[{"type":"minusSign","value":"-","source":"startRange"},{"type":"integer","value":"54","source":"startRange"},{"type":"decimal","value":".","source":"startRange"},{"type":"fraction","value":"321","source":"startRange"},{"type":"literal","value":" ~ ","source":"shared"},{"type":"integer","value":"20","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"}]`);
     26    shouldBe(JSON.stringify(nf2.formatRangeToParts("-0", "0")), `[{"type":"minusSign","value":"-","source":"startRange"},{"type":"integer","value":"0","source":"startRange"},{"type":"literal","value":" ~ ","source":"shared"},{"type":"integer","value":"0","source":"endRange"}]`);
     27    shouldBe(JSON.stringify(nf2.formatRangeToParts("-0", "1000000000000000000000000000000000000000000000")), `[{"type":"minusSign","value":"-","source":"startRange"},{"type":"integer","value":"0","source":"startRange"},{"type":"literal","value":" ~ ","source":"shared"},{"type":"integer","value":"1","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"},{"type":"group","value":",","source":"endRange"},{"type":"integer","value":"000","source":"endRange"}]`);
     28}
  • trunk/Source/JavaScriptCore/ChangeLog

    r286253 r286255  
     12021-11-29  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] Add Intl.NumberFormat.formatRangeToParts
     4        https://bugs.webkit.org/show_bug.cgi?id=233540
     5
     6        Reviewed by Ross Kirsling.
     7
     8        This patch implements Intl.NumberFormat#formatRangeToParts if ICU is 69 or greater.
     9        It also cleans up / optimizes existing Intl.NumberFormat#formatToParts implementation.
     10
     11        We first collect all fields generated by ICU. And then, flattening nested fields into
     12        non-overlapping sequence of parts via flattenFields.
     13
     14        * runtime/IntlNumberFormat.cpp:
     15        (JSC::flattenFields):
     16        (JSC::numberFieldsPracticallyEqual):
     17        (JSC::IntlNumberFormat::formatRangeToPartsInternal):
     18        (JSC::IntlNumberFormat::formatRangeToParts const):
     19        (JSC::IntlNumberFormat::formatToPartsInternal):
     20        (JSC::IntlNumberFormat::formatToParts const):
     21        * runtime/IntlNumberFormat.h:
     22        * runtime/IntlNumberFormatPrototype.cpp:
     23        (JSC::IntlNumberFormatPrototype::finishCreation):
     24        (JSC::JSC_DEFINE_HOST_FUNCTION):
     25        * runtime/IntlRelativeTimeFormat.cpp:
     26        (JSC::IntlRelativeTimeFormat::formatToParts const):
     27
    1282021-11-29  Michael Catanzaro  <mcatanzaro@gnome.org>
    229
  • trunk/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp

    r285730 r286255  
    7373}
    7474#endif
    75 
    76 struct IntlNumberFormatField {
    77     int32_t type;
    78     size_t size;
    79 };
    8075
    8176IntlNumberFormat* IntlNumberFormat::create(VM& vm, Structure* structure)
     
    896891#endif
    897892
     893static constexpr int32_t literalField = -1;
     894struct IntlNumberFormatField {
     895    int32_t m_field;
     896    WTF::Range<int32_t> m_range;
     897};
     898
     899static Vector<IntlNumberFormatField> flattenFields(Vector<IntlNumberFormatField>&& fields, int32_t formattedStringLength)
     900{
     901    // ICU generates sequence of nested fields, but ECMA402 requires non-overlapping sequence of parts.
     902    // This function flattens nested fields into sequence of non-overlapping parts.
     903    //
     904    // Formatted string: "100,000–20,000,000"
     905    //                    |  |  | | |   |  |
     906    //                    |  B  | | E   F  |
     907    //                    |     | |        |
     908    //                    +--C--+ +---G----+
     909    //                    +--A--+ +---D----+
     910    //
     911    // Ranges ICU generates:
     912    //     A:    (0, 7)   UFIELD_CATEGORY_NUMBER_RANGE_SPAN startRange
     913    //     B:    (3, 4)   UFIELD_CATEGORY_NUMBER group ","
     914    //     C:    (0, 7)   UFIELD_CATEGORY_NUMBER integer
     915    //     D:    (8, 18)  UFIELD_CATEGORY_NUMBER_RANGE_SPAN endRange
     916    //     E:    (10, 11) UFIELD_CATEGORY_NUMBER group ","
     917    //     F:    (14, 15) UFIELD_CATEGORY_NUMBER group ","
     918    //     G:    (8, 18)  UFIELD_CATEGORY_NUMBER integer
     919    //
     920    // Then, we need to generate:
     921    //     A:    (0, 3)   startRange integer
     922    //     B:    (3, 4)   startRange group ","
     923    //     C:    (4, 7)   startRange integer
     924    //     D:    (7, 8)   shared     literal "-"
     925    //     E:    (8, 10)  endRange   integer
     926    //     F:    (10, 11) endRange   group ","
     927    //     G:    (11, 14) endRange   integer
     928    //     H:    (14, 15) endRange   group ","
     929    //     I:    (15, 18) endRange   integer
     930
     931    std::sort(fields.begin(), fields.end(), [](auto& lhs, auto& rhs) {
     932        if (lhs.m_range.begin() < rhs.m_range.begin())
     933            return true;
     934        if (lhs.m_range.begin() > rhs.m_range.begin())
     935            return false;
     936        if (lhs.m_range.end() < rhs.m_range.end())
     937            return false;
     938        if (lhs.m_range.end() > rhs.m_range.end())
     939            return true;
     940        return lhs.m_field < rhs.m_field;
     941    });
     942
     943    Vector<IntlNumberFormatField> flatten;
     944    Vector<IntlNumberFormatField> stack;
     945    // Top-level field covers entire parts, which makes parts "literal".
     946    stack.append(IntlNumberFormatField { literalField, { 0, formattedStringLength } });
     947
     948    unsigned cursor = 0;
     949    int32_t begin = 0;
     950    while (cursor < fields.size()) {
     951        const auto& field = fields[cursor];
     952
     953        // If the new field is out of the current top-most field, roll up and insert a flatten field.
     954        // Because the top-level field in the stack covers all index range, this condition always becomes false
     955        // if stack size is 1.
     956        while (stack.last().m_range.end() < field.m_range.begin()) {
     957            if (begin < stack.last().m_range.end()) {
     958                IntlNumberFormatField flattenField { stack.last().m_field, { begin, stack.last().m_range.end() } };
     959                flatten.append(flattenField);
     960                begin = flattenField.m_range.end();
     961            }
     962            stack.removeLast();
     963        }
     964        ASSERT(!stack.isEmpty()); // At least, top-level field exists.
     965
     966        // If the new field is starting with the same index, diving into the new field by adding it into stack.
     967        if (begin == field.m_range.begin()) {
     968            stack.append(field);
     969            ++cursor;
     970            continue;
     971        }
     972
     973        // If there is a room between the current top-most field and the new field, insert a flatten field.
     974        if (begin < field.m_range.begin()) {
     975            IntlNumberFormatField flattenField { stack.last().m_field, { begin, field.m_range.begin() } };
     976            flatten.append(flattenField);
     977            stack.append(field);
     978            begin = field.m_range.begin();
     979            ++cursor;
     980            continue;
     981        }
     982    }
     983
     984    // Roll up the nested field at the end of the formatted string sequence.
     985    // For example,
     986    //
     987    //      <------------A-------------->
     988    //      <--------B------------>
     989    //      <---C---->
     990    //
     991    // Then, after C finishes, we should insert remaining B and A.
     992    while (!stack.isEmpty()) {
     993        if (begin < stack.last().m_range.end()) {
     994            IntlNumberFormatField flattenField { stack.last().m_field, { begin, stack.last().m_range.end() } };
     995            flatten.append(flattenField);
     996            begin = flattenField.m_range.end();
     997        }
     998        stack.removeLast();
     999    }
     1000
     1001    return flatten;
     1002}
     1003
     1004#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS)
     1005static bool numberFieldsPracticallyEqual(const UFormattedValue* formattedValue, UErrorCode& status)
     1006{
     1007    auto iterator = std::unique_ptr<UConstrainedFieldPosition, ICUDeleter<ucfpos_close>>(ucfpos_open(&status));
     1008    if (U_FAILURE(status))
     1009        return false;
     1010
     1011    // We only care about UFIELD_CATEGORY_NUMBER_RANGE_SPAN category.
     1012    ucfpos_constrainCategory(iterator.get(), UFIELD_CATEGORY_NUMBER_RANGE_SPAN, &status);
     1013    if (U_FAILURE(status))
     1014        return false;
     1015
     1016    bool hasSpan = ufmtval_nextPosition(formattedValue, iterator.get(), &status);
     1017    if (U_FAILURE(status))
     1018        return false;
     1019
     1020    return !hasSpan;
     1021}
     1022
     1023void IntlNumberFormat::formatRangeToPartsInternal(JSGlobalObject* globalObject, Style style, IntlMathematicalValue&& start, IntlMathematicalValue&& end, const UFormattedValue* formattedValue, JSArray* parts)
     1024{
     1025    VM& vm = globalObject->vm();
     1026    auto scope = DECLARE_THROW_SCOPE(vm);
     1027
     1028    UErrorCode status = U_ZERO_ERROR;
     1029
     1030    int32_t formattedStringLength = 0;
     1031    const UChar* formattedStringPointer = ufmtval_getString(formattedValue, &formattedStringLength, &status);
     1032    if (U_FAILURE(status)) {
     1033        throwTypeError(globalObject, scope, "Failed to format number range"_s);
     1034        return;
     1035    }
     1036    String resultString(formattedStringPointer, formattedStringLength);
     1037
     1038    // We care multiple categories (UFIELD_CATEGORY_DATE and UFIELD_CATEGORY_DATE_INTERVAL_SPAN).
     1039    // So we do not constraint iterator.
     1040    auto iterator = std::unique_ptr<UConstrainedFieldPosition, ICUDeleter<ucfpos_close>>(ucfpos_open(&status));
     1041    if (U_FAILURE(status)) {
     1042        throwTypeError(globalObject, scope, "Failed to format number range"_s);
     1043        return;
     1044    }
     1045
     1046    auto sharedString = jsNontrivialString(vm, "shared"_s);
     1047    auto startRangeString = jsNontrivialString(vm, "startRange"_s);
     1048    auto endRangeString = jsNontrivialString(vm, "endRange"_s);
     1049    auto literalString = jsNontrivialString(vm, "literal"_s);
     1050
     1051    WTF::Range<int32_t> startRange { -1, -1 };
     1052    WTF::Range<int32_t> endRange { -1, -1 };
     1053    Vector<IntlNumberFormatField> fields;
     1054
     1055    while (true) {
     1056        bool next = ufmtval_nextPosition(formattedValue, iterator.get(), &status);
     1057        if (U_FAILURE(status)) {
     1058            throwTypeError(globalObject, scope, "Failed to format number range"_s);
     1059            return;
     1060        }
     1061        if (!next)
     1062            break;
     1063
     1064        int32_t category = ucfpos_getCategory(iterator.get(), &status);
     1065        if (U_FAILURE(status)) {
     1066            throwTypeError(globalObject, scope, "Failed to format number range"_s);
     1067            return;
     1068        }
     1069
     1070        int32_t fieldType = ucfpos_getField(iterator.get(), &status);
     1071        if (U_FAILURE(status)) {
     1072            throwTypeError(globalObject, scope, "Failed to format number range"_s);
     1073            return;
     1074        }
     1075
     1076        int32_t beginIndex = 0;
     1077        int32_t endIndex = 0;
     1078        ucfpos_getIndexes(iterator.get(), &beginIndex, &endIndex, &status);
     1079        if (U_FAILURE(status)) {
     1080            throwTypeError(globalObject, scope, "Failed to format number interval"_s);
     1081            return;
     1082        }
     1083
     1084        dataLogLnIf(IntlNumberFormatInternal::verbose, category, " ", fieldType, " (", beginIndex, ", ", endIndex, ")");
     1085
     1086        if (category != UFIELD_CATEGORY_NUMBER && category != UFIELD_CATEGORY_NUMBER_RANGE_SPAN)
     1087            continue;
     1088        if (category == UFIELD_CATEGORY_NUMBER && fieldType < 0)
     1089            continue;
     1090
     1091        if (category == UFIELD_CATEGORY_NUMBER_RANGE_SPAN) {
     1092            // > The special field category UFIELD_CATEGORY_NUMBER_RANGE_SPAN is used to indicate which number
     1093            // > primitives came from which arguments: 0 means start, and 1 means end. The span category
     1094            // > will always occur before the corresponding fields in UFIELD_CATEGORY_NUMBER in the nextPosition() iterator.
     1095            // from ICU comment. So, field 0 is startRange, field 1 is endRange.
     1096            if (!fieldType)
     1097                startRange = WTF::Range<int32_t>(beginIndex, endIndex);
     1098            else {
     1099                ASSERT(fieldType == 1);
     1100                endRange = WTF::Range<int32_t>(beginIndex, endIndex);
     1101            }
     1102            continue;
     1103        }
     1104
     1105        ASSERT(category == UFIELD_CATEGORY_NUMBER);
     1106
     1107        fields.append(IntlNumberFormatField { fieldType, { beginIndex, endIndex } });
     1108    }
     1109
     1110    auto flatten = flattenFields(WTFMove(fields), formattedStringLength);
     1111
     1112    auto createPart = [&] (JSString* type, int32_t beginIndex, int32_t length) {
     1113        auto sourceType = [&](int32_t index) -> JSString* {
     1114            if (startRange.contains(index))
     1115                return startRangeString;
     1116            if (endRange.contains(index))
     1117                return endRangeString;
     1118            return sharedString;
     1119        };
     1120
     1121        auto value = jsString(vm, resultString.substring(beginIndex, length));
     1122        JSObject* part = constructEmptyObject(globalObject);
     1123        part->putDirect(vm, vm.propertyNames->type, type);
     1124        part->putDirect(vm, vm.propertyNames->value, value);
     1125        part->putDirect(vm, vm.propertyNames->source, sourceType(beginIndex));
     1126        return part;
     1127    };
     1128
     1129    for (auto& field : flatten) {
     1130        bool sign = false;
     1131        IntlMathematicalValue::NumberType numberType = start.numberType();
     1132        if (startRange.contains(field.m_range.begin())) {
     1133            numberType = start.numberType();
     1134            sign = start.sign();
     1135        } else {
     1136            numberType = end.numberType();
     1137            sign = end.sign();
     1138        }
     1139        auto fieldType = field.m_field;
     1140        auto partType = fieldType == literalField ? literalString : jsString(vm, partTypeString(UNumberFormatFields(fieldType), style, sign, numberType));
     1141        JSObject* part = createPart(partType, field.m_range.begin(), field.m_range.distance());
     1142        parts->push(globalObject, part);
     1143        RETURN_IF_EXCEPTION(scope, void());
     1144    }
     1145}
     1146
     1147JSValue IntlNumberFormat::formatRangeToParts(JSGlobalObject* globalObject, double start, double end) const
     1148{
     1149    VM& vm = globalObject->vm();
     1150    auto scope = DECLARE_THROW_SCOPE(vm);
     1151
     1152    ASSERT(m_numberRangeFormatter);
     1153
     1154    if (std::isnan(start) || std::isnan(end))
     1155        return throwRangeError(globalObject, scope, "Passed numbers are out of range"_s);
     1156
     1157    if (end < start)
     1158        return throwRangeError(globalObject, scope, "start is larger than end"_s);
     1159
     1160    if (isNegativeZero(end) && start >= 0)
     1161        return throwRangeError(globalObject, scope, "start is larger than end"_s);
     1162
     1163    UErrorCode status = U_ZERO_ERROR;
     1164    auto range = std::unique_ptr<UFormattedNumberRange, ICUDeleter<unumrf_closeResult>>(unumrf_openResult(&status));
     1165    if (U_FAILURE(status))
     1166        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     1167
     1168    unumrf_formatDoubleRange(m_numberRangeFormatter.get(), start, end, range.get(), &status);
     1169    if (U_FAILURE(status))
     1170        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     1171
     1172    auto* formattedValue = unumrf_resultAsValue(range.get(), &status);
     1173    if (U_FAILURE(status))
     1174        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     1175
     1176    bool equal = numberFieldsPracticallyEqual(formattedValue, status);
     1177    if (U_FAILURE(status)) {
     1178        throwTypeError(globalObject, scope, "Failed to format number range"_s);
     1179        return { };
     1180    }
     1181
     1182    if (equal)
     1183        RELEASE_AND_RETURN(scope, formatToParts(globalObject, start, jsNontrivialString(vm, "shared"_s)));
     1184
     1185    JSArray* parts = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
     1186    if (!parts) {
     1187        throwOutOfMemoryError(globalObject, scope);
     1188        return { };
     1189    }
     1190
     1191    formatRangeToPartsInternal(globalObject, m_style, IntlMathematicalValue(start), IntlMathematicalValue(end), formattedValue, parts);
     1192    RETURN_IF_EXCEPTION(scope, { });
     1193
     1194    return parts;
     1195}
     1196
     1197JSValue IntlNumberFormat::formatRangeToParts(JSGlobalObject* globalObject, IntlMathematicalValue&& start, IntlMathematicalValue&& end) const
     1198{
     1199    VM& vm = globalObject->vm();
     1200    auto scope = DECLARE_THROW_SCOPE(vm);
     1201
     1202    ASSERT(m_numberRangeFormatter);
     1203
     1204    if (start.numberType() == IntlMathematicalValue::NumberType::NaN || end.numberType() == IntlMathematicalValue::NumberType::NaN)
     1205        return throwRangeError(globalObject, scope, "Passed numbers are out of range"_s);
     1206
     1207    start.ensureNonDouble();
     1208    const auto& startString = start.getString();
     1209
     1210    end.ensureNonDouble();
     1211    const auto& endString = end.getString();
     1212
     1213    UErrorCode status = U_ZERO_ERROR;
     1214    auto range = std::unique_ptr<UFormattedNumberRange, ICUDeleter<unumrf_closeResult>>(unumrf_openResult(&status));
     1215    if (U_FAILURE(status))
     1216        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     1217
     1218    unumrf_formatDecimalRange(m_numberRangeFormatter.get(), startString.data(), startString.length(), endString.data(), endString.length(), range.get(), &status);
     1219    if (U_FAILURE(status))
     1220        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     1221
     1222    auto* formattedValue = unumrf_resultAsValue(range.get(), &status);
     1223    if (U_FAILURE(status))
     1224        return throwTypeError(globalObject, scope, "failed to format a range"_s);
     1225
     1226    bool equal = numberFieldsPracticallyEqual(formattedValue, status);
     1227    if (U_FAILURE(status)) {
     1228        throwTypeError(globalObject, scope, "Failed to format number range"_s);
     1229        return { };
     1230    }
     1231
     1232    if (equal)
     1233        RELEASE_AND_RETURN(scope, formatToParts(globalObject, WTFMove(start), jsNontrivialString(vm, "shared"_s)));
     1234
     1235    JSArray* parts = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
     1236    if (!parts) {
     1237        throwOutOfMemoryError(globalObject, scope);
     1238        return { };
     1239    }
     1240
     1241    formatRangeToPartsInternal(globalObject, m_style, WTFMove(start), WTFMove(end), formattedValue, parts);
     1242    RETURN_IF_EXCEPTION(scope, { });
     1243
     1244    return parts;
     1245}
     1246#endif
     1247
    8981248ASCIILiteral IntlNumberFormat::styleString(Style style)
    8991249{
     
    11251475}
    11261476
    1127 void IntlNumberFormat::formatToPartsInternal(JSGlobalObject* globalObject, Style style, bool sign, IntlMathematicalValue::NumberType numberType, const String& formatted, IntlFieldIterator& iterator, JSArray* parts, JSString* unit)
     1477void IntlNumberFormat::formatToPartsInternal(JSGlobalObject* globalObject, Style style, bool sign, IntlMathematicalValue::NumberType numberType, const String& formatted, IntlFieldIterator& iterator, JSArray* parts, JSString* sourceType, JSString* unit)
    11281478{
    11291479    VM& vm = globalObject->vm();
     
    11321482    auto stringLength = formatted.length();
    11331483
    1134     int32_t literalFieldType = -1;
    1135     IntlNumberFormatField literalField { literalFieldType, stringLength };
    1136     Vector<IntlNumberFormatField, 32> fields(stringLength, literalField);
    1137     int32_t beginIndex = 0;
    1138     int32_t endIndex = 0;
    1139     UErrorCode status = U_ZERO_ERROR;
    1140     auto fieldType = iterator.next(beginIndex, endIndex, status);
    1141     if (U_FAILURE(status)) {
    1142         throwTypeError(globalObject, scope, "Failed to iterate field position iterator"_s);
    1143         return;
    1144     }
    1145     while (fieldType >= 0) {
    1146         size_t size = endIndex - beginIndex;
    1147         for (auto i = beginIndex; i < endIndex; ++i) {
    1148             // Only override previous value if new value is more specific.
    1149             if (fields[i].size >= size)
    1150                 fields[i] = IntlNumberFormatField { fieldType, size };
    1151         }
    1152         fieldType = iterator.next(beginIndex, endIndex, status);
     1484    Vector<IntlNumberFormatField> fields;
     1485
     1486    while (true) {
     1487        int32_t beginIndex = 0;
     1488        int32_t endIndex = 0;
     1489        UErrorCode status = U_ZERO_ERROR;
     1490        int32_t fieldType = iterator.next(beginIndex, endIndex, status);
    11531491        if (U_FAILURE(status)) {
    11541492            throwTypeError(globalObject, scope, "Failed to iterate field position iterator"_s);
    11551493            return;
    11561494        }
    1157     }
     1495        if (fieldType < 0)
     1496            break;
     1497
     1498        fields.append(IntlNumberFormatField { fieldType, { beginIndex, endIndex } });
     1499    }
     1500
     1501    auto flatten = flattenFields(WTFMove(fields), stringLength);
    11581502
    11591503    auto literalString = jsNontrivialString(vm, "literal"_s);
     
    11621506        unitName = Identifier::fromString(vm, "unit");
    11631507
    1164     size_t currentIndex = 0;
    1165     while (currentIndex < stringLength) {
    1166         auto startIndex = currentIndex;
    1167         auto fieldType = fields[currentIndex].type;
    1168         while (currentIndex < stringLength && fields[currentIndex].type == fieldType)
    1169             ++currentIndex;
    1170         auto partType = fieldType == literalFieldType ? literalString : jsString(vm, partTypeString(UNumberFormatFields(fieldType), style, sign, numberType));
    1171         auto partValue = jsSubstring(vm, formatted, startIndex, currentIndex - startIndex);
     1508    for (auto& field : flatten) {
     1509        auto fieldType = field.m_field;
     1510        auto partType = fieldType == literalField ? literalString : jsString(vm, partTypeString(UNumberFormatFields(fieldType), style, sign, numberType));
     1511        auto partValue = jsSubstring(vm, formatted, field.m_range.begin(), field.m_range.distance());
    11721512        JSObject* part = constructEmptyObject(globalObject);
    11731513        part->putDirect(vm, vm.propertyNames->type, partType);
     
    11751515        if (unit)
    11761516            part->putDirect(vm, unitName, unit);
     1517        if (sourceType)
     1518            part->putDirect(vm, vm.propertyNames->source, sourceType);
    11771519        parts->push(globalObject, part);
    11781520        RETURN_IF_EXCEPTION(scope, void());
     
    11811523
    11821524// https://tc39.github.io/ecma402/#sec-formatnumbertoparts
    1183 JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, double value) const
     1525JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, double value, JSString* sourceType) const
    11841526{
    11851527    VM& vm = globalObject->vm();
     
    12211563        return throwOutOfMemoryError(globalObject, scope);
    12221564
    1223     formatToPartsInternal(globalObject, m_style, std::signbit(value), IntlMathematicalValue::numberTypeFromDouble(value), resultString, iterator, parts);
     1565    formatToPartsInternal(globalObject, m_style, std::signbit(value), IntlMathematicalValue::numberTypeFromDouble(value), resultString, iterator, parts, sourceType, nullptr);
    12241566    RETURN_IF_EXCEPTION(scope, { });
    12251567
     
    12281570
    12291571#if HAVE(ICU_U_NUMBER_FORMATTER)
    1230 JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, IntlMathematicalValue&& value) const
     1572JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, IntlMathematicalValue&& value, JSString* sourceType) const
    12311573{
    12321574    VM& vm = globalObject->vm();
     
    12671609        return throwOutOfMemoryError(globalObject, scope);
    12681610
    1269     formatToPartsInternal(globalObject, m_style, value.sign(), value.numberType(), resultString, iterator, parts);
     1611    formatToPartsInternal(globalObject, m_style, value.sign(), value.numberType(), resultString, iterator, parts, sourceType, nullptr);
    12701612    RETURN_IF_EXCEPTION(scope, { });
    12711613
  • trunk/Source/JavaScriptCore/runtime/IntlNumberFormat.h

    r285418 r286255  
    4747#endif
    4848
     49#if !defined(HAVE_ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS)
     50#if U_ICU_VERSION_MAJOR_NUM >= 69
     51#define HAVE_ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS 1
     52#endif
     53#endif
     54
     55struct UFormattedValue;
    4956struct UNumberFormatter;
    5057struct UNumberRangeFormatter;
     
    171178    JSValue format(JSGlobalObject*, double) const;
    172179    JSValue format(JSGlobalObject*, IntlMathematicalValue&&) const;
    173     JSValue formatToParts(JSGlobalObject*, double) const;
     180    JSValue formatToParts(JSGlobalObject*, double, JSString* sourceType = nullptr) const;
    174181#if HAVE(ICU_U_NUMBER_FORMATTER)
    175     JSValue formatToParts(JSGlobalObject*, IntlMathematicalValue&&) const;
     182    JSValue formatToParts(JSGlobalObject*, IntlMathematicalValue&&, JSString* sourceType = nullptr) const;
    176183#endif
    177184    JSObject* resolvedOptions(JSGlobalObject*) const;
     
    182189#endif
    183190
     191#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS)
     192    JSValue formatRangeToParts(JSGlobalObject*, double, double) const;
     193    JSValue formatRangeToParts(JSGlobalObject*, IntlMathematicalValue&&, IntlMathematicalValue&&) const;
     194#endif
     195
    184196    JSBoundFunction* boundFormat() const { return m_boundFormat.get(); }
    185197    void setBoundFormat(VM&, JSBoundFunction*);
     
    187199    enum class Style : uint8_t { Decimal, Percent, Currency, Unit };
    188200
    189     static void formatToPartsInternal(JSGlobalObject*, Style, bool sign, IntlMathematicalValue::NumberType, const String& formatted, IntlFieldIterator&, JSArray*, JSString* unit = nullptr);
     201    static void formatToPartsInternal(JSGlobalObject*, Style, bool sign, IntlMathematicalValue::NumberType, const String& formatted, IntlFieldIterator&, JSArray*, JSString* sourceType, JSString* unit);
     202    static void formatRangeToPartsInternal(JSGlobalObject*, Style, IntlMathematicalValue&&, IntlMathematicalValue&&, const UFormattedValue*, JSArray*);
    190203
    191204    template<typename IntlType>
  • trunk/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp

    r285730 r286255  
    4444#endif
    4545
     46#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS)
     47static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRangeToParts);
     48#endif
     49
    4650}
    4751
     
    8589#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER)
    8690    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("formatRange", intlNumberFormatPrototypeFuncFormatRange, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
     91#endif
     92#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS)
     93    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("formatRangeToParts", intlNumberFormatPrototypeFuncFormatRangeToParts, static_cast<unsigned>(PropertyAttribute::DontEnum), 2);
    8794#endif
    8895}
     
    194201}
    195202
     203#if HAVE(ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS)
     204JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRangeToParts, (JSGlobalObject* globalObject, CallFrame* callFrame))
     205{
     206    VM& vm = globalObject->vm();
     207    auto scope = DECLARE_THROW_SCOPE(vm);
     208
     209    // Do not use unwrapForOldFunctions.
     210    auto* numberFormat = jsDynamicCast<IntlNumberFormat*>(vm, callFrame->thisValue());
     211    if (UNLIKELY(!numberFormat))
     212        return JSValue::encode(throwTypeError(globalObject, scope, "Intl.NumberFormat.prototype.formatRangeToParts called on value that's not a NumberFormat"_s));
     213
     214    JSValue startValue = callFrame->argument(0);
     215    JSValue endValue = callFrame->argument(1);
     216
     217    if (startValue.isUndefined() || endValue.isUndefined())
     218        return throwVMTypeError(globalObject, scope, "start or end is undefined"_s);
     219
     220    auto start = toIntlMathematicalValue(globalObject, startValue);
     221    RETURN_IF_EXCEPTION(scope, { });
     222
     223    auto end = toIntlMathematicalValue(globalObject, endValue);
     224    RETURN_IF_EXCEPTION(scope, { });
     225
     226    if (auto startNumber = start.tryGetDouble()) {
     227        if (auto endNumber = end.tryGetDouble())
     228            RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatRangeToParts(globalObject, startNumber.value(), endNumber.value())));
     229    }
     230
     231    RELEASE_AND_RETURN(scope, JSValue::encode(numberFormat->formatRangeToParts(globalObject, WTFMove(start), WTFMove(end))));
     232}
     233#endif
     234
    196235JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncResolvedOptions, (JSGlobalObject* globalObject, CallFrame* callFrame))
    197236{
  • trunk/Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp

    r285730 r286255  
    319319
    320320        IntlFieldIterator fieldIterator(*iterator.get());
    321         IntlNumberFormat::formatToPartsInternal(globalObject, IntlNumberFormat::Style::Decimal, std::signbit(absValue), IntlMathematicalValue::numberTypeFromDouble(absValue), formattedNumber, fieldIterator, parts, jsString(vm, singularUnit(unit).toString()));
     321        IntlNumberFormat::formatToPartsInternal(globalObject, IntlNumberFormat::Style::Decimal, std::signbit(absValue), IntlMathematicalValue::numberTypeFromDouble(absValue), formattedNumber, fieldIterator, parts, nullptr, jsString(vm, singularUnit(unit).toString()));
    322322        RETURN_IF_EXCEPTION(scope, { });
    323323    }
  • trunk/Source/WTF/wtf/Range.h

    r258123 r286255  
    106106    Type end() const { return m_end; }
    107107
     108    Type distance() const { return end() - begin(); }
     109
    108110    bool overlaps(const Range& other) const
    109111    {
Note: See TracChangeset for help on using the changeset viewer.