Subject: bug#47408: [PATCH] Etags support for Mercury -- fix explicit tags for existentially-quantified procedures

From: fabrice nicol <[email-masked]>
Date: Thu, 17 Jun 2021 20:36:56 +0200

Hi, Eli,

I could finalize my tests against the entire Mercury library code.

All is OK. I applied your patch on top of mine.

Also, I added two new corner-case fixes, which are mentioned in the 
commit message:

1. The first new fix is for 0-arity predicates and functions. Yes, 
Mercury has them. (They play the role of global immutable constants in 
other languages).

They happened not to be caught by the previous code, now they are.

2. I also removed module names from within tag names. The point is that 
module name prefixing is optional in most cases, so if you leave the 
module prefix within the tag, you will fail to get to the declaration 
when striking M-. on a (non-prefixed) predicate name. It is better to 
remove the name altogether. This will automatically trigger an explicit tag.

Fabrice

>> This is intended. I commented this in the commit message (one-word declarations).
> Understood, thanks.
>
>>      To fix this second issue, I propose the change below, which should
>>      be applied on top of your patches:
>>
>>   diff --git a/lib-src/etags.c b/lib-src/etags.c
>>   index 370e825..2b0288e 100644
>>   --- a/lib-src/etags.c
>>   +++ b/lib-src/etags.c
>>   @@ -6585,10 +6585,8 @@ mercury_pr (char *s, char *last, ptrdiff_t lastlen)
>>                && c_isspace (s[pos - namelength - offset]))
>>           --offset;
>>
>>   -      /* There is no need to correct namelength or call notinname.  */
>>   -      s[pos - offset - 1] = '\0';
>>   -
>>   -      make_tag (s + pos - namelength - offset, namelength, true, s, pos, lineno, linecharno);
>>   +      make_tag (s + pos - namelength - offset, namelength - 1, true,
>>   +               s, pos - offset - 1, lineno, linecharno);
>>          return pos;
>>        }
>>
>>   I've verified that etags after this change still produces the correct
>>   TAGS file, including for the file univ.m you sent up-thread.
>>
>>   Do you agree with the changes I propose?  If not, could you please
>>   explain what I miss here?
>>
>> OK, this is another way of achieving an equivalent result. Please leave me until tomorrow to perform more
>> tests so that I can formally confirm that this is fine.
> Thanks.
>
> I also plan on adding a few lines from univ.m to accumulator.m,
> because those few lines use a feature accumulator.m doesn't.  Is this
> OK with you?
>From b4db1894e71b7aaa0be28b604a814f58bdabeef9 Mon Sep 17 00:00:00 2001
From: Fabrice Nicol <[email-masked]>
Date: Thu, 17 Jun 2021 19:59:52 +0200
Subject: [PATCH] Fix Mercury support, notably quantified procedures.

    Correct the previous fix (did not correctly handle quantified types).
    Also fix the following issues:
    - remove module name (+ dot) from tags, as prefixing module name is
      often inconsistent in code and may cause tags to be too specific.
    - now tag 0-arity predicates and functions (':- func foo_14.')
    - now tag one-word declarations (':- interface.')

    * lib-src/etags.c (mercury_pr): Pass the correct NAME and NAMELEN
    arguments to 'make_tag'.
---
 lib-src/etags.c | 126 +++++++++++++++++++++++++++++++-----------------
 1 file changed, 83 insertions(+), 43 deletions(-)

diff --git a/lib-src/etags.c b/lib-src/etags.c
index 9f20e44caf..bd57ede2f3 100644
--- a/lib-src/etags.c
+++ b/lib-src/etags.c
@@ -6081,10 +6081,10 @@ prolog_atom (char *s, size_t pos)
 	      pos++;
 	      if (s[pos] != '\'')
 		break;
-	      pos++;		/* A double quote */
+	      pos++;		/* A double quote  */
 	    }
 	  else if (s[pos] == '\0')
-	    /* Multiline quoted atoms are ignored. */
+	    /* Multiline quoted atoms are ignored.  */
 	    return 0;
 	  else if (s[pos] == '\\')
 	    {
@@ -6119,6 +6119,13 @@ prolog_atom (char *s, size_t pos)
 static bool is_mercury_type = false;
 static bool is_mercury_quantifier = false;
 static bool is_mercury_declaration = false;
+typedef struct
+{
+  size_t pos;          /* Position reached in parsing tag name.  */
+  size_t namelength;   /* Length of tag name  */
+  size_t totlength;    /* Total length of parsed tag: this field is currently
+			  reserved for control and debugging.   */
+} mercury_pos_t;
 
 /*
  * Objective-C and Mercury have identical file extension .m.
@@ -6374,10 +6381,12 @@ mercury_skip_comment (linebuffer *plb, FILE *inf)
   "initialise", "finalise", "mutable", "module", "interface", "implementation",
   "import_module", "use_module", "include_module", "end_module", "some", "all"};
 
-static size_t
+static mercury_pos_t
 mercury_decl (char *s, size_t pos)
 {
-  if (s == NULL) return 0;
+  mercury_pos_t null_pos = {0, 0, 0};
+
+  if (s == NULL) return null_pos;
 
   size_t origpos;
   origpos = pos;
@@ -6398,7 +6407,8 @@ mercury_decl (char *s, size_t pos)
   if (is_mercury_quantifier)
     {
       if (strcmp (buf, "pred") != 0 && strcmp (buf, "func") != 0) /* Bad syntax.  */
-	return 0;
+	return null_pos;
+
       is_mercury_quantifier = false; /* Reset to base value.  */
       found_decl_tag = true;
     }
@@ -6418,7 +6428,7 @@ mercury_decl (char *s, size_t pos)
 		  is_mercury_quantifier = true;
 		}
 
-	      break;  /* Found declaration tag of rank j. */
+	      break;  /* Found declaration tag of rank j.  */
 	    }
 	  else
 	    /* 'solver type' has a blank in the middle,
@@ -6461,24 +6471,36 @@ mercury_decl (char *s, size_t pos)
       if (found_decl_tag)
 	pos = skip_spaces (s + pos) - s; /* Skip len blanks again.  */
       else
-	return 0;
+	return null_pos;
     }
 
   /* From now on it is the same as for Prolog except for module dots.  */
 
+  size_t start_of_name = pos;
+
   if (c_islower (s[pos]) || s[pos] == '_' )
     {
       /* The name is unquoted.
          Do not confuse module dots with end-of-declaration dots.  */
+      int module_dot_pos = 0;
 
       while (c_isalnum (s[pos])
              || s[pos] == '_'
              || (s[pos] == '.' /* A module dot.  */
                  && s + pos + 1 != NULL
-                 && (c_isalnum (s[pos + 1]) || s[pos + 1] == '_')))
+                 && (c_isalnum (s[pos + 1]) || s[pos + 1] == '_')
+		 && (module_dot_pos = pos)))  /* Record module dot position.
+				                 Erase module from name.  */
 	++pos;
 
-      return pos - origpos;
+      if (module_dot_pos)
+	{
+	  start_of_name = module_dot_pos + 2;
+	  ++pos;
+        }
+
+      mercury_pos_t position = {pos, pos - start_of_name + 1, pos - origpos};
+      return position;
     }
   else if (s[pos] == '\'')
     {
@@ -6493,28 +6515,37 @@ mercury_decl (char *s, size_t pos)
 	      ++pos; /* A double quote.  */
 	    }
 	  else if (s[pos] == '\0')  /* Multiline quoted atoms are ignored.  */
-	    return 0;
+	    return null_pos;
 	  else if (s[pos] == '\\')
 	    {
 	      if (s[pos+1] == '\0')
-		return 0;
+		return null_pos;
 	      pos += 2;
 	    }
 	  else
 	    ++pos;
 	}
-      return pos - origpos;
+
+      mercury_pos_t position = {pos, pos - start_of_name + 1, pos - origpos};
+      return position;
     }
   else if (is_mercury_quantifier && s[pos] == '[')   /* :- some [T] pred/func.  */
     {
       for (++pos; s + pos != NULL && s[pos] != ']'; ++pos) {}
-      if (s + pos == NULL) return 0;
+      if (s + pos == NULL) return null_pos;
       ++pos;
       pos = skip_spaces (s + pos) - s;
-      return mercury_decl (s, pos) + pos - origpos;
+      mercury_pos_t position = mercury_decl (s, pos);
+      position.totlength += pos - origpos;
+      return position;
+    }
+  else if (s[pos] == '.')  /* as in ':- interface.'  */
+    {
+      mercury_pos_t position = {pos, pos - origpos + 1, pos - origpos};
+      return position;
     }
   else
-    return 0;
+    return null_pos;
 }
 
 static ptrdiff_t
@@ -6523,6 +6554,7 @@ mercury_pr (char *s, char *last, ptrdiff_t lastlen)
   size_t len0 = 0;
   is_mercury_type = false;
   is_mercury_quantifier = false;
+  bool stop_at_rule = false;
 
   if (is_mercury_declaration)
     {
@@ -6530,38 +6562,46 @@ mercury_pr (char *s, char *last, ptrdiff_t lastlen)
       len0 = skip_spaces (s + 2) - s;
     }
 
-  size_t len = mercury_decl (s, len0);
-  if (len == 0) return 0;
-  len += len0;
-
-  if (( (s[len] == '.'  /* This is a statement dot, not a module dot.  */
-	 || (s[len] == '(' && (len += 1))
-         || (s[len] == ':'  /* Stopping in case of a rule.  */
-	     && s[len + 1] == '-'
-	     && (len += 2)))
-	&& (lastlen != len || memcmp (s, last, len) != 0)
+  mercury_pos_t position = mercury_decl (s, len0);
+  size_t pos = position.pos;
+  int offset = 0;  /* may be < 0  */
+  if (pos == 0) return 0;
+
+  /* Skip white space for:
+     a. rules in definitions before :-
+     b. 0-arity predicates with inlined modes.
+     c. possibly multiline type definitions  */
+
+  while (c_isspace (s[pos])) { ++pos; ++offset; }
+
+  if (( ((s[pos] == '.' && (pos += 1))     /* case 1
+                                              This is a statement dot,
+                                              not a module dot. */
+	 || c_isalnum(s[pos])              /* 0-arity procedures  */
+	 || (s[pos] == '(' && (pos += 1))  /* case 2: arity > 0   */
+	 || ((s[pos] == ':')               /* case 3: rules  */
+	     && s[pos + 1] == '-' && (stop_at_rule = true)))
+     && (lastlen != pos || memcmp (s, last, pos) != 0)
 	)
       /* Types are often declared on several lines so keeping just
 	 the first line.  */
-      || is_mercury_type)
+
+      || is_mercury_type)  /* When types are implemented.  */
     {
-      char *name = skip_non_spaces (s + len0);
-      size_t namelen;
-      if (name >= s + len)
-	{
-	  name = s;
-	  namelen = len;
-	}
-      else
-	{
-	  name = skip_spaces (name);
-	  namelen = len - (name - s);
-	}
-      /* Remove trailing non-name characters.  */
-      while (namelen > 0 && notinname (name[namelen - 1]))
-	namelen--;
-      make_tag (name, namelen, true, s, len, lineno, linecharno);
-      return len;
+      size_t namelength = position.namelength;
+      if (stop_at_rule && offset) --offset;
+
+      /* Left-trim type definitions.  */
+
+      while (pos > namelength + offset
+	     && c_isspace (s[pos - namelength - offset]))
+	--offset;
+
+      /* There is no need to correct namelength or call notinname.  */
+
+      make_tag (s + pos - namelength - offset, namelength - 1, true,
+				s, pos - offset - 1, lineno, linecharno);
+      return pos;
     }
 
   return 0;
-- 
2.32.0