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) Coverage Total Hit
Test: PARI/GP v2.18.1 lcov report (development 31042-0fbe168e69) Lines: 0.0 % 186 0
Test Date: 2026-07-23 17:04:59 Functions: 0.0 % 17 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            0 :       && (is_keyword_char(line[start+1]) || line[start+1] == '/'))
     280              :   {
     281              :     char *(*f)(const char*, int);
     282            0 :     f = rl->username_completion_function;
     283            0 :     for(i=start+1;i<=END;i++)
     284            0 :       if (line[i] == '/') { f = rl->filename_completion_function; break; }
     285            0 :     return get_matches(rl, -1, text, f);
     286              :   }
     287            0 :   if (line[first] == '\\')
     288              :   {
     289            0 :     if (first == start) return add_space(rl, start);
     290            0 :     return get_matches(rl, -1, text, rl->filename_completion_function);
     291              :   }
     292              : 
     293            0 :   while (start && line[start] != '('
     294            0 :                && line[start] != ',') start--;
     295            0 :   if (line[start] == '(' && start)
     296              :   {
     297              :     int iend, j,k;
     298              :     entree *ep;
     299              :     char buf[200];
     300              : 
     301            0 :     i = start;
     302              : 
     303            0 :     while (i && isspace((unsigned char)line[i-1])) i--;
     304            0 :     iend = i;
     305            0 :     while (i && is_keyword_char(line[i-1])) i--;
     306              : 
     307            0 :     if (strncmp(line + i,"default",7) == 0)
     308            0 :       return get_matches(rl, -2, text, default_generator);
     309            0 :     if ( strncmp(line + i,"read",4)  == 0
     310            0 :       || strncmp(line + i,"write",5) == 0)
     311            0 :       return get_matches(rl, -1, text, rl->filename_completion_function);
     312              : 
     313            0 :     j = start + 1;
     314            0 :     while (j <= END && isspace((unsigned char)line[j])) j++;
     315            0 :     k = END;
     316            0 :     while (k > j && isspace((unsigned char)line[k])) k--;
     317              :     /* If we are in empty parens, insert the default arguments */
     318            0 :     if ((GP_DATA->readline_state & DO_ARGS_COMPLETE) && k == j
     319            0 :          && (line[j] == ')' || !line[j])
     320            0 :          && (iend - i < (long)sizeof(buf))
     321            0 :          && ( strncpy(buf, line + i, iend - i),
     322            0 :               buf[iend - i] = 0, 1)
     323            0 :          && (ep = is_entry(buf)) && ep->help)
     324              :     {
     325            0 :       const char *s = ep->help;
     326            0 :       while (is_keyword_char(*s)) s++;
     327            0 :       if (*s++ == '(')
     328              :       { /* function call: insert arguments */
     329            0 :         const char *e = s;
     330            0 :         while (*e && *e != ')' && *e != '(') e++;
     331            0 :         if (*e == ')')
     332              :         { /* we just skipped over the arguments in short help text */
     333            0 :           char *str = strncpy((char*)pari_malloc(e-s + 1), s, e-s);
     334            0 :           char **ret = (char**)pari_malloc(sizeof(char*)*2);
     335            0 :           str[e-s] = 0;
     336            0 :           ret[0] = str; ret[1] = NULL;
     337            0 :           if (GP_DATA->flags & gpd_EMACS) ret = matches_for_emacs("",ret);
     338            0 :           return ret;
     339              :         }
     340              :       }
     341              :     }
     342              :   }
     343            0 :   for(i = END-1; i >= start; i--)
     344            0 :     if (!is_keyword_char(line[i]))
     345              :     {
     346            0 :       if (line[i] == '.')
     347            0 :         return get_matches(rl, -1, text, member_generator);
     348            0 :       break;
     349              :     }
     350            0 :   return get_matches(rl, END, text, command_generator);
     351              : }
     352              : 
     353              : static char *
     354            0 : pari_completion_word(char *line, long end)
     355              : {
     356            0 :   char *s = line + end, *found_quote = NULL;
     357              :   long i;
     358            0 :   *s = 0; /* truncate at cursor position */
     359            0 :   for (i=0; i < end; i++)
     360              :   { /* first look for unclosed string */
     361            0 :     switch(line[i])
     362              :     {
     363            0 :       case '"':
     364            0 :         found_quote = found_quote? NULL: line + i;
     365            0 :         break;
     366            0 :       case '\\': i++; break;
     367              :     }
     368              :   }
     369            0 :   if (found_quote) return found_quote + 1; /* return next char after quote */
     370              :   /* else find beginning of word */
     371            0 :   while (s > line && is_keyword_char(s[-1])) s--;
     372            0 :   return s;
     373              : }
     374              : 
     375              : char **
     376            0 : pari_completion_matches(pari_rl_interface *rl, const char *s, long pos, long *wordpos)
     377              : {
     378              :   char *text, *b;
     379              :   long w;
     380              : 
     381            0 :   if (*rl->line_buffer) pari_free(*rl->line_buffer);
     382            0 :   *rl->line_buffer = b = pari_strdup(s);
     383            0 :   text = pari_completion_word(b, pos);
     384            0 :   w = text - b; if (wordpos) *wordpos = w;
     385              :   /* text = start of expression we complete */
     386            0 :   *rl->end = strlen(b)-1;
     387            0 :   *rl->point = pos;
     388            0 :   return pari_completion(rl, text, w, pos);
     389              : }
        

Generated by: LCOV version 2.0-1