<div dir="ltr">This looks fine.</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, Oct 16, 2019 at 5:04 PM Peter Wang <<a href="mailto:novalazy@gmail.com">novalazy@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">library/string.m:<br>
    Make string.index_next and string.unsafe_index_next<br>
    return either U+FFFD or an unpaired surrogate code point<br>
    when an ill-formed code unit sequence is detected.<br>
<br>
tests/hard_coded/Mmakefile:<br>
tests/hard_coded/string_index_next_ilseq.exp:<br>
tests/hard_coded/string_index_next_ilseq.exp2:<br>
tests/hard_coded/string_index_next_ilseq.m:<br>
    Add test case.<br>
---<br>
 library/string.m                              | 69 ++++++++++---------<br>
 tests/hard_coded/Mmakefile                    |  1 +<br>
 tests/hard_coded/string_index_next_ilseq.exp  |  5 ++<br>
 tests/hard_coded/string_index_next_ilseq.exp2 |  5 ++<br>
 tests/hard_coded/string_index_next_ilseq.m    | 44 ++++++++++++<br>
 5 files changed, 90 insertions(+), 34 deletions(-)<br>
 create mode 100644 tests/hard_coded/string_index_next_ilseq.exp<br>
 create mode 100644 tests/hard_coded/string_index_next_ilseq.exp2<br>
 create mode 100644 tests/hard_coded/string_index_next_ilseq.m<br>
<br>
diff --git a/library/string.m b/library/string.m<br>
index 09716dc49..b687cece3 100644<br>
--- a/library/string.m<br>
+++ b/library/string.m<br>
@@ -274,17 +274,26 @@<br>
<br>
     % index_next(String, Index, NextIndex, Char):<br>
     %<br>
-    % Like `index'/3 but also returns the position of the code unit<br>
-    % that follows the code point beginning at `Index',<br>
-    % i.e. NextIndex = Index + num_code_units_to_encode(Char).<br>
+    % If `Index' is the initial code unit offset of a well-formed code unit<br>
+    % sequence in `String' then `Char' is the code point encoded by that<br>
+    % sequence, and `NextIndex' is the offset immediately following that<br>
+    % sequence.<br>
+    %<br>
+    % If the code unit in `String' at `Index' is part of an ill-formed sequence<br>
+    % then `Char' is either U+FFFD REPLACEMENT CHARACTER (if strings use UTF-8<br>
+    % encoding) or the unpaired surrogate code point at `Index' (if strings use<br>
+    % UTF-16 encoding), and `NextIndex' is Index + 1.<br>
+    %<br>
+    % Fails if `Index' is out of range (negative, or greater than or equal to<br>
+    % the length of `String').<br>
     %<br>
 :- pred index_next(string::in, int::in, int::out, char::uo) is semidet.<br>
<br>
     % unsafe_index_next(String, Index, NextIndex, Char):<br>
     %<br>
-    % `Char' is the character (code point) in `String', beginning at the<br>
-    % code unit `Index'. `NextIndex' is the offset following the encoding<br>
-    % of `Char'. Fails if `Index' is equal to the length of `String'.<br>
+    % Like index_next/4 but does not check that `Index' is in range.<br>
+    % Fails if `Index' is equal to the length of `String'.<br>
+    %<br>
     % WARNING: behavior is UNDEFINED if `Index' is out of range<br>
     % (negative, or greater than the length of `String').<br>
     %<br>
@@ -2166,9 +2175,7 @@ duplicate_char(Char, Count, String) :-<br>
 % so that the compiler can do loop invariant hoisting on calls to them<br>
 % that occur in loops.<br>
<br>
-% XXX ILSEQ The index_next family fails if an ill-formed sequence is detected.<br>
-% It needs to be updated to match the behaviour of the index family.<br>
-%<br>
+% XXX ILSEQ<br>
 % We should allow the possibility of working with strings containing ill-formed<br>
 % sequences. That would require predicates that can return either a code point<br>
 % when possible, or else code units from ill-formed sequences.<br>
@@ -2251,11 +2258,6 @@ String ^ unsafe_elem(Index) = unsafe_index(String, Index).<br>
<br>
 %---------------------%<br>
<br>
-% XXX ILSEQ index_next/4 and unsafe_index_next/4 fail when an ill-formed<br>
-% sequence is detected, unlike the index family. Most programs will probably<br>
-% work as intended if we just replace code units in ill-formed sequences with<br>
-% replacement char (for UTF-8) or return the unpaired surrogate code point<br>
-% (for UTF-16).<br>
 index_next(Str, Index, NextIndex, Char) :-<br>
     Len = length(Str),<br>
     ( if index_check(Index, Len) then<br>
@@ -2264,10 +2266,6 @@ index_next(Str, Index, NextIndex, Char) :-<br>
         fail<br>
     ).<br>
<br>
-% XXX ILSEQ Behaviour of unsafe_index_next depends on target language.<br>
-%   - c: fails at ill-formed sequence<br>
-%   - java: fails at unpaired surrogate<br>
-%   - csharp: System.Char.ConvertToUtf32 throws exception for unpaired surrogate<br>
 :- pragma foreign_proc("C",<br>
     unsafe_index_next(Str::in, Index::in, NextIndex::out, Ch::uo),<br>
     [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,<br>
@@ -2280,7 +2278,11 @@ index_next(Str, Index, NextIndex, Char) :-<br>
     } else {<br>
         NextIndex = Index;<br>
         Ch = MR_utf8_get_next_mb(Str, &NextIndex);<br>
-        SUCCESS_INDICATOR = (Ch > 0);<br>
+        if (Ch < 0) {<br>
+            Ch = 0xfffd;<br>
+            NextIndex = Index + 1;<br>
+        }<br>
+        SUCCESS_INDICATOR = MR_TRUE;<br>
     }<br>
 ").<br>
 :- pragma foreign_proc("C#",<br>
@@ -2288,7 +2290,7 @@ index_next(Str, Index, NextIndex, Char) :-<br>
     [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,<br>
         does_not_affect_liveness, no_sharing],<br>
 "<br>
-    if (Index < Str.Length) {<br>
+    try {<br>
         Ch = System.Char.ConvertToUtf32(Str, Index);<br>
         if (Ch <= 0xffff) {<br>
             NextIndex = Index + 1;<br>
@@ -2296,10 +2298,15 @@ index_next(Str, Index, NextIndex, Char) :-<br>
             NextIndex = Index + 2;<br>
         }<br>
         SUCCESS_INDICATOR = true;<br>
-    } else {<br>
-        Ch = -1;<br>
+    } catch (System.ArgumentOutOfRangeException) {<br>
+        Ch = 0;<br>
         NextIndex = Index;<br>
         SUCCESS_INDICATOR = false;<br>
+    } catch (System.ArgumentException) {<br>
+        // Return unpaired surrogate code point.<br>
+        Ch = Str[Index];<br>
+        NextIndex = Index + 1;<br>
+        SUCCESS_INDICATOR = true;<br>
     }<br>
 ").<br>
 :- pragma foreign_proc("Java",<br>
@@ -2307,19 +2314,12 @@ index_next(Str, Index, NextIndex, Char) :-<br>
     [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,<br>
         does_not_affect_liveness, no_sharing],<br>
 "<br>
-    if (Index < Str.length()) {<br>
+    try {<br>
         Ch = Str.codePointAt(Index);<br>
-        SUCCESS_INDICATOR =<br>
-            !java.lang.Character.isHighSurrogate((char) Ch) &&<br>
-            !java.lang.Character.isLowSurrogate((char) Ch);<br>
-        if (SUCCESS_INDICATOR) {<br>
-            NextIndex = Index + java.lang.Character.charCount(Ch);<br>
-        } else {<br>
-            Ch = -1;<br>
-            NextIndex = Index;<br>
-        }<br>
-    } else {<br>
-        Ch = -1;<br>
+        NextIndex = Index + java.lang.Character.charCount(Ch);<br>
+        SUCCESS_INDICATOR = true;<br>
+    } catch (IndexOutOfBoundsException e) {<br>
+        Ch = 0;<br>
         NextIndex = Index;<br>
         SUCCESS_INDICATOR = false;<br>
     }<br>
@@ -2329,6 +2329,7 @@ index_next(Str, Index, NextIndex, Char) :-<br>
     [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,<br>
         does_not_affect_liveness, no_sharing],<br>
 "<br>
+    % XXX does not handle ill-formed sequences as described<br>
     case Str of<br>
         << _:Index/binary, Ch/utf8, _/binary >> -><br>
             if<br>
diff --git a/tests/hard_coded/Mmakefile b/tests/hard_coded/Mmakefile<br>
index 631d0da19..8c317da86 100644<br>
--- a/tests/hard_coded/Mmakefile<br>
+++ b/tests/hard_coded/Mmakefile<br>
@@ -359,6 +359,7 @@ ORDINARY_PROGS = \<br>
        string_codepoint \<br>
        string_first_char \<br>
        string_index_ilseq \<br>
+       string_index_next_ilseq \<br>
        string_loop \<br>
        string_presuffix \<br>
        string_set_char \<br>
diff --git a/tests/hard_coded/string_index_next_ilseq.exp b/tests/hard_coded/string_index_next_ilseq.exp<br>
new file mode 100644<br>
index 000000000..1ad4aba9a<br>
--- /dev/null<br>
+++ b/tests/hard_coded/string_index_next_ilseq.exp<br>
@@ -0,0 +1,5 @@<br>
+string.index_next(S, 0, 4, 0x1f600)<br>
+string.index_next(S, 4, 5, 0xfffd)<br>
+string.index_next(S, 5, 6, 0xfffd)<br>
+string.index_next(S, 6, 10, 0x1f600)<br>
+string.index_next(S, 10, _, _) failed<br>
diff --git a/tests/hard_coded/string_index_next_ilseq.exp2 b/tests/hard_coded/string_index_next_ilseq.exp2<br>
new file mode 100644<br>
index 000000000..c889bf0d8<br>
--- /dev/null<br>
+++ b/tests/hard_coded/string_index_next_ilseq.exp2<br>
@@ -0,0 +1,5 @@<br>
+string.index_next(S, 0, 2, 0x1f600)<br>
+string.index_next(S, 2, 3, 0xde00)<br>
+string.index_next(S, 3, 4, 0xd83d)<br>
+string.index_next(S, 4, 6, 0x1f600)<br>
+string.index_next(S, 6, _, _) failed<br>
diff --git a/tests/hard_coded/string_index_next_ilseq.m b/tests/hard_coded/string_index_next_ilseq.m<br>
new file mode 100644<br>
index 000000000..848a6fbae<br>
--- /dev/null<br>
+++ b/tests/hard_coded/string_index_next_ilseq.m<br>
@@ -0,0 +1,44 @@<br>
+%---------------------------------------------------------------------------%<br>
+% vim: ts=4 sw=4 et ft=mercury<br>
+%---------------------------------------------------------------------------%<br>
+%<br>
+% The .exp file is for backends using UTF-8 string encoding.<br>
+% The .exp2 file is for backends using UTF-16 string encoding.<br>
+%<br>
+%---------------------------------------------------------------------------%<br>
+<br>
+:- module string_index_next_ilseq.<br>
+:- interface.<br>
+<br>
+:- import_module io.<br>
+<br>
+:- pred main(io::di, io::uo) is det.<br>
+<br>
+%---------------------------------------------------------------------------%<br>
+<br>
+:- implementation.<br>
+<br>
+:- import_module char.<br>
+:- import_module list.<br>
+:- import_module string.<br>
+<br>
+%---------------------------------------------------------------------------%<br>
+<br>
+main(!IO) :-<br>
+    S0 = "😀",<br>
+    S1 = string.between(S0, 0, 1),<br>
+    S2 = string.between(S0, 1, 2),<br>
+    S = S0 ++ S2 ++ S1 ++ S0,<br>
+    test_index_next(S, 0, !IO).<br>
+<br>
+:- pred test_index_next(string::in, int::in, io::di, io::uo) is det.<br>
+<br>
+test_index_next(S, Index, !IO) :-<br>
+    ( if string.index_next(S, Index, NextIndex, Char) then<br>
+        io.format("string.index_next(S, %d, %d, %#x)\n",<br>
+            [i(Index), i(NextIndex), i(char.to_int(Char))], !IO),<br>
+        test_index_next(S, NextIndex, !IO)<br>
+    else<br>
+        io.format("string.index_next(S, %d, _, _) failed\n",<br>
+            [i(Index)], !IO)<br>
+    ).<br>
-- <br>
2.23.0<br>
<br>
_______________________________________________<br>
reviews mailing list<br>
<a href="mailto:reviews@lists.mercurylang.org" target="_blank">reviews@lists.mercurylang.org</a><br>
<a href="https://lists.mercurylang.org/listinfo/reviews" rel="noreferrer" target="_blank">https://lists.mercurylang.org/listinfo/reviews</a><br>
</blockquote></div>