1 /*
2 **      cdecl -- C gibberish translator
3 **      src/autocomplete.c
4 **
5 **      Copyright (C) 2017-2021  Paul J. Lucas, et al.
6 **
7 **      This program is free software: you can redistribute it and/or modify
8 **      it under the terms of the GNU General Public License as published by
9 **      the Free Software Foundation, either version 3 of the License, or
10 **      (at your option) any later version.
11 **
12 **      This program is distributed in the hope that it will be useful,
13 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
14 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 **      GNU General Public License for more details.
16 **
17 **      You should have received a copy of the GNU General Public License
18 **      along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 /**
22  * @file
23  * Defines functions for implementing command-line autocompletion.
24  */
25 
26 // local
27 #include "pjl_config.h"                 /* must go first */
28 #include "cdecl.h"
29 #include "c_keyword.h"
30 #include "c_lang.h"
31 #include "literals.h"
32 #include "options.h"
33 #include "set_options.h"
34 #include "util.h"
35 
36 /// @cond DOXYGEN_IGNORE
37 
38 // standard
39 #include <assert.h>
40 #include <stdbool.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <readline/readline.h>          /* must go last */
44 
45 #if !HAVE_DECL_RL_COMPLETION_MATCHES
46 # define rl_completion_matches    completion_matches
47 #endif /* !HAVE_DECL_RL_COMPLETION_MATCHES */
48 
49 /// @endcond
50 
51 ///////////////////////////////////////////////////////////////////////////////
52 
53 /**
54  * Subset of cdecl keywords (that are not cdecl commands, nor `help` nor `set`
55  * command arguments, nor C/C++ keywords) that are auto-completable.
56  *
57  * @note For two-word terms, only the second word is here: the first shouldn't
58  * be.
59  * For example, for the two-word term `maybe unused`, only `unused` is here and
60  * `maybe` isn't.  Having `maybe` here would only create ambiguity between
61  * `maybe` and `maybe_unused` and completing `maybe_unused` better.
62  *
63  * @sa CDECL_COMMANDS
64  * @sa CDECL_KEYWORDS
65  * @sa C_KEYWORDS
66  */
67 static c_lang_lit_t const AC_CDECL_KEYWORDS[] = {
68   { LANG_CPP_MIN(20),       L_ADDRESS         },
69   { LANG_C_CPP_MIN(11,11),  L_ALIGN           },
70   { LANG_ANY,               L_APPLE_BLOCK     },
71   { LANG_ANY,               L_ARRAY           },
72   { LANG_C_MIN(11),         L_ATOMIC          },
73   { LANG_ANY,               L_BITS            },
74   { LANG_C_CPP_MIN(11,11),  L_BYTES           },
75   { LANG_ANY,               L_CAST            },
76   { LANG_C_MIN(99),         L_COMPLEX         },
77   { LANG_CPP_ANY,           L_CONSTRUCTOR     },
78   { LANG_CPP_ANY,           L_CONVERSION      },
79   { LANG_CPP_MIN(11),       L_DEPENDENCY      },
80   { LANG_CPP_ANY,           L_DESTRUCTOR      },
81   { LANG_CPP_MIN(17),       L_DISCARD         },
82   { LANG_CPP_MIN(20),       L_EVALUATION      },
83   { LANG_CPP_MIN(11),       L_EXCEPT          },
84   { LANG_CPP_MIN(11),       L_EXPRESSION      },
85   { LANG_ANY,               L_FUNCTION        },
86   { LANG_C_MIN(99),         L_IMAGINARY       },
87   { LANG_CPP_MIN(20),       L_INITIALIZATION  },
88   { LANG_C_MIN(99),         L_LENGTH          },
89   { LANG_CPP_ANY,           L_LINKAGE         },
90   { LANG_CPP_MIN(11),       L_LITERAL         },
91   { LANG_C_CPP_MIN(11,11),  L_LOCAL           },
92   { LANG_CPP_ANY,           L_MEMBER          },
93   { LANG_CPP_ANY,           H_NON_MEMBER      },
94   { LANG_ANY,               L_POINTER         },
95   { LANG_ANY,               L_PREDEFINED      },
96   { LANG_CPP_ANY,           L_PURE            },
97   { LANG_CPP_ANY,           L_REFERENCE       },
98   { LANG_ANY,               L_RETURNING       },
99   { LANG_CPP_MIN(11),       L_RVALUE          },
100   { LANG_CPP_ANY,           L_SCOPE           },
101   { LANG_CPP_MIN(20),       L_UNIQUE          },
102   { LANG_C_CPP_MIN(2X,17),  L_UNUSED          },
103   { LANG_CPP_ANY,           H_USER_DEFINED    },
104   { LANG_C_MIN(99),         L_VARIABLE        },
105   { LANG_ANY,               L_VARIADIC        },
106   { LANG_ANY,               L_VECTOR          },
107   { LANG_ANY,               L_WIDTH           },
108 
109   { LANG_NONE,              NULL              }
110 };
111 
112 // local functions
113 static char*  command_generator( char const*, int );
114 static char*  keyword_generator( char const*, int );
115 
116 PJL_WARN_UNUSED_RESULT
117 static bool   is_command( char const* );
118 
119 ////////// local functions ////////////////////////////////////////////////////
120 
121 /**
122  * Creates and initializes an array of all auto-completable keywords composed
123  * of C/C++ keywords and cdecl keywords.
124  *
125  * @return Returns a pointer to said array.
126  */
127 PJL_WARN_UNUSED_RESULT
init_cdecl_keywords(void)128 static c_lang_lit_t const* init_cdecl_keywords( void ) {
129   size_t cdecl_keywords_size = ARRAY_SIZE( AC_CDECL_KEYWORDS );
130 
131   // pre-flight to calculate array size
132   FOREACH_C_KEYWORD( k ) {
133     if ( k->ac_lang_ids != LANG_NONE )
134       ++cdecl_keywords_size;
135   } // for
136 
137   c_lang_lit_t *const cdecl_keywords =
138     free_later( MALLOC( c_lang_lit_t, cdecl_keywords_size ) );
139   c_lang_lit_t *p = cdecl_keywords;
140 
141   for ( c_lang_lit_t const *ll = AC_CDECL_KEYWORDS; ll->literal != NULL; ++ll )
142     *p++ = *ll;
143 
144   FOREACH_C_KEYWORD( k ) {
145     if ( k->ac_lang_ids != LANG_NONE ) {
146       p->lang_ids = k->ac_lang_ids;
147       p->literal  = k->literal;
148       ++p;
149     }
150   } // for
151 
152   MEM_ZERO( p );
153 
154   return cdecl_keywords;
155 }
156 
157 /**
158  * Creates and initializes an array of all `set` option strings to be used for
159  * autocompletion for the `set` command.
160  *
161  * @return Returns a pointer to said array.
162  */
163 PJL_WARN_UNUSED_RESULT
init_set_options(void)164 static char const* const* init_set_options( void ) {
165   size_t set_options_size =
166       1                                 // for "options"
167     + 1;                                // for terminating NULL pointer
168 
169   // pre-flight to calculate array size
170   FOREACH_SET_OPTION( opt ) {
171     set_options_size += 1
172       + STATIC_CAST( size_t, opt->type == SET_OPT_TOGGLE /* "no" */);
173   } // for
174   FOREACH_LANG( lang ) {
175     if ( !lang->is_alias )
176       ++set_options_size;
177   } // for
178 
179   char **const set_options = free_later( MALLOC( char*, set_options_size ) );
180   char **p = set_options;
181 
182   *p++ = CONST_CAST( char*, L_OPTIONS );
183 
184   FOREACH_SET_OPTION( opt ) {
185     switch ( opt->type ) {
186       case SET_OPT_AFF_ONLY:
187         *p++ = CONST_CAST( char*, opt->name );
188         break;
189 
190       case SET_OPT_TOGGLE:
191         *p++ = CONST_CAST( char*, opt->name );
192         PJL_FALLTHROUGH;
193 
194       case SET_OPT_NEG_ONLY:
195         *p = free_later(
196           MALLOC( char, 2/*no*/ + strlen( opt->name ) + 1/*\0*/ )
197         );
198         strcpy( *p + 0, "no" );
199         strcpy( *p + 2, opt->name );
200         ++p;
201         break;
202     } // switch
203   } // for
204   FOREACH_LANG( lang ) {
205     if ( !lang->is_alias )
206       *p++ = free_later( check_strdup_tolower( lang->name ) );
207   } // for
208 
209   *p = NULL;
210 
211   return (char const*const*)set_options;
212 }
213 
214 /**
215  * Checks whether the current line being read is a cast command.  In C, this
216  * can only be `cast`; in C++, this can also be `const`, `dynamic`, `static`,
217  * or `reinterpret`.
218  *
219  * @return Returns `true` only if it's a cast command.
220  *
221  * @sa is_command()
222  */
223 PJL_WARN_UNUSED_RESULT
is_cast_command(void)224 static bool is_cast_command( void ) {
225   if ( is_command( L_CAST ) )
226     return true;
227   if ( OPT_LANG_IS(C_ANY) )
228     return false;
229   return  is_command( L_CONST       ) ||
230           is_command( L_DYNAMIC     ) ||
231           is_command( L_STATIC      ) ||
232           is_command( L_REINTERPRET );
233 }
234 
235 /**
236  * Checks whether the current line being read is a particular cdecl command.
237  *
238  * @param command The command to check for.
239  * @return Returns `true` only if it is.
240  *
241  * @sa is_cast_command()
242  */
243 PJL_WARN_UNUSED_RESULT
is_command(char const * command)244 static bool is_command( char const *command ) {
245   assert( command != NULL );
246   size_t const command_len = strlen( command );
247   if ( command_len > STATIC_CAST( size_t, rl_end ) )
248     return false;                       // more chars than in rl_line_buffer?
249   return strncmp( rl_line_buffer, command, command_len ) == 0;
250 }
251 
252 ////////// readline callback functions ////////////////////////////////////////
253 
254 /**
255  * Attempts command completion for readline().
256  *
257  * @param text The text read (so far) to match against.
258  * @param start The starting character position of \a text.
259  * @param end The ending character position of \a text.
260  * @return Returns an array of C strings of possible matches.
261  */
cdecl_rl_completion(char const * text,int start,int end)262 static char** cdecl_rl_completion( char const *text, int start, int end ) {
263   assert( text != NULL );
264   (void)end;
265 
266   rl_attempted_completion_over = 1;     // don't do filename completion
267 
268   //
269   // If the word is at the start of the line (start == 0), attempt to complete
270   // only cdecl commands and not all keywords.  Having two generator functions
271   // makes the logic simpler in each.
272   //
273   return rl_completion_matches(
274     text, start == 0 ? command_generator : keyword_generator
275   );
276 }
277 
278 /**
279  * Attempts to match a cdecl command.
280  *
281  * @param text The text read (so far) to match against.
282  * @param state If 0, restart matching from the beginning; if non-zero,
283  * continue to next match, if any.
284  * @return Returns a copy of the command or NULL if none.
285  */
command_generator(char const * text,int state)286 static char* command_generator( char const *text, int state ) {
287   assert( text != NULL );
288 
289   static size_t match_index;
290   static size_t text_len;
291 
292   if ( state == 0 ) {                   // new word? reset
293     match_index = 0;
294     text_len = strlen( text );
295   }
296 
297   for ( cdecl_command_t const *c;
298         (c = CDECL_COMMANDS + match_index)->literal != NULL; ) {
299     ++match_index;
300     if ( !opt_lang_is_any( c->lang_ids ) )
301       continue;
302     if ( strncmp( text, c->literal, text_len ) == 0 )
303       return check_strdup( c->literal );
304   } // for
305 
306   return NULL;
307 }
308 
309 /**
310  * Attempts to match a cdecl keyword (that is not a command).
311  *
312  * @param text The text read (so far) to match against.
313  * @param state If 0, restart matching from the beginning; if non-zero,
314  * continue to next match, if any.
315  * @return Returns a copy of the keyword or NULL if none.
316  */
keyword_generator(char const * text,int state)317 static char* keyword_generator( char const *text, int state ) {
318   assert( text != NULL );
319 
320   static char const  *command;          // current command
321   static size_t       match_index;
322   static size_t       text_len;
323 
324   if ( state == 0 ) {                   // new word? reset
325     match_index = 0;
326     text_len = strlen( text );
327 
328     //
329     // Retroactively figure out what the current command is so we can do some
330     // command-sensitive autocompletion.  We can't just set the command in
331     // command_generator() since it may never be called: the user could type an
332     // entire command, then hit <tab> sometime later, e.g.:
333     //
334     //      cdecl> set <tab>
335     //
336     if ( is_command( "?" ) )
337       command = L_HELP;
338     else if ( is_cast_command() )
339       command = L_CAST;
340     else {
341       command = NULL;
342       FOREACH_CDECL_COMMAND( c ) {
343         if ( opt_lang_is_any( c->lang_ids ) && is_command( c->literal ) ) {
344           command = c->literal;
345           break;
346         }
347       } // for
348     }
349   }
350 
351   if ( command == NULL ) {
352     //
353     // We haven't at least matched a command yet, so don't match any other
354     // keywords.
355     //
356     return NULL;
357   }
358 
359   //
360   // Special case: if it's the "cast" command, the text partially matches
361   // "into", and the user hasn't typed "into" yet, complete as "into".
362   //
363   if ( command == L_CAST &&
364        strncmp( text, L_INTO, text_len ) == 0 &&
365        strstr( rl_line_buffer, L_INTO ) == NULL ) {
366     command = NULL;                     // unambiguously match "into"
367     return check_strdup( L_INTO );
368   }
369 
370   static char const *const *command_keywords;
371 
372   if ( state == 0 ) {
373     //
374     // Special case: for certain commands, complete using specific keywords for
375     // that command.
376     //
377     if ( command == L_HELP ) {
378       static char const *const help_keywords[] = {
379         L_COMMANDS,
380         L_ENGLISH,
381         L_OPTIONS,
382         NULL
383       };
384       command_keywords = help_keywords;
385     }
386     else if ( command == L_SET_COMMAND ) {
387       static char const *const *set_options;
388       if ( set_options == NULL )
389         set_options = init_set_options();
390       command_keywords = set_options;
391     }
392     else {
393       command_keywords = NULL;
394     }
395   }
396 
397   if ( command_keywords != NULL ) {
398     //
399     // There's a special-case command having specific keywords in effect:
400     // attempt to match against only those.
401     //
402     for ( char const *s; (s = command_keywords[ match_index ]) != NULL; ) {
403       ++match_index;
404       if ( strncmp( text, s, text_len ) == 0 )
405         return check_strdup( s );
406     } // for
407   }
408   else {
409     //
410     // Otherwise, just attempt to match (almost) any keyword.
411     //
412     static c_lang_lit_t const *cdecl_keywords;
413     if ( cdecl_keywords == NULL )
414       cdecl_keywords = init_cdecl_keywords();
415 
416     for ( c_lang_lit_t const *ll;
417           (ll = cdecl_keywords + match_index)->literal != NULL; ) {
418       ++match_index;
419       if ( !opt_lang_is_any( ll->lang_ids ) )
420         continue;
421       if ( strncmp( text, ll->literal, text_len ) == 0 )
422         return check_strdup( ll->literal );
423     } // for
424   }
425 
426   return NULL;
427 }
428 
429 ////////// extern functions ///////////////////////////////////////////////////
430 
431 /**
432  * Initializes readline.
433  *
434  * @param rin The `FILE` to read from.
435  * @param rout The `FILE` to write to.
436  */
readline_init(FILE * rin,FILE * rout)437 void readline_init( FILE *rin, FILE *rout ) {
438   // allow conditional ~/.inputrc parsing
439   rl_readline_name = CONST_CAST( char*, CDECL );
440 
441   rl_attempted_completion_function = cdecl_rl_completion;
442   rl_instream = rin;
443   rl_outstream = rout;
444 }
445 
446 ///////////////////////////////////////////////////////////////////////////////
447 /* vim:set et sw=2 ts=2: */
448