This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH 3/4] (v2) Introduce class edit_context


This version removes edit_context::apply_changes and related
functionality, retaining the ability to generate diffs.

It also improves error-handling (adding a selftest for this).

gcc/ChangeLog:
	* Makefile.in (OBJS-libcommon): Add edit-context.o.
	* diagnostic-color.c (color_dict): Add "diff-filename",
	"diff-hunk", "diff-delete", and "diff-insert".
	(parse_gcc_colors): Update default value of GCC_COLORS in comment
	to reflect above changes.
	* edit-context.c: New file.
	* edit-context.h: New file.
	* selftest-run-tests.c (selftest::run_tests): Call
	edit_context_c_tests.
	* selftest.h (edit_context_c_tests): New decl.
---
 gcc/Makefile.in          |    1 +
 gcc/diagnostic-color.c   |    8 +-
 gcc/edit-context.c       | 1347 ++++++++++++++++++++++++++++++++++++++++++++++
 gcc/edit-context.h       |   65 +++
 gcc/selftest-run-tests.c |    1 +
 gcc/selftest.h           |    1 +
 6 files changed, 1422 insertions(+), 1 deletion(-)
 create mode 100644 gcc/edit-context.c
 create mode 100644 gcc/edit-context.h

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index f5f3339..506f0d4 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1561,6 +1561,7 @@ OBJS = \
 # Objects in libcommon.a, potentially used by all host binaries and with
 # no target dependencies.
 OBJS-libcommon = diagnostic.o diagnostic-color.o diagnostic-show-locus.o \
+	edit-context.o \
 	pretty-print.o intl.o \
 	vec.o input.o version.o hash-table.o ggc-none.o memory-block.o \
 	selftest.o
diff --git a/gcc/diagnostic-color.c b/gcc/diagnostic-color.c
index f76c87b..decbe84 100644
--- a/gcc/diagnostic-color.c
+++ b/gcc/diagnostic-color.c
@@ -168,6 +168,10 @@ static struct color_cap color_dict[] =
   { "range2", SGR_SEQ (COLOR_FG_BLUE), 6, false },
   { "locus", SGR_SEQ (COLOR_BOLD), 5, false },
   { "quote", SGR_SEQ (COLOR_BOLD), 5, false },
+  { "diff-filename", SGR_SEQ (COLOR_BOLD), 13, false },
+  { "diff-hunk", SGR_SEQ (COLOR_FG_CYAN), 9, false },
+  { "diff-delete", SGR_SEQ (COLOR_FG_RED), 11, false },
+  { "diff-insert", SGR_SEQ (COLOR_FG_GREEN), 11, false },
   { NULL, NULL, 0, false }
 };
 
@@ -196,7 +200,9 @@ colorize_stop (bool show_color)
 }
 
 /* Parse GCC_COLORS.  The default would look like:
-   GCC_COLORS='error=01;31:warning=01;35:note=01;36:range1=32:range2=34;locus=01:quote=01'
+   GCC_COLORS='error=01;31:warning=01;35:note=01;36:\
+   range1=32:range2=34:locus=01:quote=01:\
+   diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32'
    No character escaping is needed or supported.  */
 static bool
 parse_gcc_colors (void)
diff --git a/gcc/edit-context.c b/gcc/edit-context.c
new file mode 100644
index 0000000..6b79b45
--- /dev/null
+++ b/gcc/edit-context.c
@@ -0,0 +1,1347 @@
+/* Determining the results of applying fix-its.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "line-map.h"
+#include "edit-context.h"
+#include "pretty-print.h"
+#include "diagnostic-color.h"
+#include "selftest.h"
+
+/* This file implements a way to track the effect of fix-its,
+   via a class edit_context; the other classes are support classes for
+   edit_context.
+
+   A complication here is that fix-its are expressed relative to coordinates
+   in the file when it was parsed, before any changes have been made, and
+   so if there's more that one fix-it to be applied, we have to adjust
+   later fix-its to allow for the changes made by earlier ones.  This
+   is done by the various "get_effective_column" methods.
+
+   The "filename" params are required to outlive the edit_context (no
+   copy of the underlying str is taken, just the ptr).  */
+
+/* Forward decls.  class edit_context is declared within edit-context.h.
+   The other types are declared here.  */
+class edit_context;
+class edited_file;
+class line_state;
+class line_event;
+  class insert_event;
+  class replace_event;
+
+/* A struct to hold the params of a print_diff call.  */
+
+struct diff
+{
+  diff (pretty_printer *pp, bool show_filenames)
+  : m_pp (pp), m_show_filenames (show_filenames) {}
+
+  pretty_printer *m_pp;
+  bool m_show_filenames;
+};
+
+/* The state of one named file within an edit_context: the filename,
+   the current content of the file after applying all changes so far, and
+   a record of the changes, so that further changes can be applied in
+   the correct place.  */
+
+class edited_file
+{
+ public:
+  edited_file (const char *filename);
+  bool read_from_file ();
+  ~edited_file ();
+  static void delete_cb (edited_file *file);
+
+  const char *get_filename () const { return m_filename; }
+  const char *get_content () const { return m_content; }
+
+  bool apply_insert (int line, int column, const char *str, int len);
+  bool apply_replace (int line, int start_column,
+		      int finish_column,
+		      const char *replacement_str,
+		      int replacement_len);
+  int get_effective_column (int line, int column);
+
+  static int call_print_diff (const char *, edited_file *file,
+			      void *user_data)
+  {
+    diff *d = (diff *)user_data;
+    file->print_diff (d->m_pp, d->m_show_filenames);
+    return 0;
+  }
+
+ private:
+  void print_diff (pretty_printer *pp, bool show_filenames);
+  void print_line_in_diff (pretty_printer *pp, int line_num);
+  line_state *get_line (int line);
+  line_state &get_or_insert_line (int line);
+  void ensure_capacity (size_t len);
+  void ensure_terminated ();
+  char *get_line_start (int line_num);
+  void ensure_line_start_index ();
+  void evict_line_start_index ();
+  int get_num_lines ();
+
+  const char *m_filename;
+  char *m_content;
+  size_t m_len;
+  size_t m_alloc_sz;
+  typed_splay_tree<int, line_state *> m_edited_lines;
+  auto_vec<int> m_line_start_index;
+};
+
+/* A mapping from pre-edit columns to post-edit columns
+   within one line of one file.  */
+
+class line_state
+{
+ public:
+  line_state (int line_num) : m_line_num (line_num), m_line_events () {}
+  ~line_state ();
+  static void delete_cb (line_state *ls);
+
+  int get_line_num () const { return m_line_num; }
+
+  int get_effective_column (int orig_column) const;
+  void apply_insert (int column, int len);
+  void apply_replace (int start, int finish, int len);
+
+ private:
+  int m_line_num;
+  auto_vec <line_event *> m_line_events;
+};
+
+/* Abstract base class for representing events that have occurred
+   on one line of one file.  */
+
+class line_event
+{
+ public:
+  virtual ~line_event () {}
+  virtual int get_effective_column (int orig_column) const = 0;
+};
+
+/* Concrete subclass of line_event: an insertion of some text
+   at some column on the line.
+
+   Subsequent events will need their columns adjusting if they're
+   are on this line and their column is >= the insertion point.  */
+
+class insert_event : public line_event
+{
+ public:
+  insert_event (int column, int len) : m_column (column), m_len (len) {}
+  int get_effective_column (int orig_column) const FINAL OVERRIDE
+  {
+    if (orig_column >= m_column)
+      return orig_column + m_len;
+    else
+      return orig_column;
+  }
+
+ private:
+  int m_column;
+  int m_len;
+};
+
+/* Concrete subclass of line_event: the replacement of some text
+   betweeen some columns on the line.
+
+   Subsequent events will need their columns adjusting if they're
+   are on this line and their column is >= the finish point.  */
+
+class replace_event : public line_event
+{
+ public:
+  replace_event (int start, int finish, int len) : m_start (start),
+    m_finish (finish), m_delta (len - (finish + 1 - start)) {}
+
+  int get_effective_column (int orig_column) const FINAL OVERRIDE
+  {
+    if (orig_column >= m_start)
+      return orig_column += m_delta;
+    else
+      return orig_column;
+  }
+
+ private:
+  int m_start;
+  int m_finish;
+  int m_delta;
+};
+
+/* Implementation of class edit_context.  */
+
+/* edit_context's ctor.  */
+
+edit_context::edit_context ()
+: m_valid (true),
+  m_files (strcmp, NULL, edited_file::delete_cb)
+{}
+
+/* Add any fixits within RICHLOC to this context, recording the
+   changes that they make.  */
+
+void
+edit_context::add_fixits (rich_location *richloc)
+{
+  if (!m_valid)
+    return;
+  for (unsigned i = 0; i < richloc->get_num_fixit_hints (); i++)
+    {
+      const fixit_hint *hint = richloc->get_fixit_hint (i);
+      switch (hint->get_kind ())
+	{
+	case fixit_hint::INSERT:
+	  if (!apply_insert ((const fixit_insert *)hint))
+	    {
+	      /* Failure.  */
+	      m_valid = false;
+	      return;
+	    }
+	  break;
+	case fixit_hint::REPLACE:
+	  if (!apply_replace ((const fixit_replace *)hint))
+	    {
+	      /* Failure.  */
+	      m_valid = false;
+	      return;
+	    }
+	  break;
+	default:
+	  gcc_unreachable ();
+	}
+    }
+}
+
+/* Get the content of the given file, with fix-its applied.
+   If any errors occurred in this edit_context, return NULL.  */
+
+const char *
+edit_context::get_content (const char *filename)
+{
+  if (!m_valid)
+    return NULL;
+  edited_file &file = get_or_insert_file (filename);
+  return file.get_content ();
+}
+
+/* Map a location before the edits to a column number after the edits.
+   This method is for the selftests.  */
+
+int
+edit_context::get_effective_column (const char *filename, int line,
+				    int column)
+{
+  edited_file *file = get_file (filename);
+  if (!file)
+    return column;
+  return file->get_effective_column (line, column);
+}
+
+/* Generate a unified diff.  The resulting string should be freed by the
+   caller.  Primarily for selftests.
+   If any errors occurred in this edit_context, return NULL.  */
+
+char *
+edit_context::generate_diff (bool show_filenames)
+{
+  if (!m_valid)
+    return NULL;
+
+  pretty_printer pp;
+  print_diff (&pp, show_filenames);
+  return xstrdup (pp_formatted_text (&pp));
+}
+
+/* Print a unified diff to PP, showing the changes made within the
+   context.  */
+
+void
+edit_context::print_diff (pretty_printer *pp, bool show_filenames)
+{
+  if (!m_valid)
+    return;
+  diff d (pp, show_filenames);
+  m_files.foreach (edited_file::call_print_diff, &d);
+}
+
+/* Attempt to apply the given fixit.  Return true if it can be
+   applied, or false otherwise.  */
+
+bool
+edit_context::apply_insert (const fixit_insert *insert)
+{
+  expanded_location exploc = expand_location (insert->get_location ());
+
+  if (exploc.column == 0)
+    return false;
+
+  edited_file &file = get_or_insert_file (exploc.file);
+  if (!m_valid)
+    return false;
+  return file.apply_insert (exploc.line, exploc.column, insert->get_string (),
+			    insert->get_length ());
+}
+
+/* Attempt to apply the given fixit.  Return true if it can be
+   applied, or false otherwise.  */
+
+bool
+edit_context::apply_replace (const fixit_replace *replace)
+{
+  source_range range = replace->get_range ();
+
+  expanded_location start = expand_location (range.m_start);
+  expanded_location finish = expand_location (range.m_finish);
+  if (start.file != finish.file)
+    return false;
+  if (start.line != finish.line)
+    return false;
+  if (start.column == 0)
+    return false;
+  if (finish.column == 0)
+    return false;
+
+  edited_file &file = get_or_insert_file (start.file);
+  if (!m_valid)
+    return false;
+  return file.apply_replace (start.line, start.column, finish.column,
+			     replace->get_string (),
+			     replace->get_length ());
+}
+
+/* Locate the edited_file * for FILENAME, if any
+   Return NULL if there isn't one.  */
+
+edited_file *
+edit_context::get_file (const char *filename)
+{
+  gcc_assert (filename);
+  return m_files.lookup (filename);
+}
+
+/* Locate the edited_file for FILENAME, adding one if there isn't one.  */
+
+edited_file &
+edit_context::get_or_insert_file (const char *filename)
+{
+  gcc_assert (filename);
+
+  edited_file *file = get_file (filename);
+  if (file)
+    return *file;
+
+  /* Not found.  */
+  file = new edited_file (filename);
+  if (!file->read_from_file ())
+    m_valid = false;
+  m_files.insert (filename, file);
+  return *file;
+}
+
+/* Implementation of class edited_file.  */
+
+/* Callback for m_edited_lines, for comparing line numbers.  */
+
+static int line_comparator (int a, int b)
+{
+  return a - b;
+}
+
+/* edited_file's constructor.  */
+
+edited_file::edited_file (const char *filename)
+: m_filename (filename),
+  m_content (NULL), m_len (0), m_alloc_sz (0),
+  m_edited_lines (line_comparator, NULL, line_state::delete_cb)
+{
+}
+
+/* Read the contents of the file from the filesystem into memory.
+   Return true if successful; false if any problems occurred.  */
+
+bool
+edited_file::read_from_file ()
+{
+  FILE *f_in = fopen (m_filename, "r");
+  if (!f_in)
+    return false;
+
+  char buf[4096];
+  size_t iter_sz_in;
+
+  while ( (iter_sz_in = fread (buf, 1, sizeof (buf), f_in)) )
+    {
+      gcc_assert (m_alloc_sz >= m_len);
+      size_t old_len = m_len;
+      m_len += iter_sz_in;
+      ensure_capacity (m_len);
+      memcpy (m_content + old_len, buf, iter_sz_in);
+    }
+
+  if (!feof (f_in))
+    return false;
+
+  fclose (f_in);
+
+  ensure_terminated ();
+
+  return true;
+}
+
+/* edited_file's dtor.  */
+
+edited_file::~edited_file ()
+{
+  free (m_content);
+}
+
+/* A callback for deleting edited_file *, for use as a
+   delete_value_fn for edit_context::m_files.  */
+
+void
+edited_file::delete_cb (edited_file *file)
+{
+  delete file;
+}
+
+/* Insert the string INSERT_STR with length INSERT_LEN at LINE and COLUMN,
+   updating the in-memory copy of the file, and the record of edits to
+   the line.  */
+
+bool
+edited_file::apply_insert (int line, int column,
+			   const char *insert_str,
+			   int insert_len)
+{
+  column = get_effective_column (line, column);
+
+  int start_offset = column - 1;
+  gcc_assert (start_offset >= 0);
+
+  /* Ensure buffer is big enough.  */
+  size_t new_len = m_len + insert_len;
+  ensure_capacity (new_len);
+
+  char *line_start = get_line_start (line);
+  if (!line_start)
+    return false;
+
+  char *suffix = line_start + start_offset;
+  size_t len_suffix = (m_content + m_len) - suffix;
+
+  /* Move successor content into position.  They overlap, so use memmove.  */
+  memmove (line_start + start_offset + insert_len,
+	   suffix, len_suffix);
+
+  /* Replace target content.  They don't overlap, so use memcpy.  */
+  memcpy (line_start + start_offset,
+	  insert_str,
+	  insert_len);
+
+  m_len = new_len;
+
+  ensure_terminated ();
+  evict_line_start_index ();
+
+  /* Record the insertion, so that followup changes to this line
+     can have their columns adjusted as appropriate.  */
+  line_state &ls = get_or_insert_line (line);
+  ls.apply_insert (column, insert_len);
+
+  return true;
+}
+
+/* Replace columns START_COLUMN through FINISH_COLUMN of LINE
+   with the string REPLACEMENT_STR of length REPLACEMENT_LEN,
+   updating the in-memory copy of the file, and the record of edits to
+   the line.  */
+
+bool
+edited_file::apply_replace (int line, int start_column,
+			    int finish_column,
+			    const char *replacement_str,
+			    int replacement_len)
+{
+  start_column = get_effective_column (line, start_column);
+  finish_column = get_effective_column (line, finish_column);
+
+  int start_offset = start_column - 1;
+  int end_offset = finish_column - 1;
+
+  gcc_assert (start_offset >= 0);
+  gcc_assert (end_offset >= 0);
+
+  gcc_assert (start_column <= finish_column);
+
+  size_t victim_len = end_offset - start_offset + 1;
+
+  /* Ensure buffer is big enough.  */
+  size_t new_len = m_len + replacement_len - victim_len;
+  ensure_capacity (new_len);
+
+  char *line_start = get_line_start (line);
+  if (!line_start)
+    return false;
+
+  char *suffix = line_start + end_offset + 1;
+  size_t len_suffix = (m_content + m_len) - suffix;
+
+  /* Move successor content into position.  They overlap, so use memmove.  */
+  memmove (line_start + start_offset + replacement_len,
+	   suffix, len_suffix);
+
+  /* Replace target content.  They don't overlap, so use memcpy.  */
+  memcpy (line_start + start_offset,
+	  replacement_str,
+	  replacement_len);
+
+  m_len = new_len;
+
+  ensure_terminated ();
+  evict_line_start_index ();
+
+  /* Record the replacement, so that followup changes to this line
+     can have their columns adjusted as appropriate.  */
+  line_state &ls = get_or_insert_line (line);
+  ls.apply_replace (start_column, finish_column, replacement_len);
+
+  return true;
+}
+
+/* Given line LINE, map from COLUMN in the input file to its current
+   column after edits have been applied.  */
+
+int
+edited_file::get_effective_column (int line, int column)
+{
+  const line_state *ls = get_line (line);
+  if (!ls)
+    return column;
+  return ls->get_effective_column (column);
+}
+
+/* Print a unified diff to PP, showing any changes that have occurred
+   to this file.  */
+
+void
+edited_file::print_diff (pretty_printer *pp, bool show_filenames)
+{
+  if (show_filenames)
+    {
+      pp_string (pp, colorize_start (pp_show_color (pp), "diff-filename"));
+      pp_printf (pp, "--- %s\n", m_filename);
+      pp_printf (pp, "+++ %s\n", m_filename);
+      pp_string (pp, colorize_stop (pp_show_color (pp)));
+    }
+
+  line_state *ls = m_edited_lines.min ();
+
+  const int context_lines = 3;
+
+  while (ls)
+    {
+      int start_of_hunk = ls->get_line_num ();
+      start_of_hunk -= context_lines;
+      if (start_of_hunk < 1)
+	start_of_hunk = 1;
+
+      /* Locate end of hunk, merging in changed lines
+	 that are sufficiently close.  */
+      while (true)
+	{
+	  line_state *next_ls
+	    = m_edited_lines.successor (ls->get_line_num ());
+	  if (!next_ls)
+	    break;
+	  if (ls->get_line_num () + context_lines
+	      >= next_ls->get_line_num () - context_lines)
+	    ls = next_ls;
+	  else
+	    break;
+	}
+      int end_of_hunk = ls->get_line_num ();
+      end_of_hunk += context_lines;
+      if (end_of_hunk > get_num_lines ())
+	end_of_hunk = get_num_lines ();
+
+      int num_lines = end_of_hunk - start_of_hunk + 1;
+
+      pp_string (pp, colorize_start (pp_show_color (pp), "diff-hunk"));
+      pp_printf (pp, "@@ -%i,%i +%i,%i @@\n", start_of_hunk, num_lines,
+		 start_of_hunk, num_lines);
+      pp_string (pp, colorize_stop (pp_show_color (pp)));
+
+      int line_num = start_of_hunk;
+      while (line_num <= end_of_hunk)
+	{
+	  line_state *ls = get_line (line_num);
+	  if (ls)
+	    {
+	      /* We have an edited line.
+		 Consolidate into runs of changed lines.  */
+	      const int first_changed_line_in_run = line_num;
+	      while (get_line (line_num))
+		line_num++;
+	      const int last_changed_line_in_run = line_num - 1;
+
+	      pp_string (pp, colorize_start (pp_show_color (pp),
+					     "diff-delete"));
+
+	      /* Show old version of lines.  */
+	      for (line_num = first_changed_line_in_run;
+		   line_num <= last_changed_line_in_run;
+		   line_num++)
+		{
+		  int line_size;
+		  const char *old_line
+		    = location_get_source_line (m_filename, line_num,
+						&line_size);
+		  pp_character (pp, '-');
+		  for (int i = 0; i < line_size; i++)
+		    pp_character (pp, old_line[i]);
+		  pp_character (pp, '\n');
+		}
+
+	      pp_string (pp, colorize_stop (pp_show_color (pp)));
+
+	      pp_string (pp, colorize_start (pp_show_color (pp),
+					     "diff-insert"));
+
+	      /* Show new version of lines.  */
+	      for (line_num = first_changed_line_in_run;
+		   line_num <= last_changed_line_in_run;
+		   line_num++)
+		{
+		  pp_character (pp, '+');
+		  print_line_in_diff (pp, line_num);
+		}
+
+	      pp_string (pp, colorize_stop (pp_show_color (pp)));
+
+	    }
+	  else
+	    {
+	      /* Unchanged line.  */
+	      pp_character (pp, ' ');
+	      print_line_in_diff (pp, line_num);
+	      line_num++;
+	    }
+	}
+
+      ls = m_edited_lines.successor (ls->get_line_num ());
+    }
+}
+
+/* Subroutine of edited_file::print_diff.  Print the given line
+   (after edits) to PP (with a trailing newline).  */
+
+void
+edited_file::print_line_in_diff (pretty_printer *pp, int line_num)
+{
+  const char *line = get_line_start (line_num);
+  gcc_assert (line);
+  while (char ch = *line++)
+    {
+      pp_character (pp, ch);
+      if (ch == '\n')
+	break;
+    }
+}
+
+/* Get the state of LINE within the file, or NULL if it is untouched.  */
+
+line_state *
+edited_file::get_line (int line)
+{
+  return m_edited_lines.lookup (line);
+}
+
+/* Get the state of LINE within the file, creating a state for it
+   if necessary.  */
+
+line_state &
+edited_file::get_or_insert_line (int line)
+{
+  line_state *ls = get_line (line);
+  if (ls)
+    return *ls;
+  ls = new line_state (line);
+  m_edited_lines.insert (line, ls);
+  return *ls;
+}
+
+/* Ensure that the buffer for m_content is at least large enough to hold
+   a string of length LEN and its 0-terminator, doubling on repeated
+   allocations.  */
+
+void
+edited_file::ensure_capacity (size_t len)
+{
+  /* Allow 1 extra byte for 0-termination.  */
+  if (m_alloc_sz < (len + 1))
+    {
+      size_t new_alloc_sz = (len + 1) * 2;
+      m_content = (char *)xrealloc (m_content, new_alloc_sz);
+      m_alloc_sz = new_alloc_sz;
+    }
+}
+
+/* Ensure that m_content is 0-terminated.  */
+
+void
+edited_file::ensure_terminated ()
+{
+  /* 0-terminate the buffer.  */
+  gcc_assert (m_len < m_alloc_sz);
+  m_content[m_len] = '\0';
+}
+
+/* Return a pointer to the start of the given line within the
+   m_content buffer, or NULL if line_num is out of range.  */
+
+char *
+edited_file::get_line_start (int line_num)
+{
+  if (line_num < 1)
+    return NULL;
+  ensure_line_start_index ();
+  if (line_num > (int)m_line_start_index.length ())
+    return NULL;
+  return &m_content[m_line_start_index[line_num - 1]];
+}
+
+/* If necessary, regenerate the index of line-start offsets
+   based on the current state of m_content.  */
+
+void
+edited_file::ensure_line_start_index ()
+{
+  if (m_line_start_index.length () == 0)
+    {
+      int offset = 0;
+      m_line_start_index.safe_push (0);
+      while (char ch = m_content[offset++])
+	{
+	  if (ch == '\n')
+	    if (m_content[offset])
+	      m_line_start_index.safe_push (offset);
+	  if (ch == '\0')
+	    break;
+	}
+    }
+}
+
+/* Evict the index of line-start offsets.  */
+
+void
+edited_file::evict_line_start_index ()
+{
+  m_line_start_index.truncate (0);
+}
+
+/* Get the total number of lines in m_content.  */
+
+int
+edited_file::get_num_lines ()
+{
+  ensure_line_start_index ();
+  return m_line_start_index.length ();
+}
+
+/* Implementation of class line_state.  */
+
+/* A callback for deleting line_state *, for use as a
+   delete_value_fn for edited_file::m_edited_lines.  */
+
+void
+line_state::delete_cb (line_state *ls)
+{
+  delete ls;
+}
+
+/* line_state's dtor.  */
+
+line_state::~line_state ()
+{
+  int i;
+  line_event *event;
+  FOR_EACH_VEC_ELT (m_line_events, i, event)
+    delete event;
+}
+
+/* Map a location before the edits to a column number after the edits,
+   within a specific line.  */
+
+int
+line_state::get_effective_column (int orig_column) const
+{
+  int i;
+  line_event *event;
+  FOR_EACH_VEC_ELT (m_line_events, i, event)
+    orig_column = event->get_effective_column (orig_column);
+  return orig_column;
+}
+
+/* Record that a string of length LEN was inserted at COLUMN within
+   this line, so that future changes to the line can have their
+   column information adjusted accordingly.  */
+
+void
+line_state::apply_insert (int column, int len)
+{
+  m_line_events.safe_push (new insert_event (column, len));
+}
+
+/* Record that the string between columns START and FINISH
+   of this line was replaced with a string of length LEN,
+   so that future changes to the line can have their
+   column information adjusted accordingly.  */
+
+void
+line_state::apply_replace (int start, int finish, int len)
+{
+  m_line_events.safe_push (new replace_event (start, finish, len));
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Selftests of code-editing.  */
+
+/* Test applying an "insert" fixit.  */
+
+static void
+test_applying_fixits_insert (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     .....................0000000001111111.
+     .....................1234567890123456.  */
+  const char *content = ("/* before */\n"
+			 "foo = bar.field;\n"
+			 "/* after */\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt (case_);
+  linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 2);
+
+  /* Add a comment in front of "bar.field".  */
+  location_t start = linemap_position_for_column (line_table, 7);
+  rich_location richloc (line_table, start);
+  richloc.add_fixit_insert (start, "/* inserted */");
+
+  if (start > LINE_MAP_MAX_LOCATION_WITH_COLS)
+    return;
+
+  edit_context edit;
+  edit.add_fixits (&richloc);
+  const char *result = edit.get_content (filename);
+  if (start <= LINE_MAP_MAX_LOCATION_WITH_COLS)
+    ASSERT_STREQ ("/* before */\n"
+		  "foo = /* inserted */bar.field;\n"
+		  "/* after */\n", result);
+
+  /* Verify that locations on other lines aren't affected by the change.  */
+  ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
+  ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
+
+  /* Verify locations on the line before the change.  */
+  ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
+  ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
+
+  /* Verify locations on the line at and after the change.  */
+  ASSERT_EQ (21, edit.get_effective_column (filename, 2, 7));
+  ASSERT_EQ (22, edit.get_effective_column (filename, 2, 8));
+
+  /* Verify diff.  */
+  char *diff = edit.generate_diff (false);
+  ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
+		" /* before */\n"
+		"-foo = bar.field;\n"
+		"+foo = /* inserted */bar.field;\n"
+		" /* after */\n", diff);
+  free (diff);
+}
+
+/* Test applying a "replace" fixit that grows the affected line.  */
+
+static void
+test_applying_fixits_growing_replace (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     .....................0000000001111111.
+     .....................1234567890123456.  */
+  const char *content = ("/* before */\n"
+			 "foo = bar.field;\n"
+			 "/* after */\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt (case_);
+  linemap_add (line_table, LC_ENTER, false, filename, 2);
+
+  /* Replace "field" with "m_field".  */
+  location_t start = linemap_position_for_column (line_table, 11);
+  location_t finish = linemap_position_for_column (line_table, 15);
+  location_t field = make_location (start, start, finish);
+  rich_location richloc (line_table, field);
+  source_range range;
+  range.m_start = start;
+  range.m_finish = finish;
+  richloc.add_fixit_replace (range, "m_field");
+
+  edit_context edit;
+  edit.add_fixits (&richloc);
+  const char *result = edit.get_content (filename);
+  if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
+    {
+      ASSERT_STREQ ("/* before */\n"
+		    "foo = bar.m_field;\n"
+		    "/* after */\n", result);
+
+      /* Verify location of ";" after the change.  */
+      ASSERT_EQ (18, edit.get_effective_column (filename, 2, 16));
+
+      /* Verify diff.  */
+      char *diff = edit.generate_diff (false);
+      ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
+		    " /* before */\n"
+		    "-foo = bar.field;\n"
+		    "+foo = bar.m_field;\n"
+		    " /* after */\n", diff);
+      free (diff);
+    }
+}
+
+/* Test applying a "replace" fixit that shrinks the affected line.  */
+
+static void
+test_applying_fixits_shrinking_replace (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     .....................000000000111111111.
+     .....................123456789012345678.  */
+  const char *content = ("/* before */\n"
+			 "foo = bar.m_field;\n"
+			 "/* after */\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt (case_);
+  linemap_add (line_table, LC_ENTER, false, filename, 2);
+
+  /* Replace "field" with "m_field".  */
+  location_t start = linemap_position_for_column (line_table, 11);
+  location_t finish = linemap_position_for_column (line_table, 17);
+  location_t m_field = make_location (start, start, finish);
+  rich_location richloc (line_table, m_field);
+  source_range range;
+  range.m_start = start;
+  range.m_finish = finish;
+  richloc.add_fixit_replace (range, "field");
+
+  edit_context edit;
+  edit.add_fixits (&richloc);
+  const char *result = edit.get_content (filename);
+  if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
+    {
+      ASSERT_STREQ ("/* before */\n"
+		    "foo = bar.field;\n"
+		    "/* after */\n", result);
+
+      /* Verify location of ";" after the change.  */
+      ASSERT_EQ (16, edit.get_effective_column (filename, 2, 18));
+
+      /* Verify diff.  */
+      char *diff = edit.generate_diff (false);
+      ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
+		    " /* before */\n"
+		    "-foo = bar.m_field;\n"
+		    "+foo = bar.field;\n"
+		    " /* after */\n", diff);
+      free (diff);
+    }
+}
+
+/* Test applying a "remove" fixit.  */
+
+static void
+test_applying_fixits_remove (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     .....................000000000111111111.
+     .....................123456789012345678.  */
+  const char *content = ("/* before */\n"
+			 "foo = bar.m_field;\n"
+			 "/* after */\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt (case_);
+  linemap_add (line_table, LC_ENTER, false, filename, 2);
+
+  /* Remove ".m_field".  */
+  location_t start = linemap_position_for_column (line_table, 10);
+  location_t finish = linemap_position_for_column (line_table, 17);
+  rich_location richloc (line_table, start);
+  source_range range;
+  range.m_start = start;
+  range.m_finish = finish;
+  richloc.add_fixit_remove (range);
+
+  edit_context edit;
+  edit.add_fixits (&richloc);
+  const char *result = edit.get_content (filename);
+  if (finish <= LINE_MAP_MAX_LOCATION_WITH_COLS)
+    {
+      ASSERT_STREQ ("/* before */\n"
+		    "foo = bar;\n"
+		    "/* after */\n", result);
+
+      /* Verify location of ";" after the change.  */
+      ASSERT_EQ (10, edit.get_effective_column (filename, 2, 18));
+
+      /* Verify diff.  */
+      char *diff = edit.generate_diff (false);
+      ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
+		    " /* before */\n"
+		    "-foo = bar.m_field;\n"
+		    "+foo = bar;\n"
+		    " /* after */\n", diff);
+      free (diff);
+    }
+}
+
+/* Test applying multiple fixits to one line.  */
+
+static void
+test_applying_fixits_multiple (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     .....................00000000011111111.
+     .....................12345678901234567.  */
+  const char *content = ("/* before */\n"
+			 "foo = bar.field;\n"
+			 "/* after */\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt (case_);
+  linemap_add (line_table, LC_ENTER, false, filename, 2);
+
+  location_t c7 = linemap_position_for_column (line_table, 7);
+  location_t c9 = linemap_position_for_column (line_table, 9);
+  location_t c11 = linemap_position_for_column (line_table, 11);
+  location_t c15 = linemap_position_for_column (line_table, 15);
+  location_t c17 = linemap_position_for_column (line_table, 17);
+
+  if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS)
+    return;
+
+  /* Add a comment in front of "bar.field".  */
+  rich_location insert_a (line_table, c7);
+  insert_a.add_fixit_insert (c7, "/* alpha */");
+
+  /* Add a comment after "bar.field;".  */
+  rich_location insert_b (line_table, c17);
+  insert_b.add_fixit_insert (c17, "/* beta */");
+
+  /* Replace "bar" with "pub".   */
+  rich_location replace_a (line_table, c7);
+  replace_a.add_fixit_replace (source_range::from_locations (c7, c9),
+			       "pub");
+
+  /* Replace "field" with "meadow".   */
+  rich_location replace_b (line_table, c7);
+  replace_b.add_fixit_replace (source_range::from_locations (c11, c15),
+			       "meadow");
+
+  edit_context edit;
+  edit.add_fixits (&insert_a);
+  ASSERT_EQ (100, edit.get_effective_column (filename, 1, 100));
+  ASSERT_EQ (1, edit.get_effective_column (filename, 2, 1));
+  ASSERT_EQ (6, edit.get_effective_column (filename, 2, 6));
+  ASSERT_EQ (18, edit.get_effective_column (filename, 2, 7));
+  ASSERT_EQ (27, edit.get_effective_column (filename, 2, 16));
+  ASSERT_EQ (100, edit.get_effective_column (filename, 3, 100));
+
+  edit.add_fixits (&insert_b);
+  edit.add_fixits (&replace_a);
+  edit.add_fixits (&replace_b);
+
+  if (c17 <= LINE_MAP_MAX_LOCATION_WITH_COLS)
+    {
+      ASSERT_STREQ ("/* before */\n"
+		     "foo = /* alpha */pub.meadow;/* beta */\n"
+		     "/* after */\n",
+		    edit.get_content (filename));
+
+      /* Verify diff.  */
+      char *diff = edit.generate_diff (false);
+      ASSERT_STREQ ("@@ -1,3 +1,3 @@\n"
+		    " /* before */\n"
+		    "-foo = bar.field;\n"
+		    "+foo = /* alpha */pub.meadow;/* beta */\n"
+		    " /* after */\n", diff);
+      free (diff);
+    }
+}
+
+/* Subroutine of test_applying_fixits_multiple_lines.
+   Add the text "CHANGED: " to the front of the given line.  */
+
+static void
+change_line (edit_context &edit, int line_num)
+{
+  const line_map_ordinary *ord_map
+    = LINEMAPS_LAST_ORDINARY_MAP (line_table);
+  const int column = 1;
+  location_t loc =
+    linemap_position_for_line_and_column (line_table, ord_map,
+					  line_num, column);
+
+  expanded_location exploc = expand_location (loc);
+  ASSERT_EQ (line_num, exploc.line);
+  ASSERT_EQ (column, exploc.column);
+
+  rich_location insert (line_table, loc);
+  insert.add_fixit_insert (loc, "CHANGED: ");
+  edit.add_fixits (&insert);
+}
+
+/* Test of editing multiple lines within a long file,
+   to ensure that diffs are generated as expected.  */
+
+static void
+test_applying_fixits_multiple_lines ()
+{
+  /* Create a tempfile and write many lines of text to it.  */
+  named_temp_file tmp (".txt");
+  const char *filename = tmp.get_filename ();
+  FILE *f = fopen (filename, "w");
+  ASSERT_NE (f, NULL);
+  for (int i = 1; i <= 1000; i++)
+    fprintf (f, "line %i\n", i);
+  fclose (f);
+
+  line_table_test ltt;
+  linemap_add (line_table, LC_ENTER, false, filename, 1);
+  linemap_position_for_column (line_table, 127);
+
+  edit_context edit;
+
+  /* A run of consecutive lines.  */
+  change_line (edit, 2);
+  change_line (edit, 3);
+  change_line (edit, 4);
+
+  /* A run of nearby lines, within the contextual limit.  */
+  change_line (edit, 150);
+  change_line (edit, 151);
+  change_line (edit, 153);
+
+  /* Verify diff.  */
+  char *diff = edit.generate_diff (false);
+  ASSERT_STREQ ("@@ -1,7 +1,7 @@\n"
+		" line 1\n"
+		"-line 2\n"
+		"-line 3\n"
+		"-line 4\n"
+		"+CHANGED: line 2\n"
+		"+CHANGED: line 3\n"
+		"+CHANGED: line 4\n"
+		" line 5\n"
+		" line 6\n"
+		" line 7\n"
+		"@@ -147,10 +147,10 @@\n"
+		" line 147\n"
+		" line 148\n"
+		" line 149\n"
+		"-line 150\n"
+		"-line 151\n"
+		"+CHANGED: line 150\n"
+		"+CHANGED: line 151\n"
+		" line 152\n"
+		"-line 153\n"
+		"+CHANGED: line 153\n"
+		" line 154\n"
+		" line 155\n"
+		" line 156\n", diff);
+  free (diff);
+
+  /* Ensure tmp stays alive until this point, so that the tempfile
+     persists until after the generate_diff call.  */
+  tmp.get_filename ();
+}
+
+/* Test of converting an initializer for a named field from
+   the old GCC extension to C99 syntax.
+   Exercises a shrinking replacement followed by a growing
+   replacement on the same line.  */
+
+static void
+test_applying_fixits_modernize_named_init ()
+{
+  /* Create a tempfile and write some text to it.
+     .....................00000000011111111.
+     .....................12345678901234567.  */
+  const char *content = ("/* before */\n"
+			 "bar    : 1,\n"
+			 "/* after */\n");
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt ();
+  linemap_add (line_table, LC_ENTER, false, filename, 2);
+
+  location_t c1 = linemap_position_for_column (line_table, 1);
+  location_t c3 = linemap_position_for_column (line_table, 3);
+  location_t c8 = linemap_position_for_column (line_table, 8);
+
+  /* Replace "bar" with ".".  */
+  rich_location r1 (line_table, c8);
+  r1.add_fixit_replace (source_range::from_locations (c1, c3),
+			".");
+
+  /* Replace ":" with "bar =".   */
+  rich_location r2 (line_table, c8);
+  r2.add_fixit_replace (source_range::from_locations (c8, c8),
+			"bar =");
+
+  /* The order should not matter.  Do r1 then r2. */
+  {
+    edit_context edit;
+    edit.add_fixits (&r1);
+
+    /* Verify state after first replacement.  */
+    {
+      /* We should now have:
+	 ............00000000011.
+	 ............12345678901.  */
+      ASSERT_STREQ ("/* before */\n"
+		    ".    : 1,\n"
+		    "/* after */\n",
+		    edit.get_content (filename));
+      /* Location of the "1".  */
+      ASSERT_EQ (6, edit.get_effective_column (filename, 2, 8));
+      /* Location of the ",".  */
+      ASSERT_EQ (9, edit.get_effective_column (filename, 2, 11));
+    }
+
+    edit.add_fixits (&r2);
+
+    /* Verify state after second replacement.
+       ............00000000011111111.
+       ............12345678901234567.  */
+    ASSERT_STREQ ("/* before */\n"
+		  ".    bar = 1,\n"
+		  "/* after */\n",
+		  edit.get_content (filename));
+  }
+
+  /* Try again, doing r2 then r1; the result should be the same.  */
+  {
+    edit_context edit;
+    edit.add_fixits (&r2);
+    edit.add_fixits (&r1);
+    /*.............00000000011111111.
+      .............12345678901234567.  */
+    ASSERT_STREQ ("/* before */\n"
+		  ".    bar = 1,\n"
+		  "/* after */\n",
+		  edit.get_content (filename));
+  }
+}
+
+/* Test of a fixit affecting a file that can't be read.  */
+
+static void
+test_applying_fixits_unreadable_file ()
+{
+  const char *filename = "this-does-not-exist.txt";
+  line_table_test ltt ();
+  linemap_add (line_table, LC_ENTER, false, filename, 1);
+
+  location_t loc = linemap_position_for_column (line_table, 1);
+
+  rich_location insert (line_table, loc);
+  insert.add_fixit_insert (loc, "change 1");
+  insert.add_fixit_insert (loc, "change 2");
+
+  edit_context edit;
+  /* Attempting to add the fixits affecting the unreadable file
+     should transition the edit from valid to invalid.  */
+  ASSERT_TRUE (edit.valid_p ());
+  edit.add_fixits (&insert);
+  ASSERT_FALSE (edit.valid_p ());
+  ASSERT_EQ (NULL, edit.get_content (filename));
+  ASSERT_EQ (NULL, edit.generate_diff (false));
+}
+
+/* Verify that we gracefully handle an attempt to edit a line
+   that's beyond the end of the file.  */
+
+static void
+test_applying_fixits_line_out_of_range ()
+{
+  /* Create a tempfile and write some text to it.
+     .....................00000000011111111.
+     .....................12345678901234567.  */
+  const char *content = "One-liner file\n";
+  temp_source_file tmp (SELFTEST_LOCATION, ".txt", content);
+  const char *filename = tmp.get_filename ();
+  line_table_test ltt ();
+  linemap_add (line_table, LC_ENTER, false, filename, 2);
+
+  /* Try to insert a string in line 2.  */
+  location_t loc = linemap_position_for_column (line_table, 1);
+
+  rich_location insert (line_table, loc);
+  insert.add_fixit_insert (loc, "change");
+
+  /* Verify that attempting the insertion puts an edit_context
+     into an invalid state.  */
+  edit_context edit;
+  ASSERT_TRUE (edit.valid_p ());
+  edit.add_fixits (&insert);
+  ASSERT_FALSE (edit.valid_p ());
+  ASSERT_EQ (NULL, edit.get_content (filename));
+  ASSERT_EQ (NULL, edit.generate_diff (false));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+edit_context_c_tests ()
+{
+  for_each_line_table_case (test_applying_fixits_insert);
+  for_each_line_table_case (test_applying_fixits_growing_replace);
+  for_each_line_table_case (test_applying_fixits_shrinking_replace);
+  for_each_line_table_case (test_applying_fixits_remove);
+  for_each_line_table_case (test_applying_fixits_multiple);
+  test_applying_fixits_multiple_lines ();
+  test_applying_fixits_modernize_named_init ();
+  test_applying_fixits_unreadable_file ();
+  test_applying_fixits_line_out_of_range ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/edit-context.h b/gcc/edit-context.h
new file mode 100644
index 0000000..54e576b
--- /dev/null
+++ b/gcc/edit-context.h
@@ -0,0 +1,65 @@
+/* Determining the results of applying fix-its.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_EDIT_CONTEXT_H
+#define GCC_EDIT_CONTEXT_H
+
+#include "typed-splay-tree.h"
+
+class edit_context;
+class edited_file;
+
+/* A set of changes to the source code.
+
+   The changes are "atomic" - if any changes can't be applied,
+   none of can be (tracked by the m_valid flag).
+
+   A complication here is that fix-its are expressed relative to coordinates
+   in the files when they were parsed, before any changes have been made, and
+   so if there's more that one fix-it to be applied, we have to adjust
+   later fix-its to allow for the changes made by earlier ones.  This
+   is done by the various "get_effective_column" methods.  */
+
+class edit_context
+{
+ public:
+  edit_context ();
+
+  bool valid_p () const { return m_valid; }
+
+  void add_fixits (rich_location *richloc);
+
+  const char *get_content (const char *filename);
+
+  int get_effective_column (const char *filename, int line, int column);
+
+  char *generate_diff (bool show_filenames);
+  void print_diff (pretty_printer *pp, bool show_filenames);
+
+ private:
+  bool apply_insert (const fixit_insert *insert);
+  bool apply_replace (const fixit_replace *replace);
+  edited_file *get_file (const char *filename);
+  edited_file &get_or_insert_file (const char *filename);
+
+  bool m_valid;
+  typed_splay_tree<const char *, edited_file *> m_files;
+};
+
+#endif /* GCC_EDIT_CONTEXT_H.  */
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index e20bbd0..d9d3ea1 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -68,6 +68,7 @@ selftest::run_tests ()
      rely on.  */
   diagnostic_show_locus_c_tests ();
   diagnostic_c_tests ();
+  edit_context_c_tests ();
   fold_const_c_tests ();
   spellcheck_c_tests ();
   spellcheck_tree_c_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 8e6c47a..41487af 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -150,6 +150,7 @@ for_each_line_table_case (void (*testcase) (const line_table_case &));
 extern void bitmap_c_tests ();
 extern void diagnostic_c_tests ();
 extern void diagnostic_show_locus_c_tests ();
+extern void edit_context_c_tests ();
 extern void et_forest_c_tests ();
 extern void fold_const_c_tests ();
 extern void fibonacci_heap_c_tests ();
-- 
1.8.5.3


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]