<div dir="ltr">This looks good.</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Oct 31, 2019 at 5:18 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>
    Add append_string_pieces/2 predicate.<br>
<br>
library/io.m:<br>
    Add a comment about a potential future change.<br>
<br>
tests/hard_coded/Mmakefile:<br>
tests/hard_coded/string_append_pieces.exp:<br>
tests/hard_coded/string_append_pieces.m:<br>
    Add test case.<br>
<br>
NEWS:<br>
    Announce addition.<br>
---<br>
 NEWS                                      |   1 +<br>
 library/io.m                              |   3 +<br>
 library/string.m                          | 181 ++++++++++++++++++++++<br>
 tests/hard_coded/Mmakefile                |   1 +<br>
 tests/hard_coded/string_append_pieces.exp |  11 ++<br>
 tests/hard_coded/string_append_pieces.m   |  59 +++++++<br>
 6 files changed, 256 insertions(+)<br>
 create mode 100644 tests/hard_coded/string_append_pieces.exp<br>
 create mode 100644 tests/hard_coded/string_append_pieces.m<br>
<br>
diff --git a/NEWS b/NEWS<br>
index b8827d9b1..570d22b85 100644<br>
--- a/NEWS<br>
+++ b/NEWS<br>
@@ -430,6 +430,7 @@ Changes to the Mercury standard library:<br>
    - compare_substrings/6<br>
    - unsafe_compare_substrings/6<br>
    - nondet_append/3<br>
+   - append_string_pieces/2<br>
<br>
   The following procedures in the string module have been deprecated:<br>
<br>
diff --git a/library/io.m b/library/io.m<br>
index b8a8cabad..54d7ce133 100644<br>
--- a/library/io.m<br>
+++ b/library/io.m<br>
@@ -5155,6 +5155,9 @@ file_id(FileName, Result, !IO) :-<br>
     % XXX It would be better to use a char_array type rather than array(char).<br>
     % This is because on the Java and IL backends indexing into an array whose<br>
     % element type is known statically requires less overhead.<br>
+    %<br>
+    % It may be possible to merge with string.string_buffer.<br>
+    %<br>
 :- type buffer<br>
     --->    buffer(array(char)).<br>
<br>
diff --git a/library/string.m b/library/string.m<br>
index b0930a767..bcd7b78bc 100644<br>
--- a/library/string.m<br>
+++ b/library/string.m<br>
@@ -665,6 +665,24 @@<br>
     %<br>
 :- func join_list(string::in, list(string)::in) = (string::uo) is det.<br>
<br>
+%---------------------------------------------------------------------------%<br>
+%<br>
+% Making strings from smaller pieces.<br>
+%<br>
+<br>
+:- type string_piece<br>
+    --->    string(string)<br>
+    ;       substring(string, int, int).    % string, start, end offset<br>
+<br>
+    % append_string_pieces(Pieces, String):<br>
+    %<br>
+    % Append together the strings and substrings in `Pieces' into a string.<br>
+    % Throws an exception `Pieces' contains an element<br>
+    % `substring(S, Start, End)' where `Start' or `End' are not within<br>
+    % the range [0, length(S)], or if `Start' > `End'.<br>
+    %<br>
+:- pred append_string_pieces(list(string_piece)::in, string::uo) is det.<br>
+<br>
 %---------------------------------------------------------------------------%<br>
 %<br>
 % Splitting up strings.<br>
@@ -3951,6 +3969,169 @@ join_list(Sep, [H | T]) = H ++ join_list_loop(Sep, T).<br>
 join_list_loop(_, []) = "".<br>
 join_list_loop(Sep, [H | T]) = Sep ++ H ++ join_list_loop(Sep, T).<br>
<br>
+%---------------------------------------------------------------------------%<br>
+%<br>
+% Making strings from smaller pieces.<br>
+%<br>
+<br>
+:- type string_buffer<br>
+    --->    string_buffer(string).<br>
+<br>
+:- pragma foreign_type("C", string_buffer, "char *",<br>
+    [can_pass_as_mercury_type]).<br>
+:- pragma foreign_type("C#", string_buffer, "char[]").<br>
+:- pragma foreign_type("Java", string_buffer, "java.lang.StringBuilder").<br>
+<br>
+:- pred alloc_buffer(int::in, string_buffer::uo) is det.<br>
+<br>
+:- pragma foreign_proc("C",<br>
+    alloc_buffer(Size::in, Buffer::uo),<br>
+    [will_not_call_mercury, promise_pure, thread_safe,<br>
+        does_not_affect_liveness, no_sharing],<br>
+"<br>
+    MR_allocate_aligned_string_msg(Buffer, Size, MR_ALLOC_ID);<br>
+    Buffer[Size] = '\\0';<br>
+").<br>
+:- pragma foreign_proc("C#",<br>
+    alloc_buffer(Size::in, Buffer::uo),<br>
+    [will_not_call_mercury, promise_pure, thread_safe],<br>
+"<br>
+    Buffer = new char[Size];<br>
+").<br>
+:- pragma foreign_proc("Java",<br>
+    alloc_buffer(Size::in, Buffer::uo),<br>
+    [will_not_call_mercury, promise_pure, thread_safe],<br>
+"<br>
+    Buffer = new java.lang.StringBuilder(Size);<br>
+").<br>
+<br>
+alloc_buffer(_Size, Buffer) :-<br>
+    Buffer = string_buffer("").<br>
+<br>
+:- pred buffer_to_string(string_buffer::di, string::uo) is det.<br>
+<br>
+:- pragma foreign_proc("C",<br>
+    buffer_to_string(Buffer::di, Str::uo),<br>
+    [will_not_call_mercury, promise_pure, thread_safe,<br>
+        does_not_affect_liveness],<br>
+"<br>
+    Str = Buffer;<br>
+").<br>
+:- pragma foreign_proc("C#",<br>
+    buffer_to_string(Buffer::di, Str::uo),<br>
+    [will_not_call_mercury, promise_pure, thread_safe],<br>
+"<br>
+    Str = new string(Buffer);<br>
+").<br>
+:- pragma foreign_proc("Java",<br>
+    buffer_to_string(Buffer::di, Str::uo),<br>
+    [will_not_call_mercury, promise_pure, thread_safe],<br>
+"<br>
+    Str = Buffer.toString();<br>
+").<br>
+<br>
+buffer_to_string(Buffer, Str) :-<br>
+    Buffer = string_buffer(Str).<br>
+<br>
+:- pred copy_into_buffer(string_buffer::di, string_buffer::uo,<br>
+    int::in, int::out, string::in, int::in, int::in) is det.<br>
+<br>
+:- pragma foreign_proc("C",<br>
+    copy_into_buffer(Dest0::di, Dest::uo, DestOffset0::in, DestOffset::out,<br>
+        Src::in, SrcStart::in, SrcEnd::in),<br>
+    [will_not_call_mercury, promise_pure, thread_safe,<br>
+        does_not_affect_liveness],<br>
+"<br>
+    size_t count;<br>
+<br>
+    MR_CHECK_EXPR_TYPE(Dest0, char *);<br>
+    MR_CHECK_EXPR_TYPE(Dest, char *);<br>
+<br>
+    count = SrcEnd - SrcStart;<br>
+    Dest = Dest0;<br>
+    MR_memcpy(Dest + DestOffset0, Src + SrcStart, count);<br>
+    DestOffset = DestOffset0 + count;<br>
+").<br>
+:- pragma foreign_proc("C#",<br>
+    copy_into_buffer(Dest0::di, Dest::uo, DestOffset0::in, DestOffset::out,<br>
+        Src::in, SrcStart::in, SrcEnd::in),<br>
+    [will_not_call_mercury, promise_pure, thread_safe],<br>
+"<br>
+    int count = SrcEnd - SrcStart;<br>
+    Dest = Dest0;<br>
+    Src.CopyTo(SrcStart, Dest, DestOffset0, count);<br>
+    DestOffset = DestOffset0 + count;<br>
+").<br>
+:- pragma foreign_proc("Java",<br>
+    copy_into_buffer(Dest0::di, Dest::uo, DestOffset0::in, DestOffset::out,<br>
+        Src::in, SrcStart::in, SrcEnd::in),<br>
+    [will_not_call_mercury, promise_pure, thread_safe],<br>
+"<br>
+    // The Java implementation does not actually use the dest offsets.<br>
+    Dest = Dest0;<br>
+    Dest.append(Src, SrcStart, SrcEnd);<br>
+    DestOffset = DestOffset0 + (SrcEnd - SrcStart);<br>
+").<br>
+<br>
+copy_into_buffer(Dest0, Dest, DestOffset0, DestOffset, Src, SrcStart, SrcEnd)<br>
+        :-<br>
+    Dest0 = string_buffer(Buffer0),<br>
+    Buffer = Buffer0 ++ unsafe_between(Src, SrcStart, SrcEnd),<br>
+    DestOffset = DestOffset0 + (SrcEnd - SrcStart),<br>
+    Dest = string_buffer(Buffer).<br>
+<br>
+%---------------------%<br>
+<br>
+append_string_pieces(Pieces, String) :-<br>
+    check_pieces_and_sum_length($pred, Pieces, 0, BufferLen),<br>
+    alloc_buffer(BufferLen, Buffer0),<br>
+    list.foldl2(copy_piece_into_buffer, Pieces, 0, End, Buffer0, Buffer),<br>
+    expect(unify(End, BufferLen), $pred, "End != BufferLen"),<br>
+    buffer_to_string(Buffer, String).<br>
+<br>
+:- pred check_pieces_and_sum_length(string::in, list(string_piece)::in,<br>
+    int::in, int::out) is det.<br>
+<br>
+check_pieces_and_sum_length(PredName, Pieces, Len0, Len) :-<br>
+    (<br>
+        Pieces = [],<br>
+        Len = Len0<br>
+    ;<br>
+        Pieces = [Piece | TailPieces],<br>
+        (<br>
+            Piece = string(Str),<br>
+            PieceLen = length(Str)<br>
+        ;<br>
+            Piece = substring(BaseStr, Start, End),<br>
+            BaseLen = length(BaseStr),<br>
+            ( if<br>
+                Start >= 0,<br>
+                Start =< BaseLen,<br>
+                End >= Start,<br>
+                End =< BaseLen<br>
+            then<br>
+                PieceLen = End - Start<br>
+            else<br>
+                unexpected(PredName, "substring index out of range")<br>
+            )<br>
+        ),<br>
+        Len1 = Len0 + PieceLen,<br>
+        check_pieces_and_sum_length(PredName, TailPieces, Len1, Len)<br>
+    ).<br>
+<br>
+:- pred copy_piece_into_buffer(string_piece::in, int::in, int::out,<br>
+    string_buffer::di, string_buffer::uo) is det.<br>
+<br>
+copy_piece_into_buffer(Piece, !DestOffset, !DestBuffer) :-<br>
+    (<br>
+        Piece = string(Src),<br>
+        SrcStart = 0,<br>
+        SrcEnd = length(Src)<br>
+    ;<br>
+        Piece = substring(Src, SrcStart, SrcEnd)<br>
+    ),<br>
+    copy_into_buffer(!DestBuffer, !DestOffset, Src, SrcStart, SrcEnd).<br>
+<br>
 %---------------------------------------------------------------------------%<br>
 %<br>
 % Splitting up strings.<br>
diff --git a/tests/hard_coded/Mmakefile b/tests/hard_coded/Mmakefile<br>
index 826ea7d0d..16fdf0eb7 100644<br>
--- a/tests/hard_coded/Mmakefile<br>
+++ b/tests/hard_coded/Mmakefile<br>
@@ -355,6 +355,7 @@ ORDINARY_PROGS = \<br>
        string_append_ioi \<br>
        string_append_ooi \<br>
        string_append_ooi_ilseq \<br>
+       string_append_pieces \<br>
        string_builder_test \<br>
        string_case \<br>
        string_char_list_ilseq \<br>
diff --git a/tests/hard_coded/string_append_pieces.exp b/tests/hard_coded/string_append_pieces.exp<br>
new file mode 100644<br>
index 000000000..8e2a865d5<br>
--- /dev/null<br>
+++ b/tests/hard_coded/string_append_pieces.exp<br>
@@ -0,0 +1,11 @@<br>
+""<br>
+""<br>
+""<br>
+""<br>
+"ab"<br>
+"cool!😀"<br>
+software_error("predicate `string.append_string_pieces\'/2: Unexpected: substring index out of range")<br>
+software_error("predicate `string.append_string_pieces\'/2: Unexpected: substring index out of range")<br>
+software_error("predicate `string.append_string_pieces\'/2: Unexpected: substring index out of range")<br>
+software_error("predicate `string.append_string_pieces\'/2: Unexpected: substring index out of range")<br>
+software_error("predicate `string.append_string_pieces\'/2: Unexpected: substring index out of range")<br>
diff --git a/tests/hard_coded/string_append_pieces.m b/tests/hard_coded/string_append_pieces.m<br>
new file mode 100644<br>
index 000000000..f5397e03f<br>
--- /dev/null<br>
+++ b/tests/hard_coded/string_append_pieces.m<br>
@@ -0,0 +1,59 @@<br>
+%---------------------------------------------------------------------------%<br>
+% vim: ts=4 sw=4 et ft=mercury<br>
+%---------------------------------------------------------------------------%<br>
+<br>
+:- module string_append_pieces.<br>
+:- interface.<br>
+<br>
+:- import_module io.<br>
+<br>
+:- pred main(io::di, io::uo) is cc_multi.<br>
+<br>
+%---------------------------------------------------------------------------%<br>
+<br>
+:- implementation.<br>
+<br>
+:- import_module exception.<br>
+:- import_module int.<br>
+:- import_module list.<br>
+:- import_module string.<br>
+<br>
+%---------------------------------------------------------------------------%<br>
+<br>
+main(!IO) :-<br>
+    foldl(test_append_string_pieces, test_cases, !IO).<br>
+<br>
+:- func test_cases = list(list(string_piece)).<br>
+<br>
+test_cases = [<br>
+    [],<br>
+    [string("")],<br>
+    [substring("", 0, 0)],<br>
+    [substring("ok", 2, 2)],<br>
+    [substring("axx", 0, 1), substring("xbx", 1, 2)],<br>
+    [<br>
+        string("c"),<br>
+        substring("whoops!", 2, 4),<br>
+        string("l!"),<br>
+        substring("😀😀😀", length("😀"), 2 * length("😀"))<br>
+    ],<br>
+    [substring("bad", -1, 0)],<br>
+    [substring("bad", 4, 3)],<br>
+    [substring("bad", 0, -1)],<br>
+    [substring("bad", 0, 4)],<br>
+    [substring("bad", 3, 2)]<br>
+].<br>
+<br>
+:- pred test_append_string_pieces(list(string_piece)::in, io::di, io::uo)<br>
+    is cc_multi.<br>
+<br>
+test_append_string_pieces(Pieces, !IO) :-<br>
+    ( try []<br>
+        append_string_pieces(Pieces, Str)<br>
+    then<br>
+        io.write_string("""", !IO),<br>
+        io.write_string(Str, !IO),<br>
+        io.write_string("""\n", !IO)<br>
+    catch_any Excp -><br>
+        io.print_line(Excp, !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>