Code coverage tests

This page documents the degree to which the PARI/GP source code is tested by our public test suite, distributed with the source distribution in directory src/test/. This is measured by the gcov utility; we then process gcov output using the lcov frond-end.

We test a few variants depending on Configure flags on the pari.math.u-bordeaux.fr machine (x86_64 architecture), and agregate them in the final report:

The target is to exceed 90% coverage for all mathematical modules (given that branches depending on DEBUGLEVEL or DEBUGMEM are not covered). This script is run to produce the results below.

LCOV - code coverage report
Current view: top level - language - readline.c (source / functions) Hit Total Coverage
Test: PARI/GP v2.16.2 lcov report (development 29115-f22e516b23) Lines: 0 186 0.0 %
Date: 2024-04-16 08:06:29 Functions: 0 17 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* Copyright (C) 2000  The PARI group.
       2             : 
       3             : This file is part of the PARI/GP package.
       4             : 
       5             : PARI/GP is free software; you can redistribute it and/or modify it under the
       6             : terms of the GNU General Public License as published by the Free Software
       7             : Foundation; either version 2 of the License, or (at your option) any later
       8             : version. It is distributed in the hope that it will be useful, but WITHOUT
       9             : ANY WARRANTY WHATSOEVER.
      10             : 
      11             : Check the License for details. You should have received a copy of it, along
      12             : with the package; see the file 'COPYING'. If not, write to the Free Software
      13             : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
      14             : 
      15             : /*******************************************************************/
      16             : /*                                                                 */
      17             : /*                 INTERFACE TO READLINE COMPLETION                */
      18             : /*                                                                 */
      19             : /*******************************************************************/
      20             : #include "pari.h"
      21             : #include "paripriv.h"
      22             : 
      23             : /**************************************************************************/
      24             : static entree *current_ep = NULL;
      25             : 
      26             : /* do we add () at the end of completed word? (is it a function?) */
      27             : static int
      28           0 : add_paren(pari_rl_interface *rl, int end)
      29             : {
      30             :   entree *ep;
      31             :   const char *s;
      32             : 
      33           0 :   if (end < 0 || (*rl->line_buffer)[end] == '(')
      34           0 :     return 0; /* not from command_generator or already there */
      35           0 :   ep = do_alias(current_ep); /* current_ep set in command_generator */
      36           0 :   if (EpVALENCE(ep) < EpNEW)
      37             :   { /* is it a constant masked as a function (e.g Pi)? */
      38           0 :     s = ep->help; if (!s) return 1;
      39           0 :     while (is_keyword_char(*s)) s++;
      40           0 :     return (*s != '=');
      41             :   }
      42           0 :   switch(EpVALENCE(ep))
      43             :   {
      44           0 :     case EpVAR: return typ((GEN)ep->value) == t_CLOSURE;
      45           0 :     case EpINSTALL: return 1;
      46             :   }
      47           0 :   return 0;
      48             : }
      49             : 
      50             : static void
      51           0 : match_concat(char **matches, const char *s)
      52             : {
      53           0 :   pari_realloc_ip((void**)matches, strlen(matches[0])+strlen(s)+1);
      54           0 :   strcat(matches[0],s);
      55           0 : }
      56             : 
      57             : #define add_comma(x) (x==-2) /* from default_generator */
      58             : 
      59             : /* a single match, possibly modify matches[0] in place */
      60             : static void
      61           0 : treat_single(pari_rl_interface *rl, int code, char **matches)
      62             : {
      63           0 :   if (add_paren(rl, code))
      64             :   {
      65           0 :     match_concat(matches,"()");
      66           0 :     rl->back = 1;
      67           0 :     if (*rl->point == *rl->end)
      68           0 :     *rl->completion_append_character = '\0'; /* Do not append space. */
      69             :   }
      70           0 :   else if (add_comma(code))
      71           0 :     match_concat(matches,",");
      72           0 : }
      73             : #undef add_comma
      74             : 
      75             : static char **
      76           0 : matches_for_emacs(const char *text, char **matches)
      77             : {
      78           0 :   if (!matches) printf("@");
      79             :   else
      80             :   {
      81             :     int i;
      82           0 :     printf("%s@", matches[0] + strlen(text));
      83           0 :     if (matches[1]) print_fun_list(matches+1,0);
      84             : 
      85             :    /* we don't want readline to do anything, but insert some junk
      86             :     * which will be erased by emacs.
      87             :     */
      88           0 :     for (i=0; matches[i]; i++) pari_free(matches[i]);
      89           0 :     pari_free(matches);
      90             :   }
      91           0 :   matches = (char **) pari_malloc(2*sizeof(char *));
      92           0 :   matches[0] = (char*)pari_malloc(2); sprintf(matches[0],"_");
      93           0 :   matches[1] = NULL; printf("@E_N_D"); pari_flush();
      94           0 :   return matches;
      95             : }
      96             : 
      97             : /* Attempt to complete on the contents of TEXT. 'code' is used to
      98             :  * differentiate between callers when a single match is found.
      99             :  * Return the array of matches, NULL if there are none. */
     100             : static char **
     101           0 : get_matches(pari_rl_interface *rl, int code, const char *text, char *(*f)(const char*, int))
     102             : {
     103           0 :   char **matches = rl->completion_matches(text, f);
     104           0 :   if (matches && !matches[1]) treat_single(rl, code, matches);
     105           0 :   if (GP_DATA->flags & gpd_EMACS) matches = matches_for_emacs(text,matches);
     106           0 :   return matches;
     107             : }
     108             : 
     109             : static char *
     110           0 : add_prefix(const char *name, const char *text, long junk)
     111             : {
     112           0 :   char *s = strncpy((char*)pari_malloc(strlen(name)+1+junk),text,junk);
     113           0 :   strcpy(s+junk,name); return s;
     114             : }
     115             : static void
     116           0 : init_prefix(const char *text, int *len, int *junk, char **TEXT)
     117             : {
     118           0 :   long l = strlen(text), j = l-1;
     119           0 :   while (j >= 0 && is_keyword_char(text[j])) j--;
     120           0 :   if (j >= 7 && text[j] == '-' && !strncmp(text+(j-7),"refcard",7)) j -= 8;
     121           0 :   j++;
     122           0 :   *TEXT = (char*)text + j;
     123           0 :   *junk = j;
     124           0 :   *len  = l - j;
     125           0 : }
     126             : 
     127             : static int
     128           0 : is_internal(entree *ep) { return *ep->name == '_'; }
     129             : 
     130             : /* Generator function for command completion.  STATE lets us know whether
     131             :  * to start from scratch; without any state (i.e. STATE == 0), then we
     132             :  * start at the top of the list. */
     133             : static char *
     134           0 : hashtable_generator(const char *text, int state, entree **hash)
     135             : {
     136             :   static int hashpos, len, junk;
     137             :   static entree* ep;
     138             :   static char *TEXT;
     139             : 
     140             :  /* If this is a new word to complete, initialize now:
     141             :   *  + indexes hashpos (GP hash list) and n (keywords specific to long help).
     142             :   *  + file completion and keyword completion use different word boundaries,
     143             :   *    have TEXT point to the keyword start.
     144             :   *  + save the length of TEXT for efficiency.
     145             :   */
     146           0 :   if (!state)
     147             :   {
     148           0 :     hashpos = 0; ep = hash[hashpos];
     149           0 :     init_prefix(text, &len, &junk, &TEXT);
     150             :   }
     151             : 
     152             :   /* Return the next name which partially matches from the command list. */
     153             :   for(;;)
     154           0 :     if (!ep)
     155             :     {
     156           0 :       if (++hashpos >= functions_tblsz) return NULL; /* no names matched */
     157           0 :       ep = hash[hashpos];
     158             :     }
     159           0 :     else if (is_internal(ep) || strncmp(ep->name,TEXT,len))
     160           0 :       ep = ep->next;
     161             :     else
     162             :       break;
     163           0 :   current_ep = ep; ep = ep->next;
     164           0 :   return add_prefix(current_ep->name,text,junk);
     165             : }
     166             : /* Generator function for member completion.  STATE lets us know whether
     167             :  * to start from scratch; without any state (i.e. STATE == 0), then we
     168             :  * start at the top of the list. */
     169             : static char *
     170           0 : member_generator(const char *text, int state)
     171             : {
     172             :   static int hashpos, len, junk;
     173             :   static entree* ep;
     174             :   static char *TEXT;
     175           0 :   entree **hash=functions_hash;
     176             : 
     177             :  /* If this is a new word to complete, initialize now:
     178             :   *  + indexes hashpos (GP hash list) and n (keywords specific to long help).
     179             :   *  + file completion and keyword completion use different word boundaries,
     180             :   *    have TEXT point to the keyword start.
     181             :   *  + save the length of TEXT for efficiency.
     182             :   */
     183           0 :   if (!state)
     184             :   {
     185           0 :     hashpos = 0; ep = hash[hashpos];
     186           0 :     init_prefix(text, &len, &junk, &TEXT);
     187             :   }
     188             : 
     189             :   /* Return the next name which partially matches from the command list. */
     190             :   for(;;)
     191           0 :     if (!ep)
     192             :     {
     193           0 :       if (++hashpos >= functions_tblsz) return NULL; /* no names matched */
     194           0 :       ep = hash[hashpos];
     195             :     }
     196           0 :     else if (ep->name[0]=='_' && ep->name[1]=='.'
     197           0 :              && !strncmp(ep->name+2,TEXT,len))
     198             :         break;
     199             :     else
     200           0 :         ep = ep->next;
     201           0 :   current_ep = ep; ep = ep->next;
     202           0 :   return add_prefix(current_ep->name+2,text,junk);
     203             : }
     204             : static char *
     205           0 : command_generator(const char *text, int state)
     206           0 : { return hashtable_generator(text,state, functions_hash); }
     207             : static char *
     208           0 : default_generator(const char *text,int state)
     209           0 : { return hashtable_generator(text,state, defaults_hash); }
     210             : 
     211             : static char *
     212           0 : ext_help_generator(const char *text, int state)
     213             : {
     214             :   static int len, junk, n, def, key;
     215             :   static char *TEXT;
     216           0 :   if (!state) {
     217           0 :     n = 0;
     218           0 :     def = key = 1;
     219           0 :     init_prefix(text, &len, &junk, &TEXT);
     220             :   }
     221           0 :   if (def)
     222             :   {
     223           0 :     char *s = default_generator(TEXT, state);
     224           0 :     if (s) return add_prefix(s, text, junk);
     225           0 :     def = 0;
     226             :   }
     227           0 :   if (key)
     228             :   {
     229           0 :     const char **L = gphelp_keyword_list();
     230           0 :     for ( ; L[n]; n++)
     231           0 :       if (!strncmp(L[n],TEXT,len))
     232           0 :         return add_prefix(L[n++], text, junk);
     233           0 :     key = 0; state = 0;
     234             :   }
     235           0 :   return command_generator(text, state);
     236             : }
     237             : 
     238             : /* add a space between \<char> and following text. Attempting completion now
     239             :  * would delete char. Hitting <TAB> again will complete properly */
     240             : static char **
     241           0 : add_space(pari_rl_interface *rl, int start)
     242             : {
     243             :   char **m;
     244           0 :   int p = *rl->point + 1;
     245           0 :   *rl->point = start + 2;
     246           0 :   rl->insert(1, ' '); *rl->point = p;
     247             :   /*better: fake an empty completion, but don't append ' ' after it! */
     248           0 :   *rl->completion_append_character = '\0';
     249           0 :   m = (char**)pari_malloc(2 * sizeof(char*));
     250           0 :   m[0] = (char*)pari_malloc(1); *(m[0]) = 0;
     251           0 :   m[1] = NULL; return m;
     252             : }
     253             : 
     254             : char **
     255           0 : pari_completion(pari_rl_interface *rl, char *text, int START, int END)
     256             : {
     257           0 :   int i, first=0, start=START;
     258           0 :   char *line = *rl->line_buffer;
     259             : 
     260           0 :   *rl->completion_append_character = ' ';
     261           0 :   current_ep = NULL;
     262           0 :   while (line[first] && isspace((unsigned char)line[first])) first++;
     263           0 :   if (line[first] == '?')
     264             :   {
     265           0 :       if (line[first+1] == '?')
     266           0 :         return get_matches(rl, -1, text, ext_help_generator);
     267           0 :       return get_matches(rl, -1, text, command_generator);
     268             :   }
     269             : 
     270             : /* If the line does not begin by a backslash, then it is:
     271             :  * . an old command ( if preceded by "whatnow(" ).
     272             :  * . a default ( if preceded by "default(" ).
     273             :  * . a member function ( if preceded by "." + keyword_chars )
     274             :  * . a file name (in current directory) ( if preceded by 'read' or 'writexx' )
     275             :  * . a command */
     276           0 :   if (start >=1 && line[start] != '~') start--;
     277           0 :   while (start && is_keyword_char(line[start])) start--;
     278           0 :   if (line[start] == '~')
     279             :   {
     280             :     char *(*f)(const char*, int);
     281           0 :     f = rl->username_completion_function;
     282           0 :     for(i=start+1;i<=END;i++)
     283           0 :       if (line[i] == '/') { f = rl->filename_completion_function; break; }
     284           0 :     return get_matches(rl, -1, text, f);
     285             :   }
     286           0 :   if (line[first] == '\\')
     287             :   {
     288           0 :     if (first == start) return add_space(rl, start);
     289           0 :     return get_matches(rl, -1, text, rl->filename_completion_function);
     290             :   }
     291             : 
     292           0 :   while (start && line[start] != '('
     293           0 :                && line[start] != ',') start--;
     294           0 :   if (line[start] == '(' && start)
     295             :   {
     296             :     int iend, j,k;
     297             :     entree *ep;
     298             :     char buf[200];
     299             : 
     300           0 :     i = start;
     301             : 
     302           0 :     while (i && isspace((unsigned char)line[i-1])) i--;
     303           0 :     iend = i;
     304           0 :     while (i && is_keyword_char(line[i-1])) i--;
     305             : 
     306           0 :     if (strncmp(line + i,"default",7) == 0)
     307           0 :       return get_matches(rl, -2, text, default_generator);
     308           0 :     if ( strncmp(line + i,"read",4)  == 0
     309           0 :       || strncmp(line + i,"write",5) == 0)
     310           0 :       return get_matches(rl, -1, text, rl->filename_completion_function);
     311             : 
     312           0 :     j = start + 1;
     313           0 :     while (j <= END && isspace((unsigned char)line[j])) j++;
     314           0 :     k = END;
     315           0 :     while (k > j && isspace((unsigned char)line[k])) k--;
     316             :     /* If we are in empty parens, insert the default arguments */
     317           0 :     if ((GP_DATA->readline_state & DO_ARGS_COMPLETE) && k == j
     318           0 :          && (line[j] == ')' || !line[j])
     319           0 :          && (iend - i < (long)sizeof(buf))
     320           0 :          && ( strncpy(buf, line + i, iend - i),
     321           0 :               buf[iend - i] = 0, 1)
     322           0 :          && (ep = is_entry(buf)) && ep->help)
     323             :     {
     324           0 :       const char *s = ep->help;
     325           0 :       while (is_keyword_char(*s)) s++;
     326           0 :       if (*s++ == '(')
     327             :       { /* function call: insert arguments */
     328           0 :         const char *e = s;
     329           0 :         while (*e && *e != ')' && *e != '(') e++;
     330           0 :         if (*e == ')')
     331             :         { /* we just skipped over the arguments in short help text */
     332           0 :           char *str = strncpy((char*)pari_malloc(e-s + 1), s, e-s);
     333           0 :           char **ret = (char**)pari_malloc(sizeof(char*)*2);
     334           0 :           str[e-s] = 0;
     335           0 :           ret[0] = str; ret[1] = NULL;
     336           0 :           if (GP_DATA->flags & gpd_EMACS) ret = matches_for_emacs("",ret);
     337           0 :           return ret;
     338             :         }
     339             :       }
     340             :     }
     341             :   }
     342           0 :   for(i = END-1; i >= start; i--)
     343           0 :     if (!is_keyword_char(line[i]))
     344             :     {
     345           0 :       if (line[i] == '.')
     346           0 :         return get_matches(rl, -1, text, member_generator);
     347           0 :       break;
     348             :     }
     349           0 :   return get_matches(rl, END, text, command_generator);
     350             : }
     351             : 
     352             : static char *
     353           0 : pari_completion_word(char *line, long end)
     354             : {
     355           0 :   char *s = line + end, *found_quote = NULL;
     356             :   long i;
     357           0 :   *s = 0; /* truncate at cursor position */
     358           0 :   for (i=0; i < end; i++)
     359             :   { /* first look for unclosed string */
     360           0 :     switch(line[i])
     361             :     {
     362           0 :       case '"':
     363           0 :         found_quote = found_quote? NULL: line + i;
     364           0 :         break;
     365           0 :       case '\\': i++; break;
     366             :     }
     367           0 :   }
     368           0 :   if (found_quote) return found_quote + 1; /* return next char after quote */
     369             :   /* else find beginning of word */
     370           0 :   while (s > line && is_keyword_char(s[-1])) s--;
     371           0 :   return s;
     372             : }
     373             : 
     374             : char **
     375           0 : pari_completion_matches(pari_rl_interface *rl, const char *s, long pos, long *wordpos)
     376             : {
     377             :   char *text, *b;
     378             :   long w;
     379             : 
     380           0 :   if (*rl->line_buffer) pari_free(*rl->line_buffer);
     381           0 :   *rl->line_buffer = b = pari_strdup(s);
     382           0 :   text = pari_completion_word(b, pos);
     383           0 :   w = text - b; if (wordpos) *wordpos = w;
     384             :   /* text = start of expression we complete */
     385           0 :   *rl->end = strlen(b)-1;
     386           0 :   *rl->point = pos;
     387           0 :   return pari_completion(rl, text, w, pos);
     388             : }

Generated by: LCOV version 1.14