[m-rev.] for review: string.between

Peter Wang novalazy at gmail.com
Fri Jun 10 18:07:54 AEST 2011


Branches: main

Deprecate string.substring, string.foldl_substring, etc. in favour of
new procedures named string.between, string.foldl_between, etc.
The "between" procedures take a pair of [Start, End) endpoints instead
of Start, Count arguments.  The reasons for this change are:

- the "between" procedures are more convenient

- "between" should be unambiguous.  You can guess that it takes an End
  argument instead of a Count argument without looking up the manual.

- Count arguments necessarily counted code units, but when working with
  non-ASCII strings, almost the only way that the values would be arrived at
  is by substracting one end point from another. 

- it paves the way for a potential change to replace string offsets with an
  abstract type.  We cannot do that if users regularly have to perform a
  subtraction between two offsets.

library/string.m:
	Add string.foldl_between, string.foldl2_between,
	string.foldr_between, string.between.

	Deprecate the old substring names.

	Replace string.substring_by_codepoint by string.between_codepoints.

compiler/elds_to_erlang.m:
compiler/error_util.m:
compiler/rbmm.live_variable_analysis.m:
compiler/timestamp.m:
extras/posix/samples/mdprof_cgid.m:
library/bitmap.m:
library/integer.m:
library/lexer.m:
library/parsing_utils.m:
mdbcomp/trace_counts.m:
profiler/demangle.m:
	Conform to changes.

tests/general/Mercury.options:
tests/general/string_foldl_substring.exp:
tests/general/string_foldl_substring.m:
tests/general/string_foldr_substring.exp:
tests/general/string_foldr_substring.m:
tests/hard_coded/Mercury.options:
tests/hard_coded/string_substring.m:
	Test both between and substring procedures.

tests/hard_coded/string_codepoint.exp:
tests/hard_coded/string_codepoint.exp2:
tests/hard_coded/string_codepoint.m:
	Update names in test outputs.

NEWS:
	Announce the change.

diff --git a/NEWS b/NEWS
index 172fe6b..fa2cb74 100644
--- a/NEWS
+++ b/NEWS
@@ -108,15 +108,24 @@ Changes to the Mercury standard library:
 	bitmap.new/2
 	hash_table.new/3
 	hash_table.new_default/1
-	store.new/1
 	semaphore.new/1
 	semaphore.new/3
+	store.new/1
+	string.foldl2_substring/8
+	string.foldl_substring/5
+	string.foldl_substring/6
+	string.foldr_substring/5
+	string.foldr_substring/6
+	string.substring/3
+	string.substring/4
+	string.unsafe_substring/3
+	string.unsafe_substring/4
 	version_array.new/2
 	version_array2d.new/3
 	version_bitmap.new/2
 	version_hash_table.new/3
-	version_hash_table.unsafe_new/3
 	version_hash_table.new_default/1
+	version_hash_table.unsafe_new/3
 	version_hash_table.unsafe_new_default/1
 	version_store.new/0
 
diff --git a/compiler/elds_to_erlang.m b/compiler/elds_to_erlang.m
index d8f07a0..a176e49 100644
--- a/compiler/elds_to_erlang.m
+++ b/compiler/elds_to_erlang.m
@@ -791,9 +791,9 @@ output_term(ModuleInfo, VarSet, Indent, Term, !IO) :-
 output_float(Float, !IO) :-
     S = string.from_float(Float),
     ( digit_then_e(S, no, 0, Pos) ->
-        io.write_string(string.substring(S, 0, Pos), !IO),
+        io.write_string(string.between(S, 0, Pos), !IO),
         io.write_string(".0", !IO),
-        io.write_string(string.substring(S, Pos, length(S)), !IO)
+        io.write_string(string.between(S, Pos, length(S)), !IO)
     ;
         io.write_string(S, !IO)
     ).
diff --git a/compiler/error_util.m b/compiler/error_util.m
index 6e79a2f..332cb29 100644
--- a/compiler/error_util.m
+++ b/compiler/error_util.m
@@ -1342,7 +1342,7 @@ break_into_words(String, Words0, Words) :-
 break_into_words_from(String, Cur, Words0, Words) :-
     ( find_word_start(String, Cur, Start) ->
         find_word_end(String, Start, End),
-        string.substring(String, Start, End - Start, WordStr),
+        string.between(String, Start, End, WordStr),
         break_into_words_from(String, End, [plain_word(WordStr) | Words0],
             Words)
     ;
diff --git a/compiler/rbmm.live_variable_analysis.m b/compiler/rbmm.live_variable_analysis.m
index c0aa0d5..0726b42 100644
--- a/compiler/rbmm.live_variable_analysis.m
+++ b/compiler/rbmm.live_variable_analysis.m
@@ -385,8 +385,7 @@ collect_void_vars(ProgPoint, ProducedSet, ProcInfo, !ProcVoidVar) :-
 
 void_var(Varset, Var, !VoidVars) :-
     mercury_var_to_string(Varset, no, Var) = VarName,
-    string.substring(VarName, 0, 1, FirstChar),
-    ( FirstChar = "_" ->
+    ( string.index(VarName, 0, '_') ->
         set.insert(Var, !VoidVars)
     ;
         true
diff --git a/compiler/timestamp.m b/compiler/timestamp.m
index 68b53b7..d9ed7d0 100644
--- a/compiler/timestamp.m
+++ b/compiler/timestamp.m
@@ -161,35 +161,35 @@ string_to_timestamp(Timestamp) = timestamp(Timestamp) :-
         string.all_match(plausible_timestamp_char, Timestamp),
         string.length(Timestamp) : int = string.length("yyyy-mm-dd hh:mm:ss")
     ->
-        string.to_int(string.unsafe_substring(Timestamp, 0, 4), _),
+        string.to_int(string.unsafe_between(Timestamp, 0, 4), _),
 
         string.unsafe_index(Timestamp, 4, '-'),
 
-        string.to_int(string.unsafe_substring(Timestamp, 5, 2), Month),
+        string.to_int(string.unsafe_between(Timestamp, 5, 7), Month),
         Month >= 1,
         Month =< 12,
 
         string.unsafe_index(Timestamp, 7, '-'),
 
-        string.to_int(string.unsafe_substring(Timestamp, 8, 2), Day),
+        string.to_int(string.unsafe_between(Timestamp, 8, 10), Day),
         Day >= 1,
         Day =< 31,
 
         string.unsafe_index(Timestamp, 10, ' '),
 
-        string.to_int(string.unsafe_substring(Timestamp, 11, 2), Hour),
+        string.to_int(string.unsafe_between(Timestamp, 11, 13), Hour),
         Hour >= 0,
         Hour =< 23,
 
         string.unsafe_index(Timestamp, 13, ':'),
 
-        string.to_int(string.unsafe_substring(Timestamp, 14, 2), Minute),
+        string.to_int(string.unsafe_between(Timestamp, 14, 16), Minute),
         Minute >= 0,
         Minute =< 59,
 
         string.unsafe_index(Timestamp, 16, ':'),
 
-        string.to_int(string.unsafe_substring(Timestamp, 17, 2), Second),
+        string.to_int(string.unsafe_between(Timestamp, 17, 19), Second),
         Second >= 0,
         Second =< 61    % Seconds 60 and 61 are for leap seconds.
     ;
diff --git a/extras/posix/samples/mdprof_cgid.m b/extras/posix/samples/mdprof_cgid.m
index 92c6712..4c138a7 100644
--- a/extras/posix/samples/mdprof_cgid.m
+++ b/extras/posix/samples/mdprof_cgid.m
@@ -267,7 +267,7 @@ handle_request_2(Data, Path, !IO) :-
     %
     (if string.sub_string_search(Path, "?", QuesIndex) then
         Length = string.length(Path),
-        QueryString = string.substring(Path, QuesIndex + 1, Length)
+        QueryString = string.between(Path, QuesIndex + 1, Length)
     else
         QueryString = ""
     ),
diff --git a/library/bitmap.m b/library/bitmap.m
index b0a92d0..d2551ac 100644
--- a/library/bitmap.m
+++ b/library/bitmap.m
@@ -1426,21 +1426,18 @@ to_string_chars(Index, BM, !Chars) :-
     ).
 
 from_string(Str) = BM :-
-    Len = length(Str),
-    ( if Len >= 4 then
-        Str ^ unsafe_elem(0) = ('<'),
-        char.is_digit(Str ^ unsafe_elem(1)),
-        Str ^ unsafe_elem(Len - 1) = ('>'),
-        string.sub_string_search(Str, ":", Colon),
-        SizeStr = string.unsafe_substring(Str, 1, Colon - 1),
-        string.to_int(SizeStr, Size),
-        ( if Size >= 0 then
-            BM0 = allocate_bitmap(Size),
-            hex_chars_to_bitmap(Str, Colon + 1, Len - 1, 0, BM0, BM)
-          else
-            fail
-        )
-      else
+    string.unsafe_index_next(Str, 0, Start, '<'),
+    string.unsafe_index(Str, Start, Char),
+    char.is_digit(Char),
+    string.unsafe_prev_index(Str, length(Str), End, '>'),
+    string.sub_string_search_start(Str, ":", Start, Colon),
+    SizeStr = string.unsafe_between(Str, Start, Colon),
+    string.to_int(SizeStr, Size),
+    ( Size >= 0 ->
+        BM0 = allocate_bitmap(Size),
+        string.unsafe_index_next(Str, Colon, AfterColon, _),
+        hex_chars_to_bitmap(Str, AfterColon, End, 0, BM0, BM)
+    ;
         fail
     ).
 
diff --git a/library/integer.m b/library/integer.m
index 240723b..7394ae3 100644
--- a/library/integer.m
+++ b/library/integer.m
@@ -1221,16 +1221,16 @@ integer.from_base_string(Base0, String) = Integer :-
     Len = string.length(String),
     ( Char = ('-') ->
         Len > 1,
-        string.foldl_substring(accumulate_integer(Base), String, 1, Len - 1,
+        string.foldl_between(accumulate_integer(Base), String, 1, Len,
             integer.zero, N),
         Integer = -N
     ; Char = ('+') ->
         Len > 1,
-        string.foldl_substring(accumulate_integer(Base), String, 1, Len - 1,
+        string.foldl_between(accumulate_integer(Base), String, 1, Len,
             integer.zero, N),
         Integer = N
     ;
-        string.foldl_substring(accumulate_integer(Base), String, 0, Len,
+        string.foldl_between(accumulate_integer(Base), String, 0, Len,
             integer.zero, N),
         Integer = N
     ).
diff --git a/library/lexer.m b/library/lexer.m
index 7fb96dc..6b75e6e 100644
--- a/library/lexer.m
+++ b/library/lexer.m
@@ -348,8 +348,7 @@ string_ungetchar(String, Posn0, Posn) :-
 grab_string(String, Posn0, SubString, Posn, Posn) :-
     Posn0 = posn(_, _, Offset0),
     Posn = posn(_, _, Offset),
-    Count = Offset - Offset0,
-    string.unsafe_substring(String, Offset0, Count, SubString).
+    string.unsafe_between(String, Offset0, Offset, SubString).
 
 :- pred string_set_line_number(int::in, posn::in, posn::out) is det.
 
diff --git a/library/parsing_utils.m b/library/parsing_utils.m
index a29138a..dbfacbb 100644
--- a/library/parsing_utils.m
+++ b/library/parsing_utils.m
@@ -517,8 +517,7 @@ char_in_class(CharClass, Src, Char, !PS) :-
 input_substring(Src, Start, EndPlusOne, Substring) :-
     promise_pure (
         EndPlusOne =< Src ^ input_length,
-        Substring =
-            unsafe_substring(Src ^ input_string, Start, EndPlusOne - Start),
+        Substring = unsafe_between(Src ^ input_string, Start, EndPlusOne),
         impure record_progress(Src, Start)
     ).
 
diff --git a/library/string.m b/library/string.m
index 8ef25f7..8604de2 100644
--- a/library/string.m
+++ b/library/string.m
@@ -709,12 +709,80 @@
 :- mode string.foldl2(pred(in, in, out, in, out) is multi,
     in, in, out, in, out) is multi.
 
-    % string.foldl_substring(Closure, String, Start, Count, !Acc)
+    % string.foldl_between(Closure, String, Start, End, !Acc)
     % is equivalent to string.foldl(Closure, SubString, !Acc)
-    % where SubString = string.substring(String, Start, Count).
+    % where SubString = string.between(String, Start, End).
+    %
+    % `Start' and `End' are in terms of code units.
+    %
+:- func string.foldl_between(func(char, A) = A, string, int, int, A) = A.
+:- pred string.foldl_between(pred(char, A, A), string, int, int, A, A).
+:- mode string.foldl_between(pred(in, in, out) is det, in, in, in,
+    in, out) is det.
+:- mode string.foldl_between(pred(in, di, uo) is det, in, in, in,
+    di, uo) is det.
+:- mode string.foldl_between(pred(in, in, out) is semidet, in, in, in,
+    in, out) is semidet.
+:- mode string.foldl_between(pred(in, in, out) is nondet, in, in, in,
+    in, out) is nondet.
+:- mode string.foldl_between(pred(in, in, out) is multi, in, in, in,
+    in, out) is multi.
+
+    % string.foldl2_between(Closure, String, Start, End, !Acc1, !Acc2)
+    % A variant of string.foldl_between with two accumulators.
+    %
+    % `Start' and `End' are in terms of code units.
+    %
+:- pred string.foldl2_between(pred(char, A, A, B, B),
+    string, int, int, A, A, B, B).
+:- mode string.foldl2_between(pred(in, di, uo, di, uo) is det,
+    in, in, in, di, uo, di, uo) is det.
+:- mode string.foldl2_between(pred(in, in, out, di, uo) is det,
+    in, in, in, in, out, di, uo) is det.
+:- mode string.foldl2_between(pred(in, in, out, in, out) is det,
+    in, in, in, in, out, in, out) is det.
+:- mode string.foldl2_between(pred(in, in, out, in, out) is semidet,
+    in, in, in, in, out, in, out) is semidet.
+:- mode string.foldl2_between(pred(in, in, out, in, out) is nondet,
+    in, in, in, in, out, in, out) is nondet.
+:- mode string.foldl2_between(pred(in, in, out, in, out) is multi,
+    in, in, in, in, out, in, out) is multi.
+
+    % string.foldr(Closure, String, !Acc):
+    % As string.foldl/4, except that processing proceeds right-to-left.
+    %
+:- func string.foldr(func(char, T) = T, string, T) = T.
+:- pred string.foldr(pred(char, T, T), string, T, T).
+:- mode string.foldr(pred(in, in, out) is det, in, in, out) is det.
+:- mode string.foldr(pred(in, di, uo) is det, in, di, uo) is det.
+:- mode string.foldr(pred(in, in, out) is semidet, in, in, out) is semidet.
+:- mode string.foldr(pred(in, in, out) is nondet, in, in, out) is nondet.
+:- mode string.foldr(pred(in, in, out) is multi, in, in, out) is multi.
+
+    % string.foldr_between(Closure, String, Start, End, !Acc)
+    % is equivalent to string.foldr(Closure, SubString, !Acc)
+    % where SubString = string.between(String, Start, End).
+    %
+    % `Start' and `End' are in terms of code units.
     %
-    % `Start' and `Count' are in terms of code units.
+:- func string.foldr_between(func(char, T) = T, string, int, int, T) = T.
+:- pred string.foldr_between(pred(char, T, T), string, int, int, T, T).
+:- mode string.foldr_between(pred(in, in, out) is det, in, in, in,
+    in, out) is det.
+:- mode string.foldr_between(pred(in, di, uo) is det, in, in, in,
+    di, uo) is det.
+:- mode string.foldr_between(pred(in, in, out) is semidet, in, in, in,
+    in, out) is semidet.
+:- mode string.foldr_between(pred(in, in, out) is nondet, in, in, in,
+    in, out) is nondet.
+:- mode string.foldr_between(pred(in, in, out) is multi, in, in, in,
+    in, out) is multi.
+
+    % string.foldl_substring(Closure, String, Start, Count, !Acc)
+    % Please use string.foldl_between instead.
     %
+:- pragma obsolete(string.foldl_substring/5).
+:- pragma obsolete(string.foldl_substring/6).
 :- func string.foldl_substring(func(char, A) = A, string, int, int, A) = A.
 :- pred string.foldl_substring(pred(char, A, A), string, int, int, A, A).
 :- mode string.foldl_substring(pred(in, in, out) is det, in, in, in,
@@ -728,11 +796,10 @@
 :- mode string.foldl_substring(pred(in, in, out) is multi, in, in, in,
     in, out) is multi.
 
-    % string.foldl_substring2(Closure, String, Start, Count, !Acc1, !Acc2)
-    % A variant of string.foldl_substring with two accumulators.
-    %
-    % `Start' and `Count' are in terms of code units.
+    % string.foldl2_substring(Closure, String, Start, Count, !Acc1, !Acc2)
+    % Please use string.foldl2_between instead.
     %
+:- pragma obsolete(string.foldl2_substring/8).
 :- pred string.foldl2_substring(pred(char, A, A, B, B),
     string, int, int, A, A, B, B).
 :- mode string.foldl2_substring(pred(in, di, uo, di, uo) is det,
@@ -748,23 +815,11 @@
 :- mode string.foldl2_substring(pred(in, in, out, in, out) is multi,
     in, in, in, in, out, in, out) is multi.
 
-    % string.foldr(Closure, String, !Acc):
-    % As string.foldl/4, except that processing proceeds right-to-left.
-    %
-:- func string.foldr(func(char, T) = T, string, T) = T.
-:- pred string.foldr(pred(char, T, T), string, T, T).
-:- mode string.foldr(pred(in, in, out) is det, in, in, out) is det.
-:- mode string.foldr(pred(in, di, uo) is det, in, di, uo) is det.
-:- mode string.foldr(pred(in, in, out) is semidet, in, in, out) is semidet.
-:- mode string.foldr(pred(in, in, out) is nondet, in, in, out) is nondet.
-:- mode string.foldr(pred(in, in, out) is multi, in, in, out) is multi.
-
     % string.foldr_substring(Closure, String, Start, Count, !Acc)
-    % is equivalent to string.foldr(Closure, SubString, !Acc)
-    % where SubString = string.substring(String, Start, Count).
-    %
-    % `Start' and `Count' are in terms of code units.
+    % Please use string.foldr_between instead.
     %
+:- pragma obsolete(string.foldr_substring/5).
+:- pragma obsolete(string.foldr_substring/6).
 :- func string.foldr_substring(func(char, T) = T, string, int, int, T) = T.
 :- pred string.foldr_substring(pred(char, T, T), string, int, int, T, T).
 :- mode string.foldr_substring(pred(in, in, out) is det, in, in, in,
@@ -873,40 +928,58 @@
 :- func string.right_by_codepoint(string::in, int::in) = (string::uo) is det.
 :- pred string.right_by_codepoint(string::in, int::in, string::uo) is det.
 
-    % string.substring(String, Start, Count, Substring):
-    % `Substring' is first the `Count' code _units_ in what would remain
-    % of `String' after the first `Start' code _units_ were removed.
+    % string.between(String, Start, End, Substring):
+    % `Substring' consists of the segment of `String' within the half-open
+    % interval [Start, End), where `Start' and `End' are code unit offsets.
     % (If `Start' is out of the range [0, length of `String'], it is treated
     % as if it were the nearest end-point of that range.
-    % If `Count' is out of the range [0, length of `String' - `Start'],
+    % If `End' is out of the range [`Start', length of `String'],
     % it is treated as if it were the nearest end-point of that range.)
     %
+:- func string.between(string::in, int::in, int::in) = (string::uo) is det.
+:- pred string.between(string::in, int::in, int::in, string::uo) is det.
+
+    % string.substring(String, Start, Count, Substring):
+    % Please use string.between instead.
+    %
+:- pragma obsolete(string.substring/3).
+:- pragma obsolete(string.substring/4).
 :- func string.substring(string::in, int::in, int::in) = (string::uo) is det.
 :- pred string.substring(string::in, int::in, int::in, string::uo) is det.
 
-    % string.substring_by_codepoint(String, Start, Count, Substring):
-    % `Substring' is first the `Count' code points in what would remain
-    % of `String' after the first `Start' code points were removed.
+    % string.between_codepoints(String, Start, End, Substring):
+    % `Substring' is the part of `String' between the code point positions
+    % `Start' and `End'.
     % (If `Start' is out of the range [0, length of `String'], it is treated
     % as if it were the nearest end-point of that range.
-    % If `Count' is out of the range [0, length of `String' - `Start'],
+    % If `End' is out of the range [`Start', length of `String'],
     % it is treated as if it were the nearest end-point of that range.)
     %
-:- func string.substring_by_codepoint(string::in, int::in, int::in)
+:- func string.between_codepoints(string::in, int::in, int::in)
     = (string::uo) is det.
-:- pred string.substring_by_codepoint(string::in, int::in, int::in, string::uo)
+:- pred string.between_codepoints(string::in, int::in, int::in, string::uo)
     is det.
 
-    % string.unsafe_substring(String, Start, Count, Substring):
-    % `Substring' is first the `Count' code _units_ in what would remain
-    % of `String' after the first `Start' code _units_ were removed.
-    % WARNING: if `Start' is out of the range [0, length of `String'],
-    % or if `Count' is out of the range [0, length of `String' - `Start'],
+    % string.unsafe_between(String, Start, End, Substring):
+    % `Substring' consists of the segment of `String' within the half-open
+    % interval [Start, End), where `Start' and `End' are code unit offsets.
+    % WARNING: if `Start' is out of the range [0, length of `String'] or
+    % `End' is out of the range [`Start', length of `String']
     % then the behaviour is UNDEFINED. Use with care!
     % This version takes time proportional to the length of the substring,
     % whereas string.substring may take time proportional to the length
     % of the whole string.
     %
+:- func string.unsafe_between(string::in, int::in, int::in) = (string::uo)
+    is det.
+:- pred string.unsafe_between(string::in, int::in, int::in, string::uo)
+    is det.
+
+    % string.unsafe_substring(String, Start, Count, Substring):
+    % Please use string.unsafe_between instead.
+    %
+:- pragma obsolete(string.unsafe_substring/3).
+:- pragma obsolete(string.unsafe_substring/4).
 :- func string.unsafe_substring(string::in, int::in, int::in) = (string::uo)
     is det.
 :- pred string.unsafe_substring(string::in, int::in, int::in, string::uo)
@@ -1083,11 +1156,11 @@
 string.replace(Str, Pat, Subst, Result) :-
     sub_string_search(Str, Pat, Index),
 
-    Initial = string.unsafe_substring(Str, 0, Index),
+    Initial = string.unsafe_between(Str, 0, Index),
 
     BeginAt = Index + string.length(Pat),
-    Length = string.length(Str) - BeginAt,
-    Final = string.unsafe_substring(Str, BeginAt, Length),
+    EndAt = string.length(Str),
+    Final = string.unsafe_between(Str, BeginAt, EndAt),
 
     Result = string.append_list([Initial, Subst, Final]).
 
@@ -1108,14 +1181,12 @@ string.replace_all(Str, Pat, Subst, Result) :-
 
 string.replace_all_2(Str, Pat, Subst, PatLength, BeginAt, Result0) = Result :-
     ( sub_string_search_start(Str, Pat, BeginAt, Index) ->
-        Length = Index - BeginAt,
-        Initial = string.unsafe_substring(Str, BeginAt, Length),
+        Initial = string.unsafe_between(Str, BeginAt, Index),
         Start = Index + PatLength,
         Result = string.replace_all_2(Str, Pat, Subst, PatLength, Start,
             [Subst, Initial | Result0])
     ;
-        Length = string.length(Str) - BeginAt,
-        EndString = string.unsafe_substring(Str, BeginAt, Length),
+        EndString = string.unsafe_between(Str, BeginAt, length(Str)),
         Result = [EndString | Result0]
     ).
 
@@ -1127,13 +1198,12 @@ string.base_string_to_int(Base, String, Int) :-
     Len = string.count_codepoints(String),
     ( Char = ('-') ->
         Len > 1,
-        foldl_substring(accumulate_negative_int(Base), String, 1,
-            Len - 1, 0, Int)
+        foldl_between(accumulate_negative_int(Base), String, 1, Len, 0, Int)
     ; Char = ('+') ->
         Len > 1,
-        foldl_substring(accumulate_int(Base), String, 1, Len - 1, 0, Int)
+        foldl_between(accumulate_int(Base), String, 1, Len, 0, Int)
     ;
-        foldl_substring(accumulate_int(Base), String, 0, Len, 0, Int)
+        foldl_between(accumulate_int(Base), String, 0, Len, 0, Int)
     ).
 
 :- pred accumulate_int(int::in, char::in, int::in, int::out) is semidet.
@@ -1182,71 +1252,69 @@ string.det_set_char(Char, Int, String0, String) :-
 
 string.foldl(Closure, String, !Acc) :-
     string.length(String, Length),
-    string.foldl_substring(Closure, String, 0, Length, !Acc).
+    string.foldl_between(Closure, String, 0, Length, !Acc).
 
 string.foldl2(Closure, String, !Acc1, !Acc2) :-
     string.length(String, Length),
-    string.foldl2_substring(Closure, String, 0, Length, !Acc1, !Acc2).
+    string.foldl2_between(Closure, String, 0, Length, !Acc1, !Acc2).
 
-string.foldl_substring(Closure, String, Start0, Count0, !Acc) :-
+string.foldl_between(Closure, String, Start0, End0, !Acc) :-
     Start = max(0, Start0),
-    Count = min(Count0, length(String) - Start),
-    End = Start + Count,
-    string.foldl_substring_2(Closure, String, Start, End, !Acc).
+    End = min(End0, length(String)),
+    string.foldl_between_2(Closure, String, Start, End, !Acc).
 
-string.foldl2_substring(Closure, String, Start0, Count0, !Acc1, !Acc2) :-
+string.foldl2_between(Closure, String, Start0, End0, !Acc1, !Acc2) :-
     Start = max(0, Start0),
-    Count = min(Count0, length(String) - Start),
-    End = Start + Count,
-    string.foldl2_substring_2(Closure, String, Start, End, !Acc1, !Acc2).
+    End = min(End0, length(String)),
+    string.foldl2_between_2(Closure, String, Start, End, !Acc1, !Acc2).
 
-:- pred string.foldl_substring_2(pred(char, A, A), string, int, int, A, A).
-:- mode string.foldl_substring_2(pred(in, di, uo) is det, in, in, in,
+:- pred string.foldl_between_2(pred(char, A, A), string, int, int, A, A).
+:- mode string.foldl_between_2(pred(in, di, uo) is det, in, in, in,
     di, uo) is det.
-:- mode string.foldl_substring_2(pred(in, in, out) is det, in, in, in,
+:- mode string.foldl_between_2(pred(in, in, out) is det, in, in, in,
     in, out) is det.
-:- mode string.foldl_substring_2(pred(in, in, out) is semidet, in, in, in,
+:- mode string.foldl_between_2(pred(in, in, out) is semidet, in, in, in,
     in, out) is semidet.
-:- mode string.foldl_substring_2(pred(in, in, out) is nondet, in, in, in,
+:- mode string.foldl_between_2(pred(in, in, out) is nondet, in, in, in,
     in, out) is nondet.
-:- mode string.foldl_substring_2(pred(in, in, out) is multi, in, in, in,
+:- mode string.foldl_between_2(pred(in, in, out) is multi, in, in, in,
     in, out) is multi.
 
-string.foldl_substring_2(Closure, String, I, End, !Acc) :-
+string.foldl_between_2(Closure, String, I, End, !Acc) :-
     (
         I < End,
         string.unsafe_index_next(String, I, J, Char),
         J =< End
     ->
         Closure(Char, !Acc),
-        string.foldl_substring_2(Closure, String, J, End, !Acc)
+        string.foldl_between_2(Closure, String, J, End, !Acc)
     ;
         true
     ).
 
-:- pred string.foldl2_substring_2(pred(char, A, A, B, B), string, int, int,
+:- pred string.foldl2_between_2(pred(char, A, A, B, B), string, int, int,
     A, A, B, B).
-:- mode string.foldl2_substring_2(pred(in, di, uo, di, uo) is det,
+:- mode string.foldl2_between_2(pred(in, di, uo, di, uo) is det,
     in, in, in, di, uo, di, uo) is det.
-:- mode string.foldl2_substring_2(pred(in, in, out, di, uo) is det,
+:- mode string.foldl2_between_2(pred(in, in, out, di, uo) is det,
     in, in, in, in, out, di, uo) is det.
-:- mode string.foldl2_substring_2(pred(in, in, out, in, out) is det,
+:- mode string.foldl2_between_2(pred(in, in, out, in, out) is det,
     in, in, in, in, out, in, out) is det.
-:- mode string.foldl2_substring_2(pred(in, in, out, in, out) is semidet,
+:- mode string.foldl2_between_2(pred(in, in, out, in, out) is semidet,
     in, in, in, in, out, in, out) is semidet.
-:- mode string.foldl2_substring_2(pred(in, in, out, in, out) is nondet,
+:- mode string.foldl2_between_2(pred(in, in, out, in, out) is nondet,
     in, in, in, in, out, in, out) is nondet.
-:- mode string.foldl2_substring_2(pred(in, in, out, in, out) is multi,
+:- mode string.foldl2_between_2(pred(in, in, out, in, out) is multi,
     in, in, in, in, out, in, out) is multi.
 
-string.foldl2_substring_2(Closure, String, I, End, !Acc1, !Acc2) :-
+string.foldl2_between_2(Closure, String, I, End, !Acc1, !Acc2) :-
     (
         I < End,
         string.unsafe_index_next(String, I, J, Char),
         J =< End
     ->
         Closure(Char, !Acc1, !Acc2),
-        string.foldl2_substring_2(Closure, String, J, End, !Acc1, !Acc2)
+        string.foldl2_between_2(Closure, String, J, End, !Acc1, !Acc2)
     ;
         true
     ).
@@ -1255,43 +1323,62 @@ string.foldr(F, String, Acc0) = Acc :-
     Closure = ( pred(X::in, Y::in, Z::out) is det :- Z = F(X, Y)),
     string.foldr(Closure, String, Acc0, Acc).
 
-string.foldr_substring(F, String, Start, Count, Acc0) = Acc :-
+string.foldr_between(F, String, Start, Count, Acc0) = Acc :-
     Closure = ( pred(X::in, Y::in, Z::out) is det :- Z = F(X, Y) ),
-    string.foldr_substring(Closure, String, Start, Count, Acc0, Acc).
+    string.foldr_between(Closure, String, Start, Count, Acc0, Acc).
 
 string.foldr(Closure, String, Acc0, Acc) :-
-    string.foldr_substring(Closure, String, 0, length(String), Acc0, Acc).
+    string.foldr_between(Closure, String, 0, length(String), Acc0, Acc).
 
-string.foldr_substring(Closure, String, Start0, Count0, Acc0, Acc) :-
+string.foldr_between(Closure, String, Start0, End0, Acc0, Acc) :-
     Start = max(0, Start0),
-    Count = min(Count0, length(String) - Start),
-    End = Start + Count,
-    string.foldr_substring_2(Closure, String, Start, End, Acc0, Acc).
+    End = min(End0, length(String)),
+    string.foldr_between_2(Closure, String, Start, End, Acc0, Acc).
 
-:- pred string.foldr_substring_2(pred(char, T, T), string, int, int, T, T).
-:- mode string.foldr_substring_2(pred(in, in, out) is det, in, in, in,
+:- pred string.foldr_between_2(pred(char, T, T), string, int, int, T, T).
+:- mode string.foldr_between_2(pred(in, in, out) is det, in, in, in,
     in, out) is det.
-:- mode string.foldr_substring_2(pred(in, di, uo) is det, in, in, in,
+:- mode string.foldr_between_2(pred(in, di, uo) is det, in, in, in,
     di, uo) is det.
-:- mode string.foldr_substring_2(pred(in, in, out) is semidet, in, in, in,
+:- mode string.foldr_between_2(pred(in, in, out) is semidet, in, in, in,
     in, out) is semidet.
-:- mode string.foldr_substring_2(pred(in, in, out) is nondet, in, in, in,
+:- mode string.foldr_between_2(pred(in, in, out) is nondet, in, in, in,
     in, out) is nondet.
-:- mode string.foldr_substring_2(pred(in, in, out) is multi, in, in, in,
+:- mode string.foldr_between_2(pred(in, in, out) is multi, in, in, in,
     in, out) is multi.
 
-string.foldr_substring_2(Closure, String, Start, I, !Acc) :-
+string.foldr_between_2(Closure, String, Start, I, !Acc) :-
     (
         I > Start,
         string.unsafe_prev_index(String, I, J, Char),
         J >= Start
     ->
         Closure(Char, !Acc),
-        string.foldr_substring_2(Closure, String, Start, J, !Acc)
+        string.foldr_between_2(Closure, String, Start, J, !Acc)
     ;
         true
     ).
 
+string.foldl_substring(F, String, Start, Count, Acc0) = Acc :-
+    convert_endpoints(Start, Count, ClampStart, ClampEnd),
+    Acc = string.foldl_between(F, String, ClampStart, ClampEnd, Acc0).
+
+string.foldl_substring(Closure, String, Start, Count, !Acc) :-
+    convert_endpoints(Start, Count, ClampStart, ClampEnd),
+    string.foldl_between(Closure, String, ClampStart, ClampEnd, !Acc).
+
+string.foldl2_substring(Closure, String, Start, Count, !Acc1, !Acc2) :-
+    convert_endpoints(Start, Count, ClampStart, ClampEnd),
+    string.foldl2_between(Closure, String, ClampStart, ClampEnd, !Acc1, !Acc2).
+
+string.foldr_substring(F, String, Start, Count, Acc0) = Acc :-
+    convert_endpoints(Start, Count, ClampStart, ClampEnd),
+    Acc = string.foldr_between(F, String, ClampStart, ClampEnd, Acc0).
+
+string.foldr_substring(Closure, String, Start, Count, !Acc) :-
+    convert_endpoints(Start, Count, ClampStart, ClampEnd),
+    string.foldr_between(Closure, String, ClampStart, ClampEnd, !Acc).
+
 string.left(String, Count, LeftString) :-
     string.split(String, Count, LeftString, _RightString).
 
@@ -1372,7 +1459,7 @@ prefix_2_iii(String, Prefix, I) :-
 
 prefix_2_ioi(String, Prefix, Cur) :-
     (
-        Prefix = unsafe_substring(String, 0, Cur)
+        Prefix = unsafe_between(String, 0, Cur)
     ;
         string.unsafe_index_next(String, Cur, Next, _),
         prefix_2_ioi(String, Prefix, Next)
@@ -1405,7 +1492,7 @@ suffix_2_iiii(String, Suffix, I, Offset, Len) :-
 
 suffix_2_ioii(String, Suffix, Cur, Len) :-
     (
-        string.unsafe_substring(String, Cur, Len - Cur, Suffix)
+        string.unsafe_between(String, Cur, Len, Suffix)
     ;
         string.unsafe_prev_index(String, Cur, Prev, _),
         suffix_2_ioii(String, Suffix, Prev, Len)
@@ -2567,7 +2654,8 @@ sub_string_search_start_2(String, SubString, I, Length, SubLength, Index) :-
         % mode of substring, so this ends up calling the (in, in, in) = out
         % mode and then doing the unification. This will create a lot of
         % unnecessary garbage.
-        substring(String, I, SubLength) = SubString
+        % XXX This will abort if either index is not at a code point boundary.
+        between(String, I, I + SubLength) = SubString
     ->
         Index = I
     ;
@@ -3256,7 +3344,7 @@ format_char(Flags, Width, Char) = String :-
 format_string(Flags, Width, Prec, OldStr) = NewStr :-
     (
         Prec = yes(NumChars),
-        PrecStr = string.substring_by_codepoint(OldStr, 0, NumChars)
+        PrecStr = string.left_by_codepoint(OldStr, NumChars)
     ;
         Prec = no,
         PrecStr = OldStr
@@ -3433,7 +3521,7 @@ format_float(Flags, Width, Prec, Float) = NewFloat :-
             Prec = yes(0)
         ->
             PrecStrLen = string.count_codepoints(PrecStr),
-            PrecModStr = string.substring(PrecStr, 0, PrecStrLen - 1)
+            PrecModStr = string.between(PrecStr, 0, PrecStrLen - 1)
         ;
             PrecModStr = PrecStr
         )
@@ -3785,8 +3873,8 @@ change_to_g_notation(Float, Prec, E, Flags) = FormattedFloat :-
             split_at_exponent(ScientificFloat, BaseStr, ExponentStr),
             Exp = string.det_to_int(ExponentStr),
             split_at_decimal_point(BaseStr, MantissaStr, FractionStr),
-            RestMantissaStr = substring(FractionStr, 0, Exp),
-            NewFraction = substring(FractionStr, Exp, Prec - Exp - 1),
+            RestMantissaStr = between(FractionStr, 0, Exp),
+            NewFraction = between(FractionStr, Exp, Prec - 1),
             FormattedFloat0 = MantissaStr ++ RestMantissaStr
                 ++ "." ++ NewFraction
         ),
@@ -3941,7 +4029,7 @@ calculate_base_unsafe(Float, Prec) = Exp :-
     split_at_decimal_point(Float, MantissaStr, FractionStr),
     ( Place < 0 ->
         DecimalPos = abs(Place),
-        PaddedMantissaStr = string.substring(FractionStr, 0, DecimalPos),
+        PaddedMantissaStr = string.between(FractionStr, 0, DecimalPos),
 
         % Get rid of superfluous zeros.
         MantissaInt = string.det_to_int(PaddedMantissaStr),
@@ -3949,11 +4037,11 @@ calculate_base_unsafe(Float, Prec) = Exp :-
 
         % Create fractional part.
         PaddedFractionStr = pad_right(FractionStr, '0', Prec + 1),
-        ExpFractionStr = string.substring(PaddedFractionStr, DecimalPos,
-            Prec + 1)
+        ExpFractionStr = string.between(PaddedFractionStr, DecimalPos,
+            DecimalPos + Prec + 1)
     ; Place > 0 ->
-        ExpMantissaStr = string.substring(MantissaStr, 0, 1),
-        FirstHalfOfFractionStr = string.substring(MantissaStr, 1, Place),
+        ExpMantissaStr = string.between(MantissaStr, 0, 1),
+        FirstHalfOfFractionStr = string.between(MantissaStr, 1, Place),
         ExpFractionStr = FirstHalfOfFractionStr ++ FractionStr
     ;
         ExpMantissaStr = MantissaStr,
@@ -3976,7 +4064,7 @@ change_precision(Prec, OldFloat) = NewFloat :-
         PrecFracStr = string.pad_right(FractionStr, '0', Prec),
         PrecMantissaStr = MantissaStr
     ; Prec < FracStrLen ->
-        UnroundedFrac = string.substring(FractionStr, 0, Prec),
+        UnroundedFrac = string.between(FractionStr, 0, Prec),
         NextDigit = string.det_index(FractionStr, Prec),
         (
             UnroundedFrac \= "",
@@ -3989,7 +4077,7 @@ change_precision(Prec, OldFloat) = NewFloat :-
                 string.count_codepoints(NewPrecFracStr) >
                     string.count_codepoints(UnroundedFrac)
             ->
-                PrecFracStr = substring(NewPrecFracStr, 1, Prec),
+                PrecFracStr = between(NewPrecFracStr, 1, 1 + Prec),
                 PrecMantissaInt = det_to_int(MantissaStr) + 1,
                 PrecMantissaStr = int_to_string(PrecMantissaInt)
             ;
@@ -5348,16 +5436,14 @@ string.mercury_append(X, Y, Z) :-
 
 /*-----------------------------------------------------------------------*/
 
-substring(Str, !.Start, !.Count, SubStr) :-
-    ( !.Count =< 0 ->
+between(Str, Start, End, SubStr) :-
+    ( Start >= End ->
         SubStr = ""
     ;
-        % Be careful about integer overflow when Count = max_int.
         Len = string.length(Str),
-        max(0, !Start),
-        min(Len, !Start),
-        min(Len - !.Start, !Count),
-        CharList = strchars(!.Start, !.Start + !.Count, Str),
+        max(0, Start, ClampStart),
+        min(Len, End, ClampEnd),
+        CharList = strchars(ClampStart, ClampEnd, Str),
         SubStr = string.from_char_list(CharList)
     ).
 
@@ -5376,24 +5462,26 @@ strchars(I, End, Str) = Chars :-
     ).
 
 :- pragma foreign_proc("C",
-    string.substring(Str::in, Start::in, Count::in, SubString::uo),
+    string.between(Str::in, Start::in, End::in, SubString::uo),
     [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
         does_not_affect_liveness, may_not_duplicate, no_sharing],
 "{
     MR_Integer  len;
+    MR_Integer  Count;
     MR_Word     tmp;
 
     if (Start < 0) Start = 0;
-    if (Count <= 0) {
+    if (End <= Start) {
         MR_make_aligned_string(SubString, """");
     } else {
         len = strlen(Str);
         if (Start > len) {
             Start = len;
         }
-        if (Count > len - Start) {
-            Count = len - Start;
+        if (End > len) {
+            End = len;
         }
+        Count = End - Start;
         MR_allocate_aligned_string_msg(SubString, Count, MR_ALLOC_ID);
         MR_memcpy(SubString, Str + Start, Count);
         SubString[Count] = '\\0';
@@ -5401,117 +5489,143 @@ strchars(I, End, Str) = Chars :-
 }").
 
 :- pragma foreign_proc("C#",
-    string.substring(Str::in, Start::in, Count::in, SubString::uo),
+    string.between(Str::in, Start::in, End::in, SubString::uo),
     [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
         does_not_affect_liveness, may_not_duplicate, no_sharing],
 "
     if (Start < 0) Start = 0;
-    if (Count <= 0) {
+    if (End <= Start) {
         SubString = """";
     } else {
         int len = Str.Length;
         if (Start > len) {
             Start = len;
         }
-        if (Count > len - Start) {
-            Count = len - Start;
+        if (End > len) {
+            End = len;
         }
-        SubString = Str.Substring(Start, Count);
+        SubString = Str.Substring(Start, End - Start);
     }
 ").
 
 :- pragma foreign_proc("Java",
-    string.substring(Str::in, Start::in, Count::in, SubString::uo),
+    string.between(Str::in, Start::in, End::in, SubString::uo),
     [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
         does_not_affect_liveness, may_not_duplicate, no_sharing],
 "
     if (Start < 0) Start = 0;
-    if (Count <= 0) {
+    if (End <= Start) {
         SubString = """";
     } else {
         int len = Str.length();
         if (Start > len) {
             Start = len;
         }
-        if (Count > len - Start) {
-            Count = len - Start;
+        if (End > len) {
+            End = len;
         }
-        SubString = Str.substring(Start, Start + Count);
+        SubString = Str.substring(Start, End);
     }
 ").
 
 :- pragma foreign_proc("Erlang",
-    string.substring(Str::in, Start0::in, Count0::in, SubString::uo),
+    string.between(Str::in, Start0::in, End0::in, SubString::uo),
     [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
         does_not_affect_liveness],
 "
-    if Start0 < 0 ->
-        Start = 0;
-    true ->
-        Start = Start0
-    end,
-    if
-        Count0 =< 0 ->
-            SubString = <<>>;
-        Start > size(Str) ->
-            SubString = <<>>;
-        true ->
-            if Count0 > size(Str) - Start ->
-                Count = size(Str) - Start;
-            true ->
-                Count = Count0
-            end,
-            <<_:Start/binary, SubString:Count/binary, _/binary>> = Str
+    Start = max(Start0, 0),
+    End = min(End0, size(Str)),
+    Count = End - Start,
+    if Count =< 0 ->
+        SubString = <<>>
+    ; true ->
+        <<_:Start/binary, SubString:Count/binary, _/binary>> = Str
     end
 ").
 
-string.substring_by_codepoint(Str, Start, Count) = SubString :-
-    string.substring_by_codepoint(Str, Start, Count, SubString).
+string.between(Str, Start, End) = SubString :-
+    string.between(Str, Start, End, SubString).
 
-string.substring_by_codepoint(Str, Start, Count, SubString) :-
+string.substring(Str, Start, Count) = SubString :-
+    string.substring(Str, Start, Count, SubString).
+
+string.substring(Str, Start, Count, SubString) :-
+    convert_endpoints(Start, Count, ClampStart, ClampEnd),
+    string.between(Str, ClampStart, ClampEnd, SubString).
+
+:- pred convert_endpoints(int::in, int::in, int::out, int::out) is det.
+
+convert_endpoints(Start, Count, ClampStart, ClampEnd) :-
+    ClampStart = int.max(0, Start),
+    ( Count =< 0 ->
+        ClampEnd = ClampStart
+    ;
+        End = ClampStart + Count,
+        % Check for overflow.
+        ClampEnd = ( End =< 0 -> max_int ; End )
+    ).
+
+%-----------------------------------------------------------------------------%
+
+string.between_codepoints(Str, Start, End) = SubString :-
+    string.between_codepoints(Str, Start, End, SubString).
+
+string.between_codepoints(Str, Start, End, SubString) :-
     ( string.codepoint_offset(Str, Start, StartOffset0) ->
         StartOffset = StartOffset0
     ;
         StartOffset = 0
     ),
-    ( string.codepoint_offset(Str, StartOffset, Count, EndOffset0) ->
+    ( string.codepoint_offset(Str, End, EndOffset0) ->
         EndOffset = EndOffset0
     ;
         EndOffset = string.length(Str)
     ),
-    OffsetCount = EndOffset - StartOffset,
-    string.substring(Str, StartOffset, OffsetCount, SubString).
+    string.between(Str, StartOffset, EndOffset, SubString).
 
 :- pragma foreign_proc("C",
-    string.unsafe_substring(Str::in, Start::in, Count::in, SubString::uo),
+    string.unsafe_between(Str::in, Start::in, End::in, SubString::uo),
     [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
         does_not_affect_liveness, no_sharing],
 "{
-    MR_Integer len;
+    MR_Integer Count;
 
+    Count = End - Start;
     MR_allocate_aligned_string_msg(SubString, Count, MR_ALLOC_ID);
     MR_memcpy(SubString, Str + Start, Count);
     SubString[Count] = '\\0';
 }").
 :- pragma foreign_proc("C#",
-    string.unsafe_substring(Str::in, Start::in, Count::in, SubString::uo),
+    string.unsafe_between(Str::in, Start::in, End::in, SubString::uo),
     [will_not_call_mercury, promise_pure, thread_safe],
 "{
-    SubString = Str.Substring(Start, Count);
+    SubString = Str.Substring(Start, End - Start);
 }").
 :- pragma foreign_proc("Java",
-    string.unsafe_substring(Str::in, Start::in, Count::in, SubString::uo),
+    string.unsafe_between(Str::in, Start::in, End::in, SubString::uo),
     [will_not_call_mercury, promise_pure, thread_safe],
 "
-    SubString = Str.substring(Start, Start + Count);
+    SubString = Str.substring(Start, End);
 ").
 :- pragma foreign_proc("Erlang",
-    string.unsafe_substring(Str::in, Start::in, Count::in, SubString::uo),
+    string.unsafe_between(Str::in, Start::in, End::in, SubString::uo),
     [will_not_call_mercury, promise_pure, thread_safe],
 "
+    Count = End - Start,
     << _:Start/binary, SubString:Count/binary, _/binary >> = Str
 ").
 
+string.unsafe_between(Str, Start, End) = SubString :-
+    string.unsafe_between(Str, Start, End, SubString).
+
+string.unsafe_substring(Str, Start, Count) = SubString :-
+    string.unsafe_between(Str, Start, Start + Count) = SubString.
+
+string.unsafe_substring(Str, Start, Count, SubString) :-
+    string.unsafe_between(Str, Start, Start + Count, SubString).
+
+%-----------------------------------------------------------------------------%
+
 :- pragma foreign_proc("C",
     string.split(Str::in, Count::in, Left::uo, Right::uo),
     [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
@@ -6009,9 +6123,9 @@ string.foldl(F, S, A) = B :-
     P = ( pred(X::in, Y::in, Z::out) is det :- Z = F(X, Y) ),
     string.foldl(P, S, A, B).
 
-string.foldl_substring(F, S, Start, Count, A) = B :-
+string.foldl_between(F, S, Start, End, A) = B :-
     P = ( pred(X::in, Y::in, Z::out) is det :- Z = F(X, Y) ),
-    string.foldl_substring(P, S, Start, Count, A, B).
+    string.foldl_between(P, S, Start, End, A, B).
 
 string.left(S1, N) = S2 :-
     string.left(S1, N, S2).
@@ -6019,12 +6133,6 @@ string.left(S1, N) = S2 :-
 string.right(S1, N) = S2 :-
     string.right(S1, N, S2).
 
-string.substring(S1, N1, N2) = S2 :-
-    string.substring(S1, N1, N2, S2).
-
-string.unsafe_substring(S1, N1, N2) = S2 :-
-    string.unsafe_substring(S1, N1, N2, S2).
-
 string.format(S1, PT) = S2 :-
     string.format(S1, PT, S2).
 
@@ -6042,7 +6150,7 @@ words_2(SepP, String, WordStart, Words) :-
     ( WordEnd = WordStart ->
         Words = []
     ;
-        string.unsafe_substring(String, WordStart, WordEnd - WordStart, Word),
+        string.unsafe_between(String, WordStart, WordEnd, Word),
         next_boundary(SepP, String, WordEnd, NextWordStart),
         ( WordEnd = NextWordStart ->
             Words = [Word]
@@ -6091,7 +6199,7 @@ split_at_separator_2(DelimP, Str, I, SegEnd, Acc0, Acc) :-
         ( DelimP(C) ->
             % Chop here.
             SegStart = I,
-            Seg = string.unsafe_substring(Str, SegStart, SegEnd - SegStart),
+            Seg = string.unsafe_between(Str, SegStart, SegEnd),
             split_at_separator_2(DelimP, Str, J, J, [Seg | Acc0], Acc)
         ;
             % Extend current segment.
@@ -6099,7 +6207,7 @@ split_at_separator_2(DelimP, Str, I, SegEnd, Acc0, Acc) :-
         )
     ;
         % We've reached the beginning of the string.
-        Seg = string.unsafe_substring(Str, 0, SegEnd),
+        Seg = string.unsafe_between(Str, 0, SegEnd),
         Acc = [Seg | Acc0]
     ).
 
@@ -6117,7 +6225,7 @@ split_at_string(Needle, Total) =
 
 split_at_string(StartAt, NeedleLen, Needle, Total) = Out :-
     ( sub_string_search_start(Total, Needle, StartAt, NeedlePos) ->
-        BeforeNeedle = substring(Total, StartAt, NeedlePos - StartAt),
+        BeforeNeedle = between(Total, StartAt, NeedlePos),
         Tail = split_at_string(NeedlePos+NeedleLen, NeedleLen, Needle, Total),
         Out = [BeforeNeedle | Tail]
     ;
@@ -6164,7 +6272,7 @@ lstrip(S) = lstrip_pred(is_whitespace, S).
 strip(S0) = S :-
     L = prefix_length(is_whitespace, S0),
     R = suffix_length(is_whitespace, S0),
-    S = substring(S0, L, length(S0) - L - R).
+    S = between(S0, L, length(S0) - R).
 
 %-----------------------------------------------------------------------------%
 
diff --git a/mdbcomp/trace_counts.m b/mdbcomp/trace_counts.m
index bba6938..163bdec 100644
--- a/mdbcomp/trace_counts.m
+++ b/mdbcomp/trace_counts.m
@@ -710,7 +710,7 @@ string_to_goal_path(String) = Path :-
     string.prefix(String, "<"),
     string.suffix(String, ">"),
     string.length(String, Length),
-    string.substring(String, 1, Length-2, SubString),
+    string.between(String, 1, Length - 1, SubString),
     rev_goal_path_from_string(SubString, Path).
 
     % This function should be kept in sync with the MR_named_count_port array
diff --git a/profiler/demangle.m b/profiler/demangle.m
index cf68b1f..a5bc6bc 100644
--- a/profiler/demangle.m
+++ b/profiler/demangle.m
@@ -844,7 +844,7 @@ remove_maybe_module_prefix(MaybeModule, StringsToStopAt, String0, String) :-
         string.left(String0, Index, Module),
         string.length(String0, Len),
         Index2 = Index + 2,
-        string.substring(String0, Index2, Len, String1),
+        string.between(String0, Index2, Len, String1),
         (
             remove_maybe_module_prefix(yes(SubModule),
                 StringsToStopAt, String1, String2)
@@ -869,7 +869,7 @@ remove_maybe_pred_name(MaybePredName, String0, String) :-
         string.left(String0, Index, PredName),
         string.length(String0, Len),
         Index2 = Index + 2,
-        string.substring(String0, Index2, Len, String),
+        string.between(String0, Index2, Len, String),
         MaybePredName = yes(PredName)
     ;
         String = String0,
diff --git a/tests/general/Mercury.options b/tests/general/Mercury.options
index 744ffa9..613a916 100644
--- a/tests/general/Mercury.options
+++ b/tests/general/Mercury.options
@@ -9,6 +9,10 @@ MCFLAGS-mode_inference_reorder = --infer-all
 MCFLAGS-intermod_type = --intermodule-optimization
 MCFLAGS-intermod_type2 = --intermodule-optimization
 
+# These test cases also check obsolete procedures.
+MCFLAGS-string_foldl_substring = --no-warn-obsolete
+MCFLAGS-string_foldr_substring = --no-warn-obsolete
+
 # In grade `none' with options `-O1 --opt-space' on kryten
 # (a sparc-sun-solaris2.5 system), string_test needs to be linked
 # with `--no-strip', otherwise it gets a seg fault. 
diff --git a/tests/general/string_foldl_substring.exp b/tests/general/string_foldl_substring.exp
index 619ba98..1bec99d 100644
--- a/tests/general/string_foldl_substring.exp
+++ b/tests/general/string_foldl_substring.exp
@@ -4,4 +4,13 @@ rev("Hello, World!",  0, -5) = ""
 rev("Hello, World!", -5, 12) = "dlroW ,olleH"
 rev("Hello, World!", -5, 50) = "!dlroW ,olleH"
 rev("Hello, World!",  7,  0) = ""
+rev("Hello, World!",  7, 12) = "dlroW"
 rev("Hello, World!", 50, 10) = ""
+rev_old("Hello, World!",  0,  5) = "olleH"
+rev_old("Hello, World!",  0, 50) = "!dlroW ,olleH"
+rev_old("Hello, World!",  0, -5) = ""
+rev_old("Hello, World!", -5, 12) = "dlroW ,olleH"
+rev_old("Hello, World!", -5, 50) = "!dlroW ,olleH"
+rev_old("Hello, World!",  7,  0) = ""
+rev_old("Hello, World!",  7, 12) = "!dlroW"
+rev_old("Hello, World!", 50, 10) = ""
diff --git a/tests/general/string_foldl_substring.m b/tests/general/string_foldl_substring.m
index 2061a57..ad24895 100644
--- a/tests/general/string_foldl_substring.m
+++ b/tests/general/string_foldl_substring.m
@@ -39,14 +39,40 @@ main(!IO) :-
                rev("Hello, World!", -5, 50),
         "\"\nrev(\"Hello, World!\",  7,  0) = \"",
                rev("Hello, World!",  7,  0),
+        "\"\nrev(\"Hello, World!\",  7, 12) = \"",
+               rev("Hello, World!",  7, 12),
         "\"\nrev(\"Hello, World!\", 50, 10) = \"",
                rev("Hello, World!", 50, 10),
         "\"\n"
+    ], !IO),
+    io__write_strings([
+            "rev_old(\"Hello, World!\",  0,  5) = \"",
+               rev_old("Hello, World!",  0,  5),
+        "\"\nrev_old(\"Hello, World!\",  0, 50) = \"",
+               rev_old("Hello, World!",  0, 50),
+        "\"\nrev_old(\"Hello, World!\",  0, -5) = \"",
+               rev_old("Hello, World!",  0, -5),
+        "\"\nrev_old(\"Hello, World!\", -5, 12) = \"",
+               rev_old("Hello, World!", -5, 12),
+        "\"\nrev_old(\"Hello, World!\", -5, 50) = \"",
+               rev_old("Hello, World!", -5, 50),
+        "\"\nrev_old(\"Hello, World!\",  7,  0) = \"",
+               rev_old("Hello, World!",  7,  0),
+        "\"\nrev_old(\"Hello, World!\",  7, 12) = \"",
+               rev_old("Hello, World!",  7, 12),
+        "\"\nrev_old(\"Hello, World!\", 50, 10) = \"",
+               rev_old("Hello, World!", 50, 10),
+        "\"\n"
     ], !IO).
 
 :- func rev(string, int, int) = string.
 
 rev(S, I, N) =
+    from_char_list(foldl_between(func(X, Xs) = [X | Xs], S, I, N, [])).
+
+:- func rev_old(string, int, int) = string.
+
+rev_old(S, I, N) =
     from_char_list(foldl_substring(func(X, Xs) = [X | Xs], S, I, N, [])).
 
 %-----------------------------------------------------------------------------%
diff --git a/tests/general/string_foldr_substring.exp b/tests/general/string_foldr_substring.exp
index c3e0420..63cfeca 100644
--- a/tests/general/string_foldr_substring.exp
+++ b/tests/general/string_foldr_substring.exp
@@ -4,4 +4,13 @@ sub("Hello, World!",  0, -5) = ""
 sub("Hello, World!", -5, 12) = "Hello, World"
 sub("Hello, World!", -5, 50) = "Hello, World!"
 sub("Hello, World!",  7,  0) = ""
+sub("Hello, World!",  7, 12) = "World"
 sub("Hello, World!", 50, 10) = ""
+sub_old("Hello, World!",  0,  5) = "Hello"
+sub_old("Hello, World!",  0, 50) = "Hello, World!"
+sub_old("Hello, World!",  0, -5) = ""
+sub_old("Hello, World!", -5, 12) = "Hello, World"
+sub_old("Hello, World!", -5, 50) = "Hello, World!"
+sub_old("Hello, World!",  7,  0) = ""
+sub_old("Hello, World!",  7, 12) = "World!"
+sub_old("Hello, World!", 50, 10) = ""
diff --git a/tests/general/string_foldr_substring.m b/tests/general/string_foldr_substring.m
index 2f22d51..fdfcb38 100644
--- a/tests/general/string_foldr_substring.m
+++ b/tests/general/string_foldr_substring.m
@@ -39,14 +39,40 @@ main(!IO) :-
                sub("Hello, World!", -5, 50),
         "\"\nsub(\"Hello, World!\",  7,  0) = \"",
                sub("Hello, World!",  7,  0),
+        "\"\nsub(\"Hello, World!\",  7, 12) = \"",
+               sub("Hello, World!",  7, 12),
         "\"\nsub(\"Hello, World!\", 50, 10) = \"",
                sub("Hello, World!", 50, 10),
         "\"\n"
+    ], !IO),
+    io__write_strings([
+            "sub_old(\"Hello, World!\",  0,  5) = \"",
+               sub_old("Hello, World!",  0,  5),
+        "\"\nsub_old(\"Hello, World!\",  0, 50) = \"",
+               sub_old("Hello, World!",  0, 50),
+        "\"\nsub_old(\"Hello, World!\",  0, -5) = \"",
+               sub_old("Hello, World!",  0, -5),
+        "\"\nsub_old(\"Hello, World!\", -5, 12) = \"",
+               sub_old("Hello, World!", -5, 12),
+        "\"\nsub_old(\"Hello, World!\", -5, 50) = \"",
+               sub_old("Hello, World!", -5, 50),
+        "\"\nsub_old(\"Hello, World!\",  7,  0) = \"",
+               sub_old("Hello, World!",  7,  0),
+        "\"\nsub_old(\"Hello, World!\",  7, 12) = \"",
+               sub_old("Hello, World!",  7, 12),
+        "\"\nsub_old(\"Hello, World!\", 50, 10) = \"",
+               sub_old("Hello, World!", 50, 10),
+        "\"\n"
     ], !IO).
 
 :- func sub(string, int, int) = string.
 
 sub(S, I, N) =
+    from_char_list(foldr_between(func(X, Xs) = [X | Xs], S, I, N, [])).
+
+:- func sub_old(string, int, int) = string.
+
+sub_old(S, I, N) =
     from_char_list(foldr_substring(func(X, Xs) = [X | Xs], S, I, N, [])).
 
 %-----------------------------------------------------------------------------%
diff --git a/tests/hard_coded/Mercury.options b/tests/hard_coded/Mercury.options
index 08f34dc..cc52d39 100644
--- a/tests/hard_coded/Mercury.options
+++ b/tests/hard_coded/Mercury.options
@@ -50,6 +50,7 @@ MCFLAGS-opt_format          =	--optimize-format-calls
 MCFLAGS-reuse_ho            =	--ctgc --no-optimise-higher-order
 MCFLAGS-sharing_comb	    =	--ctgc --structure-sharing-widening 2
 MCFLAGS-simplify_multi_arm_switch = -O3
+MCFLAGS-string_substring    =	--no-warn-obsolete
 MCFLAGS-uncond_reuse	    =	--ctgc
 MCFLAGS-uncond_reuse_bad    =	--ctgc
 MCFLAGS-uo_regression1      =	--from-ground-term-threshold=4
diff --git a/tests/hard_coded/string_codepoint.exp b/tests/hard_coded/string_codepoint.exp
index 42b96d5..82c494b 100644
--- a/tests/hard_coded/string_codepoint.exp
+++ b/tests/hard_coded/string_codepoint.exp
@@ -86,5 +86,5 @@ aƟĪ¾
 right_by_codepoint:
 啕š€€.
 
-substring_by_codepoint:
+between_codepoints:
 Ī¾å••
diff --git a/tests/hard_coded/string_codepoint.exp2 b/tests/hard_coded/string_codepoint.exp2
index 4c30884..e2ef44e 100644
--- a/tests/hard_coded/string_codepoint.exp2
+++ b/tests/hard_coded/string_codepoint.exp2
@@ -86,5 +86,5 @@ aƟĪ¾
 right_by_codepoint:
 啕š€€.
 
-substring_by_codepoint:
+between_codepoints:
 Ī¾å••
diff --git a/tests/hard_coded/string_codepoint.m b/tests/hard_coded/string_codepoint.m
index c9771fe..a9d3bce 100644
--- a/tests/hard_coded/string_codepoint.m
+++ b/tests/hard_coded/string_codepoint.m
@@ -99,8 +99,8 @@ main(!IO) :-
     io.write_string(R3, !IO),
     io.nl(!IO),
 
-    io.write_string("\nsubstring_by_codepoint:\n", !IO),
-    string.substring_by_codepoint(Str, 2, 2, Sub),
+    io.write_string("\nbetween_codepoints:\n", !IO),
+    string.between_codepoints(Str, 2, 4, Sub),
     io.write_string(Sub, !IO),
     io.nl(!IO).
 
diff --git a/tests/hard_coded/string_substring.m b/tests/hard_coded/string_substring.m
index 07e7bfe..93cf945 100644
--- a/tests/hard_coded/string_substring.m
+++ b/tests/hard_coded/string_substring.m
@@ -33,7 +33,24 @@ main(!IO) :-
         string.substring("cat", 1, 0, ""),
         string.substring("cat", 1, 1, "a"),
         string.substring("cat", 1, 2, "at"),
-        string.substring("cat", 1, 3, "at")
+        string.substring("cat", 1, 3, "at"),
+
+        string.between("cat", -1, max_int, "cat"),
+        string.between("cat", 0, max_int, "cat"),
+        string.between("cat", 1, max_int, "at"),
+        string.between("cat", 2, max_int, "t"),
+        string.between("cat", 3, max_int, ""),
+        string.between("cat", 4, max_int, ""),
+        string.between("cat", 0, 0, ""),
+        string.between("cat", 0, 1, "c"),
+        string.between("cat", 0, 2, "ca"),
+        string.between("cat", 0, 3, "cat"),
+        string.between("cat", 1, -1, ""),
+        string.between("cat", 1, 0, ""),
+        string.between("cat", 1, 1, ""),
+        string.between("cat", 1, 2, "a"),
+        string.between("cat", 1, 3, "at"),
+        string.between("cat", 1, 4, "at")
     ->
         io.write_string("test succeeded\n", !IO)
     ;
--------------------------------------------------------------------------
mercury-reviews mailing list
Post messages to:       mercury-reviews at csse.unimelb.edu.au
Administrative Queries: owner-mercury-reviews at csse.unimelb.edu.au
Subscriptions:          mercury-reviews-request at csse.unimelb.edu.au
--------------------------------------------------------------------------



More information about the reviews mailing list