Changeset 286255 in webkit
- Timestamp:
- Nov 29, 2021 12:29:00 PM (8 months ago)
- Location:
- trunk
- Files:
-
- 11 edited
-
JSTests/ChangeLog (modified) (1 diff)
-
JSTests/stress/intl-numberformat-format-large.js (modified) (1 diff)
-
JSTests/stress/intl-numberformat-format-range-v3.js (modified) (2 diffs)
-
JSTests/stress/intl-numberformat-format-string-v3.js (modified) (1 diff)
-
JSTests/stress/intl-numberformat-format-string.js (modified) (1 diff)
-
Source/JavaScriptCore/ChangeLog (modified) (1 diff)
-
Source/JavaScriptCore/runtime/IntlNumberFormat.cpp (modified) (10 diffs)
-
Source/JavaScriptCore/runtime/IntlNumberFormat.h (modified) (4 diffs)
-
Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp (modified) (3 diffs)
-
Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp (modified) (1 diff)
-
Source/WTF/wtf/Range.h (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/JSTests/ChangeLog
r286251 r286255 1 2021-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 1 21 2021-11-29 Yusuke Suzuki <ysuzuki@apple.com> 2 22 -
trunk/JSTests/stress/intl-numberformat-format-large.js
r285418 r286255 14 14 shouldBe(nf2.formatRange(start, end), `987,654,321,987,654,321~987,654,321,987,654,322`); 15 15 } 16 if (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 51 51 52 52 const nf = new Intl.NumberFormat("en", {signDisplay: "exceptZero"}); 53 if (nf.formatRange) { 54 ['formatRange'].forEach(function(method) { 53 if (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) { 55 60 shouldBe("function", typeof nf[method]); 56 61 … … 128 133 const formatted_x_Y = nf[method](x, Y); 129 134 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)); 134 138 }); 135 139 }); -
trunk/JSTests/stress/intl-numberformat-format-string-v3.js
r285418 r286255 42 42 shouldBe(nf2.formatRange(string, string2), `987,654,321,987,654,321~987,654,321,987,654,322`); 43 43 } 44 if (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 17 17 shouldBe(nf2.formatRange("-0", "1000000000000000000000000000000000000000000000"), `-0 ~ 1,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000`); 18 18 } 19 if (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 1 2021-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 1 28 2021-11-29 Michael Catanzaro <mcatanzaro@gnome.org> 2 29 -
trunk/Source/JavaScriptCore/runtime/IntlNumberFormat.cpp
r285730 r286255 73 73 } 74 74 #endif 75 76 struct IntlNumberFormatField {77 int32_t type;78 size_t size;79 };80 75 81 76 IntlNumberFormat* IntlNumberFormat::create(VM& vm, Structure* structure) … … 896 891 #endif 897 892 893 static constexpr int32_t literalField = -1; 894 struct IntlNumberFormatField { 895 int32_t m_field; 896 WTF::Range<int32_t> m_range; 897 }; 898 899 static 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) 1005 static 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 1023 void 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 1147 JSValue 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 1197 JSValue 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 898 1248 ASCIILiteral IntlNumberFormat::styleString(Style style) 899 1249 { … … 1125 1475 } 1126 1476 1127 void IntlNumberFormat::formatToPartsInternal(JSGlobalObject* globalObject, Style style, bool sign, IntlMathematicalValue::NumberType numberType, const String& formatted, IntlFieldIterator& iterator, JSArray* parts, JSString* unit)1477 void IntlNumberFormat::formatToPartsInternal(JSGlobalObject* globalObject, Style style, bool sign, IntlMathematicalValue::NumberType numberType, const String& formatted, IntlFieldIterator& iterator, JSArray* parts, JSString* sourceType, JSString* unit) 1128 1478 { 1129 1479 VM& vm = globalObject->vm(); … … 1132 1482 auto stringLength = formatted.length(); 1133 1483 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); 1153 1491 if (U_FAILURE(status)) { 1154 1492 throwTypeError(globalObject, scope, "Failed to iterate field position iterator"_s); 1155 1493 return; 1156 1494 } 1157 } 1495 if (fieldType < 0) 1496 break; 1497 1498 fields.append(IntlNumberFormatField { fieldType, { beginIndex, endIndex } }); 1499 } 1500 1501 auto flatten = flattenFields(WTFMove(fields), stringLength); 1158 1502 1159 1503 auto literalString = jsNontrivialString(vm, "literal"_s); … … 1162 1506 unitName = Identifier::fromString(vm, "unit"); 1163 1507 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()); 1172 1512 JSObject* part = constructEmptyObject(globalObject); 1173 1513 part->putDirect(vm, vm.propertyNames->type, partType); … … 1175 1515 if (unit) 1176 1516 part->putDirect(vm, unitName, unit); 1517 if (sourceType) 1518 part->putDirect(vm, vm.propertyNames->source, sourceType); 1177 1519 parts->push(globalObject, part); 1178 1520 RETURN_IF_EXCEPTION(scope, void()); … … 1181 1523 1182 1524 // https://tc39.github.io/ecma402/#sec-formatnumbertoparts 1183 JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, double value ) const1525 JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, double value, JSString* sourceType) const 1184 1526 { 1185 1527 VM& vm = globalObject->vm(); … … 1221 1563 return throwOutOfMemoryError(globalObject, scope); 1222 1564 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); 1224 1566 RETURN_IF_EXCEPTION(scope, { }); 1225 1567 … … 1228 1570 1229 1571 #if HAVE(ICU_U_NUMBER_FORMATTER) 1230 JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, IntlMathematicalValue&& value ) const1572 JSValue IntlNumberFormat::formatToParts(JSGlobalObject* globalObject, IntlMathematicalValue&& value, JSString* sourceType) const 1231 1573 { 1232 1574 VM& vm = globalObject->vm(); … … 1267 1609 return throwOutOfMemoryError(globalObject, scope); 1268 1610 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); 1270 1612 RETURN_IF_EXCEPTION(scope, { }); 1271 1613 -
trunk/Source/JavaScriptCore/runtime/IntlNumberFormat.h
r285418 r286255 47 47 #endif 48 48 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 55 struct UFormattedValue; 49 56 struct UNumberFormatter; 50 57 struct UNumberRangeFormatter; … … 171 178 JSValue format(JSGlobalObject*, double) const; 172 179 JSValue format(JSGlobalObject*, IntlMathematicalValue&&) const; 173 JSValue formatToParts(JSGlobalObject*, double ) const;180 JSValue formatToParts(JSGlobalObject*, double, JSString* sourceType = nullptr) const; 174 181 #if HAVE(ICU_U_NUMBER_FORMATTER) 175 JSValue formatToParts(JSGlobalObject*, IntlMathematicalValue&& ) const;182 JSValue formatToParts(JSGlobalObject*, IntlMathematicalValue&&, JSString* sourceType = nullptr) const; 176 183 #endif 177 184 JSObject* resolvedOptions(JSGlobalObject*) const; … … 182 189 #endif 183 190 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 184 196 JSBoundFunction* boundFormat() const { return m_boundFormat.get(); } 185 197 void setBoundFormat(VM&, JSBoundFunction*); … … 187 199 enum class Style : uint8_t { Decimal, Percent, Currency, Unit }; 188 200 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*); 190 203 191 204 template<typename IntlType> -
trunk/Source/JavaScriptCore/runtime/IntlNumberFormatPrototype.cpp
r285730 r286255 44 44 #endif 45 45 46 #if HAVE(ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS) 47 static JSC_DECLARE_HOST_FUNCTION(intlNumberFormatPrototypeFuncFormatRangeToParts); 48 #endif 49 46 50 } 47 51 … … 85 89 #if HAVE(ICU_U_NUMBER_RANGE_FORMATTER) 86 90 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); 87 94 #endif 88 95 } … … 194 201 } 195 202 203 #if HAVE(ICU_U_NUMBER_RANGE_FORMATTER_FORMAT_RANGE_TO_PARTS) 204 JSC_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 196 235 JSC_DEFINE_HOST_FUNCTION(intlNumberFormatPrototypeFuncResolvedOptions, (JSGlobalObject* globalObject, CallFrame* callFrame)) 197 236 { -
trunk/Source/JavaScriptCore/runtime/IntlRelativeTimeFormat.cpp
r285730 r286255 319 319 320 320 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())); 322 322 RETURN_IF_EXCEPTION(scope, { }); 323 323 } -
trunk/Source/WTF/wtf/Range.h
r258123 r286255 106 106 Type end() const { return m_end; } 107 107 108 Type distance() const { return end() - begin(); } 109 108 110 bool overlaps(const Range& other) const 109 111 {
Note: See TracChangeset
for help on using the changeset viewer.