[m-rev.] for review: allow lists in terms in breakpoint conditions

Zoltan Somogyi zs at csse.unimelb.edu.au
Tue Jun 12 16:14:18 AEST 2007


For review by anyone. I am also attaching the entirety of mercury_trace_term.c,
since it is easier to read than the diff.

Zoltan.

Provide support for list syntax and quoted function symbols in the terms
that appear in breakpoint conditions.

runtime/mercury_trace_term.[ch]:
	Provide the new capability when creating terms from strings.

	Since with nonempty lists, the function symbol of the created term
	("[|]") does not appear in the original string, do not try to reuse
	the memory of the string for the memory of the function symbols;
	instead, make a copy of each function symbol as needed. This also
	makes the code simpler by avoiding the need to mangle the original
	string to null-terminate these function symbols.

	Add a mechanism for reporting the location and nature of syntax errors.

	Don't assume that the initial string has no spaces.

trace/mercury_trace_spy.[ch]:
	Don't record the strings from which the terms in breakpoint conditions
	came from, since function symbols' memory no longer comes from there,
	and thus we don't have to free them when the breakpoint is deleted.

trace/mercury_trace_cmd_breakpoint.c:
	Use the new mechanism for reporting the details of syntax errors in
	terms.

	Include spaces in the string from which terms are constructed
	at the points at which the user included spaces, since the absence
	of such spaces in any reports of syntax errors would be surprising.

	Delete the temporary memory for strings containing terms as soon
	as they has been used to construct their terms.

trace/mercury_trace_internal.c:
	Document a problem.

trace/Mmakefile:
	Rebuild this directory automatically if a few more headers in the
	runtime change.

doc/user_guide.texi:
	Document the new capability.

/*
** vim: ts=4 sw=4 expandtab
*/
/*
** Copyright (C) 2005-2006 The University of Melbourne.
** This file may only be copied under the terms of the GNU Library General
** Public License - see the file COPYING.LIB in the Mercury distribution.
*/

/*
** This file contains code to manage terms, for conditional breakpoints.
**
** Author: Zoltan Somogyi.
*/

#include "mercury_imp.h"
#include "mercury_trace_term.h"
#include <stdlib.h>

static  MR_CTerm    MR_parse_cterm(char *str, char endchar, char **rest,
                        MR_bool *mismatch, char **error);
static  MR_CTerm    MR_parse_clist_tail(char *str, char **rest,
                        char *left_bracket, MR_bool *mismatch, char **error);
static  MR_CArgs    MR_parse_cargs(char *str, char endchar, char **rest,
                        char *left_paren, MR_bool *mismatch, char **error);
static  void        MR_delete_cargs(MR_CArgs args);

static  void        MR_print_clist_tail(FILE *fp, MR_CTerm term);

static  MR_bool     MR_cterm_is_nil(MR_CTerm term);
static  MR_bool     MR_cterm_is_cons(MR_CTerm term,
                        MR_CTerm *head_ptr, MR_CTerm *tail_ptr);

static  MR_CTerm    MR_make_nil_cterm(void);
static  MR_CTerm    MR_make_cons_cterm(MR_CTerm head_term, MR_CTerm tail_term);

static  char        *MR_copy_str_fragment(const char *original,
                        int string_length);

/**************************************************************************/

MR_CTerm
MR_create_cterm(char *str, char **rest, MR_bool *mismatch, char **error)
{
    *mismatch = MR_FALSE;
    *error = NULL;
    return MR_parse_cterm(str, '\0', rest, mismatch, error);
}

MR_CTerm
MR_parse_cterm(char *str, char endchar, char **rest,
    MR_bool *mismatch, char **error)
{
    MR_CTerm    term;
    MR_CArgs    args;
    char        *functor;
    char        *more;
    int         i;

    while (str[0] != '\0' && MR_isspace(str[0])) {
        str++;
    }

    if (str[0] == '[') {
        MR_CTerm    head_term;
        MR_CTerm    tail_term;

        if (str[1] == ']') {
            *rest = &str[2];
            return MR_make_nil_cterm();
        }

        /*
        ** The EBNF grammar for nonempty lists that our code follows is:
        **
        ** list ::= "[" term {"," term}* {"|" term}? "]"
        **
        ** The initial "[" and the first term are read here; all the rest
        ** are read by MR_parse_clist_tail.
        */

        head_term = MR_parse_cterm(&str[1], ']', &more, mismatch, error);
        if (head_term == NULL) {
            /* MR_parse_cterm has already set *mismatch and *error. */
            return NULL;
        }

        tail_term = MR_parse_clist_tail(&more[0], rest, &str[0],
            mismatch, error);
        if (tail_term == NULL) {
            /* MR_parse_clist_tail has already set *mismatch and *error. */
            MR_delete_cterm(head_term);
            return NULL;
        }

        return MR_make_cons_cterm(head_term, tail_term);
    }

    if (str[0] == '"') {
        i = 1;
        while (str[i] != '\0' && str[i] != '"') {
            i++;
        }

        if (str[i] == '"') {
            term = MR_malloc(sizeof(struct MR_CTerm_Struct));
            /* Include the double quotes in the function symbol. */
            term->MR_term_functor = MR_copy_str_fragment(&str[0], i + 1);
            term->MR_term_args = NULL;
            *rest = &str[i + 1];
            return term;
        }

        *mismatch = MR_TRUE;
        *error = &str[0];
        return NULL;
    }

    if (str[0] == '\'') {
        i = 1;
        while (str[i] != '\0' && str[i] != '\'') {
            i++;
        }

        if (str[i] != '\'') {
            *mismatch = MR_TRUE;
            *error = &str[0];
            return NULL;
        }

        /* Don't include the single quotes in the function symbol. */
        functor = MR_copy_str_fragment(&str[1], i - 1);
        more = &str[i + 1];
    } else if (MR_isalnumunder(str[0]) && !MR_isupper(str[0])) {
        i = 0;
        while (MR_isalnumunder(str[i])) {
            i++;
        }

        functor = MR_copy_str_fragment(&str[0], i);
        more = &str[i];
    } else {
        *mismatch = MR_FALSE;
        *error = &str[0];
        return NULL;
    }

    i = 0;
    while (more[i] != '\0' && MR_isspace(more[i])) {
        i++;
    }

    if ((more[i] == '\0') || (more[i] == ',') || (more[i] == endchar)) {
        term = MR_malloc(sizeof(struct MR_CTerm_Struct));
        term->MR_term_functor = functor;
        term->MR_term_args = NULL;
        *rest = &more[i];
        return term;
    } else if (more[i] == '(') {
        args = MR_parse_cargs(&more[i + 1], ')', rest, &more[i],
            mismatch, error);
        if (args == NULL) {
            /* MR_parse_cargs has already set *mismatch and *error. */
            MR_free(functor);
            return NULL;
        }

        term = MR_malloc(sizeof(struct MR_CTerm_Struct));
        term->MR_term_functor = functor;
        term->MR_term_args = args;
        return term;
    }

    *mismatch = MR_FALSE;
    *error = &more[i];
    return NULL;
}

static MR_CTerm
MR_parse_clist_tail(char *str, char **rest, char *left_bracket,
    MR_bool *mismatch, char **error)
{
    char        *more;
    int         i;

    while (str[0] != '\0' && MR_isspace(str[0])) {
        str++;
    }

    if (str[0] == ']') {
        *rest = &str[1];
        return MR_make_nil_cterm();
    } else if (str[0] == '|') {
        MR_CTerm    term;

        i = 1;
        while (str[i] != '\0' && MR_isspace(str[i])) {
            i++;
        }

        term = MR_parse_cterm(&str[i], ']', &more, mismatch, error);
        if (term == NULL) {
            /* MR_parse_cterm has already set *mismatch and *error. */
            return NULL;
        }

        i = 0;
        while (more[i] != '\0' && MR_isspace(more[i])) {
            i++;
        }

        if (more[i] != ']') {
            *mismatch = MR_TRUE;
            *error = left_bracket;
            return NULL;
        }

        *rest = &more[i+1];
        return term;
    } else if (str[0] == ',') {
        MR_CTerm    head_term;
        MR_CTerm    tail_term;

        i = 1;
        while (str[i] != '\0' && MR_isspace(str[i])) {
            i++;
        }

        head_term = MR_parse_cterm(&str[i], ']', &more, mismatch, error);
        if (head_term == NULL) {
            /* MR_parse_cterm has already set *mismatch and *error. */
            return NULL;
        }

        i = 0;
        while (more[i] != '\0' && MR_isspace(more[i])) {
            i++;
        }

        if (more[i] == '\0') {
            *mismatch = MR_TRUE;
            *error = left_bracket;
            MR_delete_cterm(head_term);
            return NULL;
        }

        tail_term = MR_parse_clist_tail(&more[i], rest, left_bracket,
            mismatch, error);
        if (tail_term == NULL) {
            /* MR_parse_clist_tail has already set *mismatch and *error. */
            MR_delete_cterm(head_term);
            return NULL;
        }

        return MR_make_cons_cterm(head_term, tail_term);
    }

    *mismatch = MR_TRUE;
    *error = left_bracket;
    return NULL;
}

static MR_CArgs
MR_parse_cargs(char *str, char endchar, char **rest,
    char *left_paren, MR_bool *mismatch, char **error)
{
    char            *more;
    MR_CTerm        term;
    MR_CArgs        args;
    MR_CArgs        tail;
    int             i;

    term = MR_parse_cterm(str, endchar, &more, mismatch, error);
    if (term == NULL) {
        /* MR_parse_cterm has already set *mismatch and *error. */
        return NULL;
    }

    while (more[0] != '\0' && MR_isspace(more[0])) {
        more++;
    }

    if (more[0] == endchar) {
        args = MR_malloc(sizeof(struct MR_CArgs_Struct));
        args->MR_args_head = term;
        args->MR_args_tail = NULL;
        if (endchar == '\0') {
            *rest = &more[0];
        } else {
            *rest = &more[1];
        }
        return args;
    } else if (more[0] == ',') {
        i = 1;
        while (more[i] != '\0' && MR_isspace(more[i])) {
            i++;
        }

        if (more[i] == '\0') {
            *mismatch = MR_TRUE;
            *error = left_paren;
            MR_delete_cterm(term);
            return NULL;
        }

        tail = MR_parse_cargs(more + i, endchar, rest, left_paren,
            mismatch, error);
        if (tail == NULL) {
            /* MR_parse_cargs has already set *mismatch and *error. */
            MR_delete_cterm(term);
            return NULL;
        }

        args = MR_malloc(sizeof(struct MR_CArgs_Struct));
        args->MR_args_head = term;
        args->MR_args_tail = tail;
        return args;
    }

    if (more[0] == '\0') {
        *mismatch = MR_TRUE;
        *error = left_paren;
    } else {
        *mismatch = MR_FALSE;
        *error = &more[0];
    }

    MR_delete_cterm(term);
    return NULL;
}

void
MR_print_cterm(FILE *fp, MR_CTerm term)
{
    MR_CTerm    head;
    MR_CTerm    tail;

    if (MR_cterm_is_nil(term)) {
        fprintf(fp, "[]");
    } else if (MR_cterm_is_cons(term, &head, &tail)) {
        fprintf(fp, "[");
        MR_print_cterm(fp, head);
        MR_print_clist_tail(fp, tail);
        fprintf(fp, "]");
    } else {
        MR_CArgs    args;
        int         i;

        fprintf(fp, "%s", term->MR_term_functor);
        if (term->MR_term_args != NULL) {
            fprintf(fp, "(");
            i = 0;
            args = term->MR_term_args;
            while (args != NULL) {
                if (i > 0) {
                    fprintf(fp, ", ");
                }
                MR_print_cterm(fp, args->MR_args_head);
                args = args->MR_args_tail;
                i++;
            }
            fprintf(fp, ")");
        }
    }
}

static void
MR_print_clist_tail(FILE *fp, MR_CTerm term)
{
    MR_CTerm    head;
    MR_CTerm    tail;

    if (MR_cterm_is_nil(term)) {
        return;
    } else if (MR_cterm_is_cons(term, &head, &tail)) {
        fprintf(fp, ", ");
        MR_print_cterm(fp, head);
        MR_print_clist_tail(fp, tail);
    } else {
        fprintf(fp, " | ");
        MR_print_cterm(fp, term);
    }
}

static MR_bool
MR_cterm_is_nil(MR_CTerm term)
{
    if (term == NULL) {
        return MR_FALSE;
    }

    if (MR_strdiff(term->MR_term_functor, "[]")) {
        return MR_FALSE;
    }

    if (term->MR_term_args != NULL) {
        return MR_FALSE;
    }

    return MR_TRUE;
}

static MR_bool
MR_cterm_is_cons(MR_CTerm term, MR_CTerm *head_ptr, MR_CTerm *tail_ptr)
{
    MR_CArgs    args1;
    MR_CArgs    args2;
    MR_CArgs    args3;

    if (term == NULL) {
        return MR_FALSE;
    }

    if (MR_strdiff(term->MR_term_functor, "[|]")) {
        return MR_FALSE;
    }

    args1 = term->MR_term_args;
    if (args1 == NULL) {
        return MR_FALSE;
    }

    args2 = args1->MR_args_tail;
    if (args2 == NULL) {
        return MR_FALSE;
    }

    args3 = args2->MR_args_tail;
    if (args3 != NULL) {
        return MR_FALSE;
    }

    *head_ptr = args1->MR_args_head;
    *tail_ptr = args2->MR_args_head;
    return MR_TRUE;
}

void
MR_delete_cterm(MR_CTerm term)
{
    if (term != NULL) {
        MR_free(term->MR_term_functor);
        MR_delete_cargs(term->MR_term_args);
        MR_free(term);
    }
}

static void
MR_delete_cargs(MR_CArgs args)
{
    if (args != NULL) {
        MR_delete_cargs(args->MR_args_tail);
        MR_delete_cterm(args->MR_args_head);
        MR_free(args);
    }
}

static MR_CTerm
MR_make_nil_cterm(void)
{
    MR_CTerm    term;

    term = MR_malloc(sizeof(struct MR_CTerm_Struct));
    term->MR_term_functor = MR_copy_str_fragment("[]", 2);
    term->MR_term_args = NULL;
    return term;
}

static MR_CTerm
MR_make_cons_cterm(MR_CTerm head_term, MR_CTerm tail_term)
{
    MR_CTerm    term;
    MR_CArgs    args1;
    MR_CArgs    args2;

    args2 = MR_malloc(sizeof(struct MR_CArgs_Struct));
    args2->MR_args_head = tail_term;
    args2->MR_args_tail = NULL;

    args1 = MR_malloc(sizeof(struct MR_CArgs_Struct));
    args1->MR_args_head = head_term;
    args1->MR_args_tail = args2;

    term = MR_malloc(sizeof(struct MR_CTerm_Struct));
    term->MR_term_functor = MR_copy_str_fragment("[|]", 3);
    term->MR_term_args = args1;

    return term;
}

/*
** Make an MR_malloc'd copy of a fragment of a string: string_length bytes
** starting at *original. Don't assume the original string is NULL-terminated.
*/

static char *
MR_copy_str_fragment(const char *original, int string_length)
{
    char    *copy;

    copy = MR_malloc(string_length + 1);
    strncpy(copy, original, string_length);
    copy[string_length] = '\0';
    return copy;
}

cvs diff: Diffing .
cvs diff: Diffing analysis
cvs diff: Diffing bindist
cvs diff: Diffing boehm_gc
cvs diff: Diffing boehm_gc/Mac_files
cvs diff: Diffing boehm_gc/cord
cvs diff: Diffing boehm_gc/cord/private
cvs diff: Diffing boehm_gc/doc
cvs diff: Diffing boehm_gc/include
cvs diff: Diffing boehm_gc/include/private
cvs diff: Diffing boehm_gc/libatomic_ops-1.2
cvs diff: Diffing boehm_gc/libatomic_ops-1.2/doc
cvs diff: Diffing boehm_gc/libatomic_ops-1.2/src
cvs diff: Diffing boehm_gc/libatomic_ops-1.2/src/atomic_ops
cvs diff: Diffing boehm_gc/libatomic_ops-1.2/src/atomic_ops/sysdeps
cvs diff: Diffing boehm_gc/libatomic_ops-1.2/src/atomic_ops/sysdeps/gcc
cvs diff: Diffing boehm_gc/libatomic_ops-1.2/src/atomic_ops/sysdeps/hpc
cvs diff: Diffing boehm_gc/libatomic_ops-1.2/src/atomic_ops/sysdeps/ibmc
cvs diff: Diffing boehm_gc/libatomic_ops-1.2/src/atomic_ops/sysdeps/icc
cvs diff: Diffing boehm_gc/libatomic_ops-1.2/src/atomic_ops/sysdeps/msftc
cvs diff: Diffing boehm_gc/libatomic_ops-1.2/src/atomic_ops/sysdeps/sunc
cvs diff: Diffing boehm_gc/libatomic_ops-1.2/tests
cvs diff: Diffing boehm_gc/tests
cvs diff: Diffing boehm_gc/windows-untested
cvs diff: Diffing boehm_gc/windows-untested/vc60
cvs diff: Diffing boehm_gc/windows-untested/vc70
cvs diff: Diffing boehm_gc/windows-untested/vc71
cvs diff: Diffing browser
cvs diff: Diffing bytecode
cvs diff: Diffing compiler
cvs diff: Diffing compiler/notes
cvs diff: Diffing debian
cvs diff: Diffing debian/patches
cvs diff: Diffing deep_profiler
cvs diff: Diffing deep_profiler/notes
cvs diff: Diffing doc
Index: doc/user_guide.texi
===================================================================
RCS file: /home/mercury/mercury1/repository/mercury/doc/user_guide.texi,v
retrieving revision 1.524
diff -u -b -r1.524 user_guide.texi
--- doc/user_guide.texi	12 Jun 2007 06:06:31 -0000	1.524
+++ doc/user_guide.texi	12 Jun 2007 06:06:43 -0000
@@ -3473,7 +3473,11 @@
 @var{term} may contain integers and strings
 (as long as the strings don't contain double quotes),
 but floats and characters aren't supported (yet),
-and neither is any special syntax for lists, operators, etc.
+and neither is any special syntax for operators.
+Operators can be specified in prefix form
+by quoting them with escaped single quotes,
+as in @samp{\'+\'(1, 2)}.
+Lists can be specified using the usual syntax.
 @var{term} also may not contain variables, with one exception:
 any occurrence of @samp{_} in @var{term} matches any term.
 @sp 1
cvs diff: Diffing extras
cvs diff: Diffing extras/base64
cvs diff: Diffing extras/cgi
cvs diff: Diffing extras/complex_numbers
cvs diff: Diffing extras/complex_numbers/samples
cvs diff: Diffing extras/complex_numbers/tests
cvs diff: Diffing extras/concurrency
cvs diff: Diffing extras/curs
cvs diff: Diffing extras/curs/samples
cvs diff: Diffing extras/curses
cvs diff: Diffing extras/curses/sample
cvs diff: Diffing extras/dynamic_linking
cvs diff: Diffing extras/error
cvs diff: Diffing extras/fixed
cvs diff: Diffing extras/gator
cvs diff: Diffing extras/gator/generations
cvs diff: Diffing extras/gator/generations/1
cvs diff: Diffing extras/graphics
cvs diff: Diffing extras/graphics/easyx
cvs diff: Diffing extras/graphics/easyx/samples
cvs diff: Diffing extras/graphics/mercury_allegro
cvs diff: Diffing extras/graphics/mercury_allegro/examples
cvs diff: Diffing extras/graphics/mercury_allegro/samples
cvs diff: Diffing extras/graphics/mercury_allegro/samples/demo
cvs diff: Diffing extras/graphics/mercury_allegro/samples/mandel
cvs diff: Diffing extras/graphics/mercury_allegro/samples/pendulum2
cvs diff: Diffing extras/graphics/mercury_allegro/samples/speed
cvs diff: Diffing extras/graphics/mercury_glut
cvs diff: Diffing extras/graphics/mercury_opengl
cvs diff: Diffing extras/graphics/mercury_tcltk
cvs diff: Diffing extras/graphics/samples
cvs diff: Diffing extras/graphics/samples/calc
cvs diff: Diffing extras/graphics/samples/gears
cvs diff: Diffing extras/graphics/samples/maze
cvs diff: Diffing extras/graphics/samples/pent
cvs diff: Diffing extras/lazy_evaluation
cvs diff: Diffing extras/lex
cvs diff: Diffing extras/lex/samples
cvs diff: Diffing extras/lex/tests
cvs diff: Diffing extras/log4m
cvs diff: Diffing extras/logged_output
cvs diff: Diffing extras/moose
cvs diff: Diffing extras/moose/samples
cvs diff: Diffing extras/moose/tests
cvs diff: Diffing extras/mopenssl
cvs diff: Diffing extras/morphine
cvs diff: Diffing extras/morphine/non-regression-tests
cvs diff: Diffing extras/morphine/scripts
cvs diff: Diffing extras/morphine/source
cvs diff: Diffing extras/net
cvs diff: Diffing extras/odbc
cvs diff: Diffing extras/posix
cvs diff: Diffing extras/posix/samples
cvs diff: Diffing extras/quickcheck
cvs diff: Diffing extras/quickcheck/tutes
cvs diff: Diffing extras/references
cvs diff: Diffing extras/references/samples
cvs diff: Diffing extras/references/tests
cvs diff: Diffing extras/solver_types
cvs diff: Diffing extras/solver_types/library
cvs diff: Diffing extras/trailed_update
cvs diff: Diffing extras/trailed_update/samples
cvs diff: Diffing extras/trailed_update/tests
cvs diff: Diffing extras/windows_installer_generator
cvs diff: Diffing extras/windows_installer_generator/sample
cvs diff: Diffing extras/windows_installer_generator/sample/images
cvs diff: Diffing extras/xml
cvs diff: Diffing extras/xml/samples
cvs diff: Diffing extras/xml_stylesheets
cvs diff: Diffing java
cvs diff: Diffing java/runtime
cvs diff: Diffing library
cvs diff: Diffing mdbcomp
cvs diff: Diffing profiler
cvs diff: Diffing robdd
cvs diff: Diffing runtime
Index: runtime/mercury_trace_term.c
===================================================================
RCS file: /home/mercury/mercury1/repository/mercury/runtime/mercury_trace_term.c,v
retrieving revision 1.2
diff -u -b -r1.2 mercury_trace_term.c
--- runtime/mercury_trace_term.c	24 Nov 2006 03:48:19 -0000	1.2
+++ runtime/mercury_trace_term.c	9 Jun 2007 02:21:35 -0000
@@ -17,23 +17,85 @@
 #include "mercury_trace_term.h"
 #include <stdlib.h>
 
-static  MR_CArgs    MR_create_cargs(char *str, char **rest);
+static  MR_CTerm    MR_parse_cterm(char *str, char endchar, char **rest,
+                        MR_bool *mismatch, char **error);
+static  MR_CTerm    MR_parse_clist_tail(char *str, char **rest,
+                        char *left_bracket, MR_bool *mismatch, char **error);
+static  MR_CArgs    MR_parse_cargs(char *str, char endchar, char **rest,
+                        char *left_paren, MR_bool *mismatch, char **error);
 static  void        MR_delete_cargs(MR_CArgs args);
 
+static  void        MR_print_clist_tail(FILE *fp, MR_CTerm term);
+
+static  MR_bool     MR_cterm_is_nil(MR_CTerm term);
+static  MR_bool     MR_cterm_is_cons(MR_CTerm term,
+                        MR_CTerm *head_ptr, MR_CTerm *tail_ptr);
+
+static  MR_CTerm    MR_make_nil_cterm(void);
+static  MR_CTerm    MR_make_cons_cterm(MR_CTerm head_term, MR_CTerm tail_term);
+
+static  char        *MR_copy_str_fragment(const char *original,
+                        int string_length);
+
 /**************************************************************************/
 
 MR_CTerm
-MR_create_cterm(char *str, char **rest)
+MR_create_cterm(char *str, char **rest, MR_bool *mismatch, char **error)
+{
+    *mismatch = MR_FALSE;
+    *error = NULL;
+    return MR_parse_cterm(str, '\0', rest, mismatch, error);
+}
+
+MR_CTerm
+MR_parse_cterm(char *str, char endchar, char **rest,
+    MR_bool *mismatch, char **error)
 {
     MR_CTerm    term;
     MR_CArgs    args;
+    char        *functor;
+    char        *more;
     int         i;
 
+    while (str[0] != '\0' && MR_isspace(str[0])) {
+        str++;
+    }
+
+    if (str[0] == '[') {
+        MR_CTerm    head_term;
+        MR_CTerm    tail_term;
+
+        if (str[1] == ']') {
+            *rest = &str[2];
+            return MR_make_nil_cterm();
+        }
+
     /*
-    ** We don't have to worry about skipping white space, because there
-    ** won't be any, except inside strings.
+        ** The EBNF grammar for nonempty lists that our code follows is:
+        **
+        ** list ::= "[" term {"," term}* {"|" term}? "]"
+        **
+        ** The initial "[" and the first term are read here; all the rest
+        ** are read by MR_parse_clist_tail.
     */
 
+        head_term = MR_parse_cterm(&str[1], ']', &more, mismatch, error);
+        if (head_term == NULL) {
+            /* MR_parse_cterm has already set *mismatch and *error. */
+            return NULL;
+        }
+
+        tail_term = MR_parse_clist_tail(&more[0], rest, &str[0],
+            mismatch, error);
+        if (tail_term == NULL) {
+            /* MR_parse_clist_tail has already set *mismatch and *error. */
+            MR_delete_cterm(head_term);
+            return NULL;
+        }
+
+        return MR_make_cons_cterm(head_term, tail_term);
+    }
+
     if (str[0] == '"') {
         i = 1;
         while (str[i] != '\0' && str[i] != '"') {
@@ -42,50 +104,165 @@
 
         if (str[i] == '"') {
             term = MR_malloc(sizeof(struct MR_CTerm_Struct));
-            term->MR_term_functor = &str[0];
+            /* Include the double quotes in the function symbol. */
+            term->MR_term_functor = MR_copy_str_fragment(&str[0], i + 1);
             term->MR_term_args = NULL;
             *rest = &str[i + 1];
             return term;
         }
 
+        *mismatch = MR_TRUE;
+        *error = &str[0];
         return NULL;
     }
 
-    if (! MR_isalnumunder(str[0]) || MR_isupper(str[0])) {
+    if (str[0] == '\'') {
+        i = 1;
+        while (str[i] != '\0' && str[i] != '\'') {
+            i++;
+        }
+
+        if (str[i] != '\'') {
+            *mismatch = MR_TRUE;
+            *error = &str[0];
         return NULL;
     }
 
+        /* Don't include the single quotes in the function symbol. */
+        functor = MR_copy_str_fragment(&str[1], i - 1);
+        more = &str[i + 1];
+    } else if (MR_isalnumunder(str[0]) && !MR_isupper(str[0])) {
     i = 0;
     while (MR_isalnumunder(str[i])) {
         i++;
     }
 
-    term = MR_malloc(sizeof(struct MR_CTerm_Struct));
+        functor = MR_copy_str_fragment(&str[0], i);
+        more = &str[i];
+    } else {
+        *mismatch = MR_FALSE;
+        *error = &str[0];
+        return NULL;
+    }
 
-    if ((str[i] == '\0') || str[i] == ',' || str[i] == ')') {
-        term->MR_term_functor = str;
+    i = 0;
+    while (more[i] != '\0' && MR_isspace(more[i])) {
+        i++;
+    }
+
+    if ((more[i] == '\0') || (more[i] == ',') || (more[i] == endchar)) {
+        term = MR_malloc(sizeof(struct MR_CTerm_Struct));
+        term->MR_term_functor = functor;
         term->MR_term_args = NULL;
-        *rest = &str[i];
+        *rest = &more[i];
         return term;
-    } else if (str[i] == '(') {
-        str[i] = '\0';
-        term->MR_term_functor = str;
-        args = MR_create_cargs(&str[i + 1], rest);
+    } else if (more[i] == '(') {
+        args = MR_parse_cargs(&more[i + 1], ')', rest, &more[i],
+            mismatch, error);
         if (args == NULL) {
-            MR_free(term);
+            /* MR_parse_cargs has already set *mismatch and *error. */
+            MR_free(functor);
             return NULL;
         }
 
+        term = MR_malloc(sizeof(struct MR_CTerm_Struct));
+        term->MR_term_functor = functor;
         term->MR_term_args = args;
         return term;
     }
 
-    MR_free(term);
+    *mismatch = MR_FALSE;
+    *error = &more[i];
+    return NULL;
+}
+
+static MR_CTerm
+MR_parse_clist_tail(char *str, char **rest, char *left_bracket,
+    MR_bool *mismatch, char **error)
+{
+    char        *more;
+    int         i;
+
+    while (str[0] != '\0' && MR_isspace(str[0])) {
+        str++;
+    }
+
+    if (str[0] == ']') {
+        *rest = &str[1];
+        return MR_make_nil_cterm();
+    } else if (str[0] == '|') {
+        MR_CTerm    term;
+
+        i = 1;
+        while (str[i] != '\0' && MR_isspace(str[i])) {
+            i++;
+        }
+
+        term = MR_parse_cterm(&str[i], ']', &more, mismatch, error);
+        if (term == NULL) {
+            /* MR_parse_cterm has already set *mismatch and *error. */
+            return NULL;
+        }
+
+        i = 0;
+        while (more[i] != '\0' && MR_isspace(more[i])) {
+            i++;
+        }
+
+        if (more[i] != ']') {
+            *mismatch = MR_TRUE;
+            *error = left_bracket;
+            return NULL;
+        }
+
+        *rest = &more[i+1];
+        return term;
+    } else if (str[0] == ',') {
+        MR_CTerm    head_term;
+        MR_CTerm    tail_term;
+
+        i = 1;
+        while (str[i] != '\0' && MR_isspace(str[i])) {
+            i++;
+        }
+
+        head_term = MR_parse_cterm(&str[i], ']', &more, mismatch, error);
+        if (head_term == NULL) {
+            /* MR_parse_cterm has already set *mismatch and *error. */
+            return NULL;
+        }
+
+        i = 0;
+        while (more[i] != '\0' && MR_isspace(more[i])) {
+            i++;
+        }
+
+        if (more[i] == '\0') {
+            *mismatch = MR_TRUE;
+            *error = left_bracket;
+            MR_delete_cterm(head_term);
+            return NULL;
+        }
+
+        tail_term = MR_parse_clist_tail(&more[i], rest, left_bracket,
+            mismatch, error);
+        if (tail_term == NULL) {
+            /* MR_parse_clist_tail has already set *mismatch and *error. */
+            MR_delete_cterm(head_term);
+            return NULL;
+        }
+
+        return MR_make_cons_cterm(head_term, tail_term);
+    }
+
+    *mismatch = MR_TRUE;
+    *error = left_bracket;
     return NULL;
 }
 
 static MR_CArgs
-MR_create_cargs(char *str, char **rest)
+MR_parse_cargs(char *str, char endchar, char **rest,
+    char *left_paren, MR_bool *mismatch, char **error)
 {
     char            *more;
     MR_CTerm        term;
@@ -93,48 +270,79 @@
     MR_CArgs        tail;
     int             i;
 
-    term = MR_create_cterm(str, &more);
+    term = MR_parse_cterm(str, endchar, &more, mismatch, error);
     if (term == NULL) {
+        /* MR_parse_cterm has already set *mismatch and *error. */
         return NULL;
     }
 
+    while (more[0] != '\0' && MR_isspace(more[0])) {
+        more++;
+    }
+
+    if (more[0] == endchar) {
     args = MR_malloc(sizeof(struct MR_CArgs_Struct));
     args->MR_args_head = term;
-
-    if (more[0] == ')') {
-        more[0] = '\0'; /* to terminate the just previous functor, if any */
         args->MR_args_tail = NULL;
+        if (endchar == '\0') {
+            *rest = &more[0];
+        } else {
         *rest = &more[1];
+        }
         return args;
     } else if (more[0] == ',') {
-        more[0] = '\0'; /* to terminate the just previous functor, if any */
         i = 1;
         while (more[i] != '\0' && MR_isspace(more[i])) {
             i++;
         }
 
         if (more[i] == '\0') {
-            MR_free(args);
+            *mismatch = MR_TRUE;
+            *error = left_paren;
+            MR_delete_cterm(term);
             return NULL;
         }
 
-        tail = MR_create_cargs(more + i, rest);
+        tail = MR_parse_cargs(more + i, endchar, rest, left_paren,
+            mismatch, error);
         if (tail == NULL) {
-            MR_free(args);
+            /* MR_parse_cargs has already set *mismatch and *error. */
+            MR_delete_cterm(term);
             return NULL;
         }
 
+        args = MR_malloc(sizeof(struct MR_CArgs_Struct));
+        args->MR_args_head = term;
         args->MR_args_tail = tail;
         return args;
     }
 
-    MR_free(args);
+    if (more[0] == '\0') {
+        *mismatch = MR_TRUE;
+        *error = left_paren;
+    } else {
+        *mismatch = MR_FALSE;
+        *error = &more[0];
+    }
+
+    MR_delete_cterm(term);
     return NULL;
 }
 
 void
 MR_print_cterm(FILE *fp, MR_CTerm term)
 {
+    MR_CTerm    head;
+    MR_CTerm    tail;
+
+    if (MR_cterm_is_nil(term)) {
+        fprintf(fp, "[]");
+    } else if (MR_cterm_is_cons(term, &head, &tail)) {
+        fprintf(fp, "[");
+        MR_print_cterm(fp, head);
+        MR_print_clist_tail(fp, tail);
+        fprintf(fp, "]");
+    } else {
     MR_CArgs    args;
     int         i;
 
@@ -153,12 +361,85 @@
         }
         fprintf(fp, ")");
     }
+    }
+}
+
+static void
+MR_print_clist_tail(FILE *fp, MR_CTerm term)
+{
+    MR_CTerm    head;
+    MR_CTerm    tail;
+
+    if (MR_cterm_is_nil(term)) {
+        return;
+    } else if (MR_cterm_is_cons(term, &head, &tail)) {
+        fprintf(fp, ", ");
+        MR_print_cterm(fp, head);
+        MR_print_clist_tail(fp, tail);
+    } else {
+        fprintf(fp, " | ");
+        MR_print_cterm(fp, term);
+    }
+}
+
+static MR_bool
+MR_cterm_is_nil(MR_CTerm term)
+{
+    if (term == NULL) {
+        return MR_FALSE;
+    }
+
+    if (MR_strdiff(term->MR_term_functor, "[]")) {
+        return MR_FALSE;
+    }
+
+    if (term->MR_term_args != NULL) {
+        return MR_FALSE;
+    }
+
+    return MR_TRUE;
+}
+
+static MR_bool
+MR_cterm_is_cons(MR_CTerm term, MR_CTerm *head_ptr, MR_CTerm *tail_ptr)
+{
+    MR_CArgs    args1;
+    MR_CArgs    args2;
+    MR_CArgs    args3;
+
+    if (term == NULL) {
+        return MR_FALSE;
+    }
+
+    if (MR_strdiff(term->MR_term_functor, "[|]")) {
+        return MR_FALSE;
+    }
+
+    args1 = term->MR_term_args;
+    if (args1 == NULL) {
+        return MR_FALSE;
+    }
+
+    args2 = args1->MR_args_tail;
+    if (args2 == NULL) {
+        return MR_FALSE;
+    }
+
+    args3 = args2->MR_args_tail;
+    if (args3 != NULL) {
+        return MR_FALSE;
+    }
+
+    *head_ptr = args1->MR_args_head;
+    *tail_ptr = args2->MR_args_head;
+    return MR_TRUE;
 }
 
 void
 MR_delete_cterm(MR_CTerm term)
 {
     if (term != NULL) {
+        MR_free(term->MR_term_functor);
         MR_delete_cargs(term->MR_term_args);
         MR_free(term);
     }
@@ -173,3 +454,52 @@
         MR_free(args);
     }
 }
+
+static MR_CTerm
+MR_make_nil_cterm(void)
+{
+    MR_CTerm    term;
+
+    term = MR_malloc(sizeof(struct MR_CTerm_Struct));
+    term->MR_term_functor = MR_copy_str_fragment("[]", 2);
+    term->MR_term_args = NULL;
+    return term;
+}
+
+static MR_CTerm
+MR_make_cons_cterm(MR_CTerm head_term, MR_CTerm tail_term)
+{
+    MR_CTerm    term;
+    MR_CArgs    args1;
+    MR_CArgs    args2;
+
+    args2 = MR_malloc(sizeof(struct MR_CArgs_Struct));
+    args2->MR_args_head = tail_term;
+    args2->MR_args_tail = NULL;
+
+    args1 = MR_malloc(sizeof(struct MR_CArgs_Struct));
+    args1->MR_args_head = head_term;
+    args1->MR_args_tail = args2;
+
+    term = MR_malloc(sizeof(struct MR_CTerm_Struct));
+    term->MR_term_functor = MR_copy_str_fragment("[|]", 3);
+    term->MR_term_args = args1;
+
+    return term;
+}
+
+/*
+** Make an MR_malloc'd copy of a fragment of a string: string_length bytes
+** starting at *original. Don't assume the original string is NULL-terminated.
+*/
+
+static char *
+MR_copy_str_fragment(const char *original, int string_length)
+{
+    char    *copy;
+
+    copy = MR_malloc(string_length + 1);
+    strncpy(copy, original, string_length);
+    copy[string_length] = '\0';
+    return copy;
+}
Index: runtime/mercury_trace_term.h
===================================================================
RCS file: /home/mercury/mercury1/repository/mercury/runtime/mercury_trace_term.h,v
retrieving revision 1.2
diff -u -b -r1.2 mercury_trace_term.h
--- runtime/mercury_trace_term.h	24 Nov 2006 03:48:19 -0000	1.2
+++ runtime/mercury_trace_term.h	8 Jun 2007 15:15:04 -0000
@@ -14,6 +14,8 @@
 #ifndef MERCURY_TRACE_TERM_H
 #define MERCURY_TRACE_TERM_H
 
+#include "mercury_std.h"
+
 typedef	struct MR_CTerm_Struct		*MR_CTerm;
 typedef	struct MR_CArgs_Struct		*MR_CArgs;
 
@@ -41,12 +43,28 @@
 };
 
 /*
-** Read a term from the string starting at str, and leave *rest pointing
-** to the first character after the term. In case of a syntax error, the return
-** value will be NULL, and *rest is not valid.
+** Read a term from the string starting at str.
+**
+** If successful,
+**
+** - the return value will be non-NULL,
+** - *rest will point to the first character after the term,
+** - *mismatch and *error will not be meaningful.
+**
+** If unsuccessful (due to a syntax error),
+**
+** - the return value will be NULL,
+** - *rest will not be meaningful,
+** - *mismatch will be MR_TRUE if the syntax error is due a missing
+**   close bracket at the end of a list, a missing double quote character
+**   at the end of a string, or a missing single quote character at the end
+**   of a quoted function symbol name, and MR_FALSE otherwise;
+** - *error will point to the unmatched character if *mismatch is MR_TRUE,
+**   and to the site of the syntax error otherwise.
 */
 
-extern	MR_CTerm	MR_create_cterm(char *str, char **rest);
+extern	MR_CTerm	MR_create_cterm(char *str, char **rest,
+				MR_bool *mismatch, char **error);
 
 /*
 ** Print this term to the given file.
cvs diff: Diffing runtime/GETOPT
cvs diff: Diffing runtime/machdeps
cvs diff: Diffing samples
cvs diff: Diffing samples/c_interface
cvs diff: Diffing samples/c_interface/c_calls_mercury
cvs diff: Diffing samples/c_interface/cplusplus_calls_mercury
cvs diff: Diffing samples/c_interface/mercury_calls_c
cvs diff: Diffing samples/c_interface/mercury_calls_cplusplus
cvs diff: Diffing samples/c_interface/mercury_calls_fortran
cvs diff: Diffing samples/c_interface/simpler_c_calls_mercury
cvs diff: Diffing samples/c_interface/simpler_cplusplus_calls_mercury
cvs diff: Diffing samples/c_interface/standalone_c
cvs diff: Diffing samples/diff
cvs diff: Diffing samples/muz
cvs diff: Diffing samples/rot13
cvs diff: Diffing samples/solutions
cvs diff: Diffing samples/solver_types
cvs diff: Diffing samples/tests
cvs diff: Diffing samples/tests/c_interface
cvs diff: Diffing samples/tests/c_interface/c_calls_mercury
cvs diff: Diffing samples/tests/c_interface/cplusplus_calls_mercury
cvs diff: Diffing samples/tests/c_interface/mercury_calls_c
cvs diff: Diffing samples/tests/c_interface/mercury_calls_cplusplus
cvs diff: Diffing samples/tests/c_interface/mercury_calls_fortran
cvs diff: Diffing samples/tests/c_interface/simpler_c_calls_mercury
cvs diff: Diffing samples/tests/c_interface/simpler_cplusplus_calls_mercury
cvs diff: Diffing samples/tests/diff
cvs diff: Diffing samples/tests/muz
cvs diff: Diffing samples/tests/rot13
cvs diff: Diffing samples/tests/solutions
cvs diff: Diffing samples/tests/toplevel
cvs diff: Diffing scripts
cvs diff: Diffing slice
cvs diff: Diffing tests
cvs diff: Diffing tests/benchmarks
cvs diff: Diffing tests/debugger
cvs diff: Diffing tests/debugger/declarative
cvs diff: Diffing tests/dppd
cvs diff: Diffing tests/general
cvs diff: Diffing tests/general/accumulator
cvs diff: Diffing tests/general/string_format
cvs diff: Diffing tests/general/structure_reuse
cvs diff: Diffing tests/grade_subdirs
cvs diff: Diffing tests/hard_coded
cvs diff: Diffing tests/hard_coded/exceptions
cvs diff: Diffing tests/hard_coded/purity
cvs diff: Diffing tests/hard_coded/sub-modules
cvs diff: Diffing tests/hard_coded/typeclasses
cvs diff: Diffing tests/invalid
cvs diff: Diffing tests/invalid/purity
cvs diff: Diffing tests/misc_tests
cvs diff: Diffing tests/mmc_make
cvs diff: Diffing tests/mmc_make/lib
cvs diff: Diffing tests/par_conj
cvs diff: Diffing tests/recompilation
cvs diff: Diffing tests/tabling
cvs diff: Diffing tests/term
cvs diff: Diffing tests/trailing
cvs diff: Diffing tests/valid
cvs diff: Diffing tests/warnings
cvs diff: Diffing tools
cvs diff: Diffing trace
Index: trace/Mmakefile
===================================================================
RCS file: /home/mercury/mercury1/repository/mercury/trace/Mmakefile,v
retrieving revision 1.52
diff -u -b -r1.52 Mmakefile
--- trace/Mmakefile	5 Dec 2006 10:15:03 -0000	1.52
+++ trace/Mmakefile	8 Jun 2007 15:14:20 -0000
@@ -111,7 +111,9 @@
 # whose changes don't usually require a make clean but which nevertheless
 # require the trace library to be recompiled.
 RUNTIME_HDRS	=	\
-			$(RUNTIME_DIR)/mercury_stack_layout.h
+			$(RUNTIME_DIR)/mercury_stack_layout.h \
+			$(RUNTIME_DIR)/mercury_trace_base.h \
+			$(RUNTIME_DIR)/mercury_trace_term.h
 
 TRACE_OBJS		= $(TRACE_SRCS:.c=.$O)
 EVENTSPEC_OBJS		= $(EVENTSPEC_SRCS:.c=.$O)
Index: trace/mercury_trace_cmd_breakpoint.c
===================================================================
RCS file: /home/mercury/mercury1/repository/mercury/trace/mercury_trace_cmd_breakpoint.c,v
retrieving revision 1.4
diff -u -b -r1.4 mercury_trace_cmd_breakpoint.c
--- trace/mercury_trace_cmd_breakpoint.c	12 Jun 2007 06:06:35 -0000	1.4
+++ trace/mercury_trace_cmd_breakpoint.c	12 Jun 2007 06:06:58 -0000
@@ -549,6 +549,8 @@
     MR_SpyCond      *cond;
     MR_VarSpec      var_spec;
     char            *path;
+    MR_bool         mismatch;
+    char            *error_point;
 
     break_num = MR_most_recent_spy_point;
     require_var = MR_TRUE;
@@ -598,19 +600,50 @@
 
     len = 0;
     for (i = 3; i < word_count; i++) {
+        if (i > 3) {
+            len += 1;
+        }
+
         len += strlen(words[i]);
     }
 
     term_str = MR_malloc(len + 1);
     len = 0;
     for (i = 3; i < word_count; i++) {
+        if (i > 3) {
+            strcpy(term_str + len, " ");
+            len += 1;
+        }
+
         strcpy(term_str + len, words[i]);
         len += strlen(words[i]);
     }
 
-    term = MR_create_cterm(term_str, &rest);
+    term = MR_create_cterm(term_str, &rest, &mismatch, &error_point);
     if (term == NULL) {
-        fprintf(MR_mdb_out, "syntax error in term\n");
+        const char  *msg;
+        int         j;
+
+        msg = "syntax error in term: ";
+        fprintf(MR_mdb_out, "%s%s\n", msg, term_str);
+        if (term_str <= error_point &&
+            error_point < term_str + strlen(term_str))
+        {
+            for (j = 0; j < strlen(msg); j++) {
+                putc(' ', MR_mdb_out);
+            }
+
+            for (j = 0; term_str + j != error_point; j++) {
+                putc(' ', MR_mdb_out);
+            }
+
+            if (mismatch) {
+                fprintf(MR_mdb_out, "^ unmatched character\n");
+            } else {
+                fprintf(MR_mdb_out, "^ here\n");
+            }
+        }
+
         return KEEP_INTERACTING;
     }
 
@@ -622,7 +655,6 @@
     if (MR_spy_points[break_num]->MR_spy_cond != NULL) {
         MR_delete_cterm(MR_spy_points[break_num]->MR_spy_cond->MR_cond_term);
         MR_free(MR_spy_points[break_num]->MR_spy_cond->MR_cond_what_string);
-        MR_free(MR_spy_points[break_num]->MR_spy_cond->MR_cond_term_string);
         MR_free(MR_spy_points[break_num]->MR_spy_cond);
     }
 
@@ -636,12 +668,13 @@
         require_path = MR_FALSE;
     }
 
+    MR_free(term_str);
+
     cond = MR_malloc(sizeof(MR_SpyCond));
     cond->MR_cond_var_spec = var_spec;
     cond->MR_cond_path = path;
     cond->MR_cond_test = test;
     cond->MR_cond_term = term;
-    cond->MR_cond_term_string = term_str;
     cond->MR_cond_what_string = what_str;
     cond->MR_cond_require_var = require_var;
     cond->MR_cond_require_path = require_path;
Index: trace/mercury_trace_internal.c
===================================================================
RCS file: /home/mercury/mercury1/repository/mercury/trace/mercury_trace_internal.c,v
retrieving revision 1.234
diff -u -b -r1.234 mercury_trace_internal.c
--- trace/mercury_trace_internal.c	2 May 2007 02:16:01 -0000	1.234
+++ trace/mercury_trace_internal.c	8 Jun 2007 16:17:21 -0000
@@ -1047,6 +1047,9 @@
 ** quotes (') or escapes (\) change the treatment of characters. Make
 ** each word a NULL-terminated string, and remove the quotes and escapes,
 ** overwriting some parts of the line array in the process.
+** XXX The "condition" command would work better if single quotes were
+** left in place; that way, users could type "condition X = '+'"
+** instead of "condition X = \'+\'".
 **
 ** On return *words will point to an array of strings, with space for
 ** *words_max strings. The number of strings filled in will be given by
Index: trace/mercury_trace_spy.c
===================================================================
RCS file: /home/mercury/mercury1/repository/mercury/trace/mercury_trace_spy.c,v
retrieving revision 1.31
diff -u -b -r1.31 mercury_trace_spy.c
--- trace/mercury_trace_spy.c	12 Jun 2007 06:06:35 -0000	1.31
+++ trace/mercury_trace_spy.c	12 Jun 2007 06:06:58 -0000
@@ -1025,7 +1025,6 @@
     if (point->MR_spy_cond != NULL) {
         MR_delete_cterm(point->MR_spy_cond->MR_cond_term);
         MR_free(point->MR_spy_cond->MR_cond_what_string);
-        MR_free(point->MR_spy_cond->MR_cond_term_string);
         MR_free(point->MR_spy_cond);
 
         /* in case it gets deleted again */
@@ -1382,7 +1381,8 @@
                     break;
             }
 
-            fprintf(fp, "%s\n", cond->MR_cond_term_string);
+            MR_print_cterm(fp, cond->MR_cond_term);
+            fprintf(fp, "\n");
         }
 
         if (!point->MR_spy_enabled) {
Index: trace/mercury_trace_spy.h
===================================================================
RCS file: /home/mercury/mercury1/repository/mercury/trace/mercury_trace_spy.h,v
retrieving revision 1.14
diff -u -b -r1.14 mercury_trace_spy.h
--- trace/mercury_trace_spy.h	19 Jan 2007 04:42:51 -0000	1.14
+++ trace/mercury_trace_spy.h	8 Jun 2007 15:19:30 -0000
@@ -87,7 +87,6 @@
     MR_bool         MR_cond_require_var;
     MR_bool         MR_cond_require_path;
     char            *MR_cond_what_string;
-    char            *MR_cond_term_string;
 } MR_SpyCond;
 
 struct MR_SpyPoint_Struct {
cvs diff: Diffing util
cvs diff: Diffing vim
cvs diff: Diffing vim/after
cvs diff: Diffing vim/ftplugin
cvs diff: Diffing vim/syntax
--------------------------------------------------------------------------
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