From f4ace6d3ae58e169fd5e44439678fa7c3b1ed931 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 14 Jun 2017 12:24:03 -0400
Subject: [PATCH 30/44] FIXME: add word_breaker class (allocing selftests for
 word-breaking); fix handling of year ranges in copyright line

---
 gcc/spellcheck-enchant.c                           | 376 +++++++++++++++------
 .../c-c++-common/Wspellcheck-comments-3.c          |   6 +
 2 files changed, 277 insertions(+), 105 deletions(-)

diff --git a/gcc/spellcheck-enchant.c b/gcc/spellcheck-enchant.c
index 0812f37..1c63c90 100644
--- a/gcc/spellcheck-enchant.c
+++ b/gcc/spellcheck-enchant.c
@@ -247,7 +247,7 @@ make_word_location (const line_map_ordinary *ord_map, int line, int column,
    Issue warnings and suggestions for possibly misspelled words.  */
 
 static void
-spellcheck_word (const unsigned char *word, size_t len,
+spellcheck_word (const char *word, size_t len,
 		 const line_map_ordinary *ord_map, int line, int column)
 {
   /* Don't bother spellchecking words with underscores or any upper case,
@@ -269,10 +269,10 @@ spellcheck_word (const unsigned char *word, size_t len,
   /* Don't warn for anything that starts with "lib" (including "lib"
      itself).  */
   if (len >= 3)
-    if (0 == strncmp ((const char *)word, "lib", 3))
+    if (0 == strncmp (word, "lib", 3))
       return;
 
-  if (!checker::singleton->misspelled_p ((const char *)word, len))
+  if (!checker::singleton->misspelled_p (word, len))
     return;
 
   location_t word_loc = make_word_location (ord_map, line, column, len);
@@ -337,6 +337,185 @@ spellcheck_enchant_init (const char *language_tag)
     ("/home/david/coding-3/gcc-git-fixits/src/gcc/gcc-wordlist.txt");
 }
 
+/* FIXME.  */
+// both are 1-based.
+
+struct coord
+{
+  coord (int line_, int column_)
+  : line (line_), column (column_) {}
+
+  int line;
+  int column;
+};
+
+/* FIXME.  */
+
+class word_breaker
+{
+ public:
+  word_breaker (const char *content, size_t len, coord start_coord)
+  : m_content (content), m_len (len), m_cur_coord (start_coord),
+    m_word_start (-1, -1),
+    m_word_start_idx (-1),
+    m_ignore_until_next_whitespace (false)
+  {}
+
+  void handle_char (char ch, size_t idx);
+  void on_end_of_text (size_t idx);
+
+  void on_word_ending (char ch, size_t idx);
+
+  virtual void on_word (const char *word_start, size_t word_len) = 0;
+
+ private:
+  void update_coord (char ch);
+
+  const char *m_content;
+  size_t m_len;
+  coord m_cur_coord;
+ protected: // FIXME
+  coord m_word_start;
+ private:
+  int m_word_start_idx;
+  bool m_ignore_until_next_whitespace;
+};
+
+/* FIXME.  */
+
+class spellchecker : public word_breaker
+{
+ public:
+  spellchecker (const char *content, size_t len, coord start_coord,
+		const line_map_ordinary *ord_map)
+  : word_breaker (content, len, start_coord),
+    m_ord_map (ord_map)
+  {}
+
+  void on_word (const char *word_start, size_t word_len) FINAL OVERRIDE
+  {
+    spellcheck_word (word_start, word_len, m_ord_map,
+		     m_word_start.line, m_word_start.column);
+  }
+
+ private:
+  const line_map_ordinary *m_ord_map;
+};
+
+/* FIXME.  */
+
+void
+word_breaker::handle_char (char ch, size_t idx)
+{
+  /* If we see a '.' or '/' followed by non-whitespace, treat it as
+     a filename, and ignore it.  */
+  if (ch == '.' || ch == '/')
+    if (idx + 1 < m_len)
+      if (!ISSPACE (m_content[idx + 1]))
+	m_ignore_until_next_whitespace = true;
+
+  /* If we see a '@' followed by non-whitespace, treat it as
+     an email address, and ignore it.  */
+  if (ch == '@')
+    if (idx + 1 < m_len)
+      if (!ISSPACE (m_content[idx + 1]))
+	m_ignore_until_next_whitespace = true;
+
+  /* If we see a '-' after whitespace, treat it as a command-line
+     option, and ignore it.  */
+  if (ch == '-')
+    if (idx > 0)
+      if (ISSPACE (m_content[idx - 1]))
+	m_ignore_until_next_whitespace = true;
+
+  /* If we have ' after whitespace, or a ` , ", or < , treat it as the
+     start of a quoted word, and suppress spellchecking until further
+     whitespace.  */
+  if (ch == '`' || ch == '"' || ch == '<' )
+    m_ignore_until_next_whitespace = true;
+  if (ch == '\'')
+    if (idx > 0)
+      if (ISSPACE (m_content[idx - 1]))
+	m_ignore_until_next_whitespace = true;
+
+  /* Detect word breaks.  */
+  if (m_word_start_idx != -1)
+    {
+      /* Within a word; look for word endings.  */
+      if (!(ISALNUM (ch) || ch == '_' || ch == '\'' || ch == '-'))
+	on_word_ending (ch, idx);
+    }
+  else
+    {
+      /* Outside a word; detect start of a word.  */
+      if (ISALNUM (ch) || ch == '_' || ch == '-')
+	{
+	  /* We have the start of a word.  */
+	  m_word_start_idx = idx;
+	  m_word_start = m_cur_coord;
+	}
+    }
+
+  if (ISSPACE (ch))
+    m_ignore_until_next_whitespace = false;
+
+  update_coord (ch);
+}
+
+void
+word_breaker::on_end_of_text (size_t idx)
+{
+  if (m_word_start_idx != -1)
+    on_word_ending ('\0', idx);
+}
+
+void
+word_breaker::on_word_ending (char ch, size_t idx)
+{
+  /* We have a word ending.  */
+  size_t word_len = idx - m_word_start_idx;
+
+  /* Don't check any comments that appear to contain DejaGnu
+     directives.  */
+  if (word_len >= 2)
+    if (0 == strncmp ((const char *)(m_content + m_word_start_idx),
+		      "dg", 2))
+      return; // FIXME
+
+  /* Detect things that look like URLs.  */
+  /* "www."  */
+  if (word_len == 3 && ch == '.')
+    if (0 == strncmp ((const char *)(m_content + m_word_start_idx),
+		      "www", 3))
+      m_ignore_until_next_whitespace = true;
+
+  /* "http:"  */
+  if (word_len == 4 && ch == ':')
+    if (0 == strncmp ((const char *)(m_content + m_word_start_idx),
+		      "http", 4))
+      m_ignore_until_next_whitespace = true;
+  // FIXME: https
+  // FIXME: helper function
+
+  if (!m_ignore_until_next_whitespace)
+    on_word ((const char *)m_content + m_word_start_idx,
+	     word_len);
+
+  m_word_start_idx = -1;
+}
+
+void
+word_breaker::update_coord (char ch)
+{
+  if (ch == '\n')
+    {
+      m_cur_coord.column = 1;
+      m_cur_coord.line++;
+    }
+  else
+    m_cur_coord.column++;
+}
+
 /* Spellcheck comments within TOKEN using Enchant.  */
 
 void
@@ -368,116 +547,19 @@ spellcheck_enchant_check_comment (const cpp_token *token, location_t loc)
   if (c_style)
     len -= 2;
 
-  int word_start_idx = -1;
-
   const struct line_map *map = linemap_lookup (line_table, loc);
   const line_map_ordinary *ord_map = linemap_check_ordinary (map);
 
-  int column = LOCATION_COLUMN (loc) + 2;
-  int line = LOCATION_LINE (loc);
-  int word_start_column = column;
-  int word_start_line = line;
-
-  bool ignore_until_next_whitespace = true;
+  spellchecker checker ((const char *)content, len,
+			coord (LOCATION_LINE (loc), LOCATION_COLUMN (loc) + 2),
+			ord_map);
   size_t idx;
   for (idx = 2; idx < len; idx++)
     {
       unsigned char ch = content[idx];
-
-      /* If we see a '.' or '/' followed by non-whitespace, treat it as
-	 a filename, and ignore it.  */
-      if (ch == '.' || ch == '/')
-	if (idx + 1 < len)
-	  if (!ISSPACE (content[idx + 1]))
-	    ignore_until_next_whitespace = true;
-
-      /* If we see a '@' followed by non-whitespace, treat it as
-	 an email address, and ignore it.  */
-      if (ch == '@')
-	if (idx + 1 < len)
-	  if (!ISSPACE (content[idx + 1]))
-	    ignore_until_next_whitespace = true;
-
-      /* If we see a '-' after whitespace, treat it as a command-line
-	 option, and ignore it.  */
-      if (ch == '-')
-	if (idx > 0)
-	  if (ISSPACE (content[idx - 1]))
-	    ignore_until_next_whitespace = true;
-
-      /* If we have ', `, ", or < after whitespace, treat it as the
-	 start of a quoted word, and suppress spellchecking until further
-	 whitespace.  */
-      if (ch == '\'' || ch == '`' || ch == '"' || ch == '<' )
-	if (idx > 0)
-	  if (ISSPACE (content[idx - 1]))
-	    ignore_until_next_whitespace = true;
-
-      /* Detect word breaks.  */
-      if (word_start_idx != -1)
-	{
-	  /* Within a word; look for word endings.  */
-	  if (!(ISALPHA (ch) || ch == '_' || ch == '\''))
-	    {
-	      /* We have a word ending.  */
-	      size_t word_len = idx - word_start_idx;
-
-	      /* Don't check any comments that appear to contain DejaGnu
-		 directives.  */
-	      if (word_len >= 2)
-		if (0 == strncmp ((const char *)(content + word_start_idx),
-				  "dg", 2))
-		  return;
-
-	      /* Detect things that look like URLs.  */
-	      /* "www."  */
-	      if (word_len == 3 && ch == '.')
-		if (0 == strncmp ((const char *)(content + word_start_idx),
-				  "www", 3))
-		  ignore_until_next_whitespace = true;
-
-	      /* "http:"  */
-	      if (word_len == 4 && ch == ':')
-		if (0 == strncmp ((const char *)(content + word_start_idx),
-				  "http", 4))
-		  ignore_until_next_whitespace = true;
-	      // FIXME: https
-	      // FIXME: helper function
-
-	      if (!ignore_until_next_whitespace)
-		spellcheck_word (content + word_start_idx, word_len, ord_map,
-				 word_start_line, word_start_column);
-
-	      word_start_idx = -1;
-	    }
-	}
-      else
-	{
-	  /* Outside a word; detect start of a word.  */
-	  if (ISALNUM (ch) || ch == '_' || ch == '-')
-	    {
-	      /* We have the start of a word.  */
-	      word_start_idx = idx;
-	      word_start_column = column;
-	      word_start_line = line;
-	    }
-	}
-
-      if (ISSPACE (ch))
-	ignore_until_next_whitespace = false;
-
-      if (ch == '\n')
-	{
-	  column = 1;
-	  line++;
-	}
-      else
-	column++;
+      checker.handle_char (ch, idx);
     }
-  if (word_start_idx != -1)
-    if (!ignore_until_next_whitespace)
-      spellcheck_word (content + word_start_idx, idx - word_start_idx, ord_map,
-		       word_start_line, word_start_column);
+  checker.on_end_of_text (idx);
 }
 
 /* Clean up this module.  */
@@ -538,6 +620,89 @@ test_loading_word_list ()
   }
 }
 
+/* FIXME.  */
+
+class word_break_test : public word_breaker
+{
+ public:
+  word_break_test (const char *input)
+  : word_breaker (input, strlen (input), coord (1, 1))
+  {
+    int len = strlen (input);
+    int idx;
+    for (idx = 0; idx < len; idx++)
+      {
+	unsigned char ch = input[idx];
+	handle_char (ch, idx);
+      }
+    on_end_of_text (idx);
+  }
+
+  ~word_break_test ()
+  {
+    unsigned i;
+    char *word;
+    FOR_EACH_VEC_ELT (m_results, i, word)
+      free (word);
+  }
+
+  int get_num_words () const { return m_results.length (); }
+  const char *get_word (int idx) { return m_results[idx]; }
+
+  void on_word (const char *word_start, size_t word_len) FINAL OVERRIDE
+  {
+    m_results.safe_push (xstrndup (word_start, word_len));
+  }
+
+  auto_vec <char *> m_results;
+};
+
+/* FIXME.  */
+
+static void
+test_word_breaking ()
+{
+  {
+    word_break_test test ("foo  bar baz");
+    ASSERT_EQ (3, test.get_num_words ());
+    ASSERT_STREQ ("foo", test.get_word (0));
+    ASSERT_STREQ ("bar", test.get_word (1));
+    ASSERT_STREQ ("baz", test.get_word (2));
+  }
+
+  /* Verify handling of apostrophes.  */
+  {
+    word_break_test test ("it's they're that's");
+    ASSERT_EQ (3, test.get_num_words ());
+    ASSERT_STREQ ("it's", test.get_word (0));
+    ASSERT_STREQ ("they're", test.get_word (1));
+    ASSERT_STREQ ("that's", test.get_word (2));
+  }
+
+  /* Verify ordinals with leading digits.  */
+  {
+    word_break_test test ("1st 2nd 3rd");
+    ASSERT_EQ (3, test.get_num_words ());
+    ASSERT_STREQ ("1st", test.get_word (0));
+    ASSERT_STREQ ("2nd", test.get_word (1));
+    ASSERT_STREQ ("3rd", test.get_word (2));
+  }
+
+  /* Verify handling of a copyright line with a dash.  */
+  {
+    word_break_test test
+      ("Copyright (C) 1987-2017 Free Software Foundation, Inc.");
+    ASSERT_EQ (7, test.get_num_words ());
+    ASSERT_STREQ ("Copyright", test.get_word (0));
+    ASSERT_STREQ ("C", test.get_word (1));
+    ASSERT_STREQ ("1987-2017", test.get_word (2));
+    ASSERT_STREQ ("Free", test.get_word (3));
+    ASSERT_STREQ ("Software", test.get_word (4));
+    ASSERT_STREQ ("Foundation", test.get_word (5));
+    ASSERT_STREQ ("Inc", test.get_word (6));
+  }
+}
+
 /* Run all of the selftests within this file.  */
 
 void
@@ -545,6 +710,7 @@ spellcheck_enchant_c_tests ()
 {
   test_basic_spellchecking ();
   test_loading_word_list ();
+  test_word_breaking ();
 }
 
 } // namespace selftest
diff --git a/gcc/testsuite/c-c++-common/Wspellcheck-comments-3.c b/gcc/testsuite/c-c++-common/Wspellcheck-comments-3.c
index 385f84c..4d99938 100644
--- a/gcc/testsuite/c-c++-common/Wspellcheck-comments-3.c
+++ b/gcc/testsuite/c-c++-common/Wspellcheck-comments-3.c
@@ -2,6 +2,12 @@
 /* { dg-require-enchant "" } */
 /* { dg-options "-Wspellcheck-comments" } */
 
+/* Example of range of years.  */
+
+/* Copyright (C) 1987-2017 Free Software Foundation, Inc.  */
+
+/* Example of single year.  */
+
 /* Copyright (C) 2017 Free Software Foundation, Inc.
 
 This file is part of GCC.
-- 
1.8.5.3

