[m-rev.] diff: java versions of string functions

Peter Wang novalazy at gmail.com
Mon Jun 29 16:22:16 AEST 2009


Branches: main

library/string.m:
        Add Java versions of string functions.

        Fix integer overflow in slow version of substring/4, as can happen when
        someone passes max_int for Count.

tests/hard_coded/Mmakefile:
tests/hard_coded/contains_char_2.exp:
tests/hard_coded/contains_char_2.m:
tests/hard_coded/string_append_iii.exp:
tests/hard_coded/string_append_iii.m:
tests/hard_coded/string_append_ioi.exp:
tests/hard_coded/string_append_ioi.m:
tests/hard_coded/string_append_ooi.exp:
tests/hard_coded/string_append_ooi.m:
tests/hard_coded/string_set_char.exp:
tests/hard_coded/string_set_char.m:
tests/hard_coded/string_split_2.exp:
tests/hard_coded/string_split_2.m:
tests/hard_coded/string_sub_string_search.exp:
tests/hard_coded/string_sub_string_search.m:
tests/hard_coded/string_substring.exp:
tests/hard_coded/string_substring.m:
        Add some test cases.

diff --git a/library/string.m b/library/string.m
index e996100..f23329e 100644
--- a/library/string.m
+++ b/library/string.m
@@ -1334,6 +1334,19 @@ string.to_char_list(Str::uo, CharList::in) :-
     }
 }").

+:- pragma foreign_proc("Java",
+    string.to_char_list_2(Str::in, CharList::out),
+    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
+        does_not_affect_liveness, no_sharing],
+"
+    list.List_1 lst = new list.List_1.F_nil_0();
+    for (int i = Str.length() - 1; i >= 0; i--) {
+        char c = Str.charAt(i);
+        lst = new list.List_1.F_cons_2(c, lst);
+    }
+    CharList = lst;
+").
+
 :- pragma foreign_proc("Erlang",
     string.to_char_list_2(Str::in, CharList::out),
     [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
@@ -1731,6 +1744,28 @@ string.append_list(Strs::in) = (Str::uo) :-
     Str[len] = '\\0';
 }").

+:- pragma foreign_proc("Java",
+    string.join_list(Sep::in, Strs::in) = (Str::uo),
+    [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
+        does_not_affect_liveness],
+"
+    java.lang.StringBuilder sb = new java.lang.StringBuilder();
+    boolean add_sep = false;
+
+    while (Strs instanceof list.List_1.F_cons_2) {
+        list.List_1.F_cons_2 cons = (list.List_1.F_cons_2) Strs;
+        java.lang.String s = (java.lang.String) cons.F1;
+        if (add_sep) {
+            sb.append(Sep);
+        }
+        sb.append(s);
+        add_sep = true;
+        Strs = cons.F2;
+    }
+
+    Str = sb.toString();
+").
+
 string.join_list(_, []) = "".
 string.join_list(Sep, [H | T]) = H ++ string.join_list_2(Sep, T).

@@ -1796,6 +1831,15 @@ string.sub_string_search(WholeString, Pattern, Index) :-
     SUCCESS_INDICATOR = (Index >= 0);
 }").

+:- pragma foreign_proc("Java",
+    sub_string_search_start(WholeString::in, Pattern::in, BeginAt::in,
+        Index::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    Index = WholeString.indexOf(Pattern, BeginAt);
+    succeeded = (Index >= 0);
+").
+
 :- pragma foreign_proc("Erlang",
     sub_string_search_start(String::in, SubString::in, BeginAt::in,
Index::out),
     [will_not_call_mercury, promise_pure, thread_safe],
@@ -3539,12 +3583,21 @@ count_extra_trailing_zeroes(FloatStr, I, N0) = N :-
 "
     SUCCESS_INDICATOR = (strchr(Str, Ch) != NULL) && Ch != '\\0';
 ").
+
 :- pragma foreign_proc("C#",
     string.contains_char(Str::in, Ch::in),
     [will_not_call_mercury, promise_pure, thread_safe],
 "
     SUCCESS_INDICATOR = (Str.IndexOf(Ch) != -1);
 ").
+
+:- pragma foreign_proc("Java",
+    string.contains_char(Str::in, Ch::in),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    succeeded = (Str.indexOf(Ch) != -1);
+").
+
 string.contains_char(String, Char) :-
     string.contains_char(String, Char, 0, string.length(String)).

@@ -3716,6 +3769,20 @@ string.set_char(Char, Index, !Str) :-
         SUCCESS_INDICATOR = true;
     }
 ").
+:- pragma foreign_proc("Java",
+    string.set_char_2(Ch::in, Index::in, Str0::in, Str::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    if (Index < 0 || Index >= Str0.length()) {
+        Str = null;
+        succeeded = false;
+    } else {
+        java.lang.StringBuilder sb = new StringBuilder(Str0);
+        sb.setCharAt(Index, Ch);
+        Str = sb.toString();
+        succeeded = true;
+    }
+").
 string.set_char_2(Ch, Index, Str0, Str) :-
     string.to_char_list(Str0, List0),
     list.replace_nth(List0, Index + 1, Ch, List),
@@ -3921,6 +3988,13 @@ string.append(S1::out, S2::out, S3::in) :-
     SUCCESS_INDICATOR = S3.Equals(System.String.Concat(S1, S2));
 }").

+:- pragma foreign_proc("Java",
+    string.append_iii(S1::in, S2::in, S3::in),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    succeeded = S3.equals(S1.concat(S2));
+").
+
 :- pragma foreign_proc("Erlang",
     string.append_iii(S1::in, S2::in, S3::in),
     [will_not_call_mercury, promise_pure, thread_safe],
@@ -3975,6 +4049,19 @@ string.append_iii(X, Y, Z) :-
     }
 }").

+:- pragma foreign_proc("Java",
+    string.append_ioi(S1::in, S2::uo, S3::in),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    if (S3.startsWith(S1)) {
+        S2 = S3.substring(S1.length());
+        succeeded = true;
+    } else {
+        S2 = null;
+        succeeded = false;
+    }
+").
+
 string.append_ioi(X, Y, Z) :-
     string.mercury_append(X, Y, Z).

@@ -4000,6 +4087,13 @@ string.append_ioi(X, Y, Z) :-
     S3 = System.String.Concat(S1, S2);
 }").

+:- pragma foreign_proc("Java",
+    string.append_iio(S1::in, S2::in, S3::uo),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    S3 = S1.concat(S2);
+").
+
 :- pragma foreign_proc("Erlang",
     string.append_iio(S1::in, S2::in, S3::uo),
     [will_not_call_mercury, promise_pure, thread_safe],
@@ -4053,6 +4147,14 @@ string.append_ooi_2(NextS1Len, S3Len, S1, S2, S3) :-
     S2 = S3.Substring(S1Len);
 ").

+:- pragma foreign_proc("Java",
+    string.append_ooi_3(S1Len::in, _S3Len::in, S1::out, S2::out, S3::in),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    S1 = S3.substring(0, S1Len);
+    S2 = S3.substring(S1Len);
+").
+
 :- pragma foreign_proc("Erlang",
     string.append_ooi_3(S1Len::in, _S3Len::in, S1::out, S2::out, S3::in),
     [will_not_call_mercury, promise_pure, thread_safe],
@@ -4077,21 +4179,28 @@ string.mercury_append(X, Y, Z) :-

 /*-----------------------------------------------------------------------*/

-string.substring(Str::in, Start::in, Count::in, SubStr::uo) :-
-    End = min(Start + Count, string.length(Str)),
-    SubStr = string.from_char_list(strchars(Start, End, Str)).
+substring(Str, !.Start, !.Count, SubStr) :-
+    ( !.Count =< 0 ->
+        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),
+        SubStr = string.from_char_list(CharList)
+    ).

 :- func strchars(int, int, string) = list(char).

-strchars(I, End, Str) =
-    (
-        ( I < 0
-        ; End =< I
-        )
-    ->
-        []
+strchars(I, End, Str) = Chars :-
+    ( I >= End ->
+        Chars = []
     ;
-        [string.index_det(Str, I) | strchars(I + 1, End, Str)]
+        C = string.unsafe_index(Str, I),
+        Cs = strchars(I + 1, End, Str),
+        Chars = [C | Cs]
     ).

 :- pragma foreign_proc("C",
@@ -4119,6 +4228,26 @@ strchars(I, End, Str) =
     }
 }").

+:- pragma foreign_proc("Java",
+    string.substring(Str::in, Start::in, Count::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) {
+        SubString = """";
+    } else {
+        int len = Str.length();
+        if (Start > len) {
+            Start = len;
+        }
+        if (Count > len - Start) {
+            Count = len - Start;
+        }
+        SubString = Str.substring(Start, Start + Count);
+    }
+").
+
 :- pragma foreign_proc("Erlang",
     string.substring(Str::in, Start0::in, Count::in, SubString::uo),
     [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail,
@@ -4222,6 +4351,23 @@ strchars(I, End, Str) =
     }
 }").

+:- pragma foreign_proc("Java",
+    string.split(Str::in, Count::in, Left::uo, Right::uo),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    if (Count <= 0) {
+        Left = """";
+        Right = Str;
+    } else {
+        int len = Str.length();
+        if (Count > len) {
+            Count = len;
+        }
+        Left = Str.substring(0, Count);
+        Right = Str.substring(Count);
+    }
+").
+
 :- pragma foreign_proc("Erlang",
     string.split(Str::in, Count::in, Left::uo, Right::uo),
     [will_not_call_mercury, promise_pure, thread_safe],
diff --git a/tests/hard_coded/Mmakefile b/tests/hard_coded/Mmakefile
index 6271558..837bee0 100644
--- a/tests/hard_coded/Mmakefile
+++ b/tests/hard_coded/Mmakefile
@@ -33,6 +33,7 @@ ORDINARY_PROGS=	\
 	construct_test \
 	construct_test_exist \
 	contains_char \
+	contains_char_2 \
 	contravariance_bug \
 	contravariance_poly \
 	curry \
@@ -215,11 +216,18 @@ ORDINARY_PROGS=	\
 	stream_test \
 	string_alignment \
 	string_alignment_bug \
+	string_append_iii \
+	string_append_ioi \
+	string_append_ooi \
 	string_builder_test \
 	string_loop \
+	string_set_char \
 	string_split \
+	string_split_2 \
 	string_string \
 	string_strip \
+	string_substring \
+	string_sub_string_search \
 	string_suffix_bug \
 	string_various \
 	sv_nested_closures \
@@ -286,6 +294,7 @@ JAVA_PASS_PROGS= \
 	compare_spec \
 	constant_prop_2 \
 	contains_char \
+	contains_char_2 \
 	constraint \
 	curry \
 	cut_test \
diff --git a/tests/hard_coded/contains_char_2.exp
b/tests/hard_coded/contains_char_2.exp
new file mode 100644
index 0000000..4745bd8
--- /dev/null
+++ b/tests/hard_coded/contains_char_2.exp
@@ -0,0 +1 @@
+test succeeded
diff --git a/tests/hard_coded/contains_char_2.m
b/tests/hard_coded/contains_char_2.m
new file mode 100644
index 0000000..b250cdc
--- /dev/null
+++ b/tests/hard_coded/contains_char_2.m
@@ -0,0 +1,32 @@
+%-----------------------------------------------------------------------------%
+
+:- module contains_char_2.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is det.
+
+%-----------------------------------------------------------------------------%
+%-----------------------------------------------------------------------------%
+
+:- implementation.
+
+:- import_module string.
+
+%-----------------------------------------------------------------------------%
+
+main(!IO) :-
+    (
+        string__contains_char("cat", 'c'),
+        string__contains_char("cat", 'a'),
+        string__contains_char("cat", 't'),
+        not string__contains_char("cat", 'm')
+    ->
+        io.write_string("test succeeded\n", !IO)
+    ;
+        io.write_string("test failed\n", !IO)
+    ).
+
+%-----------------------------------------------------------------------------%
+% vim: ft=mercury ts=8 sts=4 sw=4 et
diff --git a/tests/hard_coded/string_append_iii.exp
b/tests/hard_coded/string_append_iii.exp
new file mode 100644
index 0000000..4745bd8
--- /dev/null
+++ b/tests/hard_coded/string_append_iii.exp
@@ -0,0 +1 @@
+test succeeded
diff --git a/tests/hard_coded/string_append_iii.m
b/tests/hard_coded/string_append_iii.m
new file mode 100644
index 0000000..e38c69d
--- /dev/null
+++ b/tests/hard_coded/string_append_iii.m
@@ -0,0 +1,32 @@
+%-----------------------------------------------------------------------------%
+
+:- module string_append_iii.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is det.
+
+%-----------------------------------------------------------------------------%
+%-----------------------------------------------------------------------------%
+
+:- implementation.
+
+:- import_module string.
+
+%-----------------------------------------------------------------------------%
+
+main(!IO) :-
+    (
+        string.append("", "cat", "cat"),
+        string.append("c", "at", "cat"),
+        string.append("ca", "t", "cat"),
+        string.append("cat", "", "cat")
+    ->
+        io.write_string("test succeeded\n", !IO)
+    ;
+        io.write_string("test failed\n", !IO)
+    ).
+
+%-----------------------------------------------------------------------------%
+% vim: ft=mercury ts=8 sts=4 sw=4 et
diff --git a/tests/hard_coded/string_append_ioi.exp
b/tests/hard_coded/string_append_ioi.exp
new file mode 100644
index 0000000..13c6a01
--- /dev/null
+++ b/tests/hard_coded/string_append_ioi.exp
@@ -0,0 +1 @@
+["cat", "at", "t", ""]
diff --git a/tests/hard_coded/string_append_ioi.m
b/tests/hard_coded/string_append_ioi.m
new file mode 100644
index 0000000..1109af4
--- /dev/null
+++ b/tests/hard_coded/string_append_ioi.m
@@ -0,0 +1,35 @@
+%-----------------------------------------------------------------------------%
+
+:- module string_append_ioi.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is det.
+
+%-----------------------------------------------------------------------------%
+%-----------------------------------------------------------------------------%
+
+:- implementation.
+
+:- import_module list.
+:- import_module string.
+
+%-----------------------------------------------------------------------------%
+
+main(!IO) :-
+    (
+        string.append("", A, "cat"),
+        string.append("c", B, "cat"),
+        string.append("ca", C, "cat"),
+        string.append("cat", D, "cat"),
+        not string.append("cat", _, "dogcat")
+    ->
+        io.write([A, B, C, D], !IO),
+        io.nl(!IO)
+    ;
+        io.write_string("tested failed\n", !IO)
+    ).
+
+%-----------------------------------------------------------------------------%
+% vim: ft=mercury ts=8 sts=4 sw=4 et
diff --git a/tests/hard_coded/string_append_ooi.exp
b/tests/hard_coded/string_append_ooi.exp
new file mode 100644
index 0000000..54d4d91
--- /dev/null
+++ b/tests/hard_coded/string_append_ooi.exp
@@ -0,0 +1 @@
+["" - "cat", "c" - "at", "ca" - "t", "cat" - ""]
diff --git a/tests/hard_coded/string_append_ooi.m
b/tests/hard_coded/string_append_ooi.m
new file mode 100644
index 0000000..98a3b44
--- /dev/null
+++ b/tests/hard_coded/string_append_ooi.m
@@ -0,0 +1,32 @@
+%-----------------------------------------------------------------------------%
+
+:- module string_append_ooi.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is cc_multi.
+
+%-----------------------------------------------------------------------------%
+%-----------------------------------------------------------------------------%
+
+:- implementation.
+
+:- import_module list.
+:- import_module pair.
+:- import_module solutions.
+:- import_module string.
+
+%-----------------------------------------------------------------------------%
+
+main(!IO) :-
+    unsorted_solutions(
+        (pred(L - R::out) is multi :-
+            string.append(L, R, "cat")
+        ), UnsortedSolutions),
+    list.sort(UnsortedSolutions, Solutions),
+    io.write(Solutions, !IO),
+    io.nl(!IO).
+
+%-----------------------------------------------------------------------------%
+% vim: ft=mercury ts=8 sts=4 sw=4 et
diff --git a/tests/hard_coded/string_set_char.exp
b/tests/hard_coded/string_set_char.exp
new file mode 100644
index 0000000..4745bd8
--- /dev/null
+++ b/tests/hard_coded/string_set_char.exp
@@ -0,0 +1 @@
+test succeeded
diff --git a/tests/hard_coded/string_set_char.m
b/tests/hard_coded/string_set_char.m
new file mode 100644
index 0000000..d2f27b9
--- /dev/null
+++ b/tests/hard_coded/string_set_char.m
@@ -0,0 +1,61 @@
+%-----------------------------------------------------------------------------%
+
+:- module string_set_char.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is det.
+
+%-----------------------------------------------------------------------------%
+%-----------------------------------------------------------------------------%
+
+:- implementation.
+
+:- import_module require.
+:- import_module string.
+
+%-----------------------------------------------------------------------------%
+
+main(!IO) :-
+    ( string.set_char('x', -1, "", _) ->
+        error("test failed")
+    ;
+        true
+    ),
+    ( string.set_char('x', 0, "", _) ->
+        error("test failed")
+    ;
+        true
+    ),
+    (  string.set_char('x', 1, "", _) ->
+        error("test failed")
+    ;
+        true
+    ),
+
+    ( string.set_char('m', 0, "cat", "mat") ->
+        true
+    ;
+        error("test failed")
+    ),
+    ( string.set_char('u', 1, "cat", "cut") ->
+        true
+    ;
+        error("test failed")
+    ),
+    ( string.set_char('b', 2, "cat", "cab") ->
+        true
+    ;
+        error("test failed")
+    ),
+    ( string.set_char('x', 3, "cat", _) ->
+        error("test failed")
+    ;
+        true
+    ),
+
+    io.write_string("test succeeded\n", !IO).
+
+%-----------------------------------------------------------------------------%
+% vim: ft=mercury ts=8 sts=4 sw=4 et
diff --git a/tests/hard_coded/string_split_2.exp
b/tests/hard_coded/string_split_2.exp
new file mode 100644
index 0000000..4745bd8
--- /dev/null
+++ b/tests/hard_coded/string_split_2.exp
@@ -0,0 +1 @@
+test succeeded
diff --git a/tests/hard_coded/string_split_2.m
b/tests/hard_coded/string_split_2.m
new file mode 100644
index 0000000..0aae852
--- /dev/null
+++ b/tests/hard_coded/string_split_2.m
@@ -0,0 +1,34 @@
+%-----------------------------------------------------------------------------%
+
+:- module string_split_2.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is det.
+
+%-----------------------------------------------------------------------------%
+%-----------------------------------------------------------------------------%
+
+:- implementation.
+
+:- import_module string.
+
+%-----------------------------------------------------------------------------%
+
+main(!IO) :-
+    (
+        string.split("cat", -1, "", "cat"),
+        string.split("cat", 0, "", "cat"),
+        string.split("cat", 1, "c", "at"),
+        string.split("cat", 2, "ca", "t"),
+        string.split("cat", 3, "cat", ""),
+        string.split("cat", 4, "cat", "")
+    ->
+        io.write_string("test succeeded\n", !IO)
+    ;
+        io.write_string("test failed\n", !IO)
+    ).
+
+%-----------------------------------------------------------------------------%
+% vim: ft=mercury ts=8 sts=4 sw=4 et
diff --git a/tests/hard_coded/string_sub_string_search.exp
b/tests/hard_coded/string_sub_string_search.exp
new file mode 100644
index 0000000..4745bd8
--- /dev/null
+++ b/tests/hard_coded/string_sub_string_search.exp
@@ -0,0 +1 @@
+test succeeded
diff --git a/tests/hard_coded/string_sub_string_search.m
b/tests/hard_coded/string_sub_string_search.m
new file mode 100644
index 0000000..2124bc5
--- /dev/null
+++ b/tests/hard_coded/string_sub_string_search.m
@@ -0,0 +1,39 @@
+%-----------------------------------------------------------------------------%
+
+:- module string_sub_string_search.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is det.
+
+%-----------------------------------------------------------------------------%
+%-----------------------------------------------------------------------------%
+
+:- implementation.
+
+:- import_module string.
+
+%-----------------------------------------------------------------------------%
+
+main(!IO) :-
+    (
+        string.sub_string_search("", "", 0),
+        not string.sub_string_search("", "dog", _),
+        not string.sub_string_search("cat", "catdog", _),
+
+        string.sub_string_search("cat", "", 0),
+        string.sub_string_search("cat", "cat", 0),
+        string.sub_string_search("cat", "at", 1),
+        string.sub_string_search("cat", "t", 2),
+
+        string.sub_string_search_start("catcatcat", "cat", 1, 3),
+        not string.sub_string_search_start("catcatcat", "cat", 9, _)
+    ->
+        io.write_string("test succeeded\n", !IO)
+    ;
+        io.write_string("test failed\n", !IO)
+    ).
+
+%-----------------------------------------------------------------------------%
+% vim: ft=mercury ts=8 sts=4 sw=4 et
diff --git a/tests/hard_coded/string_substring.exp
b/tests/hard_coded/string_substring.exp
new file mode 100644
index 0000000..4745bd8
--- /dev/null
+++ b/tests/hard_coded/string_substring.exp
@@ -0,0 +1 @@
+test succeeded
diff --git a/tests/hard_coded/string_substring.m
b/tests/hard_coded/string_substring.m
new file mode 100644
index 0000000..07e7bfe
--- /dev/null
+++ b/tests/hard_coded/string_substring.m
@@ -0,0 +1,44 @@
+%-----------------------------------------------------------------------------%
+
+:- module string_substring.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is det.
+
+%-----------------------------------------------------------------------------%
+%-----------------------------------------------------------------------------%
+
+:- implementation.
+
+:- import_module int.
+:- import_module string.
+
+%-----------------------------------------------------------------------------%
+
+main(!IO) :-
+    (
+        string.substring("cat", -1, max_int, "cat"),
+        string.substring("cat", 0, max_int, "cat"),
+        string.substring("cat", 1, max_int, "at"),
+        string.substring("cat", 2, max_int, "t"),
+        string.substring("cat", 3, max_int, ""),
+        string.substring("cat", 4, max_int, ""),
+        string.substring("cat", 0, 0, ""),
+        string.substring("cat", 0, 1, "c"),
+        string.substring("cat", 0, 2, "ca"),
+        string.substring("cat", 0, 3, "cat"),
+        string.substring("cat", 1, -1, ""),
+        string.substring("cat", 1, 0, ""),
+        string.substring("cat", 1, 1, "a"),
+        string.substring("cat", 1, 2, "at"),
+        string.substring("cat", 1, 3, "at")
+    ->
+        io.write_string("test succeeded\n", !IO)
+    ;
+        io.write_string("test failed\n", !IO)
+    ).
+
+%-----------------------------------------------------------------------------%
+% vim: ft=mercury ts=8 sts=4 sw=4 et
--------------------------------------------------------------------------
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