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