]> git.draconx.ca Git - cdecl99.git/blob - src/cdecl99.c
Add basic readline history support.
[cdecl99.git] / src / cdecl99.c
1 /*
2  * Command line utility for making sense of C declarations.
3  * Copyright © 2011-2012 Nick Bowler
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 #include <config.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <stdbool.h>
22 #include <string.h>
23 #include <ctype.h>
24 #include <errno.h>
25 #include <locale.h>
26 #include <getopt.h>
27 #include <gettext.h>
28 #include <assert.h>
29 #include "readline.h"
30 #include "history.h"
31 #include "cdecl.h"
32
33 #define _(x) gettext(x)
34 #define N_(x) x
35 #define PN_(c, x) x
36
37 static const char *progname = "cdecl99";
38 static const char sopts[] = "qbif:e:VH";
39 #define RAW_LOPTS \
40         { PN_("longopt", "quiet"),       0, NULL, 'q' }, \
41         { PN_("longopt", "batch"),       0, NULL, 'b' }, \
42         { PN_("longopt", "interactive"), 0, NULL, 'i' }, \
43         { PN_("longopt", "file"),        1, NULL, 'f' }, \
44         { PN_("longopt", "execute"),     1, NULL, 'e' }, \
45         { PN_("longopt", "version"),     0, NULL, 'V' }, \
46         { PN_("longopt", "help"),        0, NULL, 'H' }
47
48 /*
49  * With NLS, we need a buffer big enough to store the translated options.
50  * The translations will be filled in at program startup.
51  */
52 enum { NOPTS = sizeof (struct option[]){RAW_LOPTS} / sizeof (struct option) };
53 static struct option lopts[NOPTS + !!ENABLE_NLS * NOPTS + 1] = { RAW_LOPTS };
54
55 static struct helptext {
56         int val;
57         const char *text;
58         const char *argname;
59 } helptext[] = {
60         /*
61          * TRANSLATORS: Help messages are indented 20 spaces and thus should
62          * not have lines longer than 60 columns.
63          */
64         { 'q', N_("Suppress the welcome message.\n") },
65         { 'b', N_("Execute commands as normal, but do not print any prompts.\n") },
66         { 'i', N_("Run in interactive mode.  This is the default.\n") },
67         { 'f', N_("Read commands from FILE instead of standard input.\n"),
68                PN_("longopt|file", "FILE") },
69         { 'e', N_("Execute COMMAND as if it were entered at the prompt.\n"
70                   "This can be specified multiple times.\n"),
71                PN_("longopt|execute", "COMMAND") },
72         { 'V', N_("Print a version message and then exit.\n") },
73         { 'H', N_("Print this message.\n") },
74
75         /* TRANSLATORS: ARG is only used for options without help text. */
76         { 0, NULL, PN_("longopt", "ARG") }
77 };
78
79 static void print_version(void)
80 {
81         puts(PACKAGE_STRING);
82         /* TRANSLATORS: (C) must *always* be translated as ©. */
83         printf("Copyright %s 2011 Nick Bowler.\n", gettext("(C)"));
84         puts("License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.");
85         puts("This is free software: you are free to change and redistribute it.");
86         puts("There is NO WARRANTY, to the extent permitted by law.");
87 }
88
89 static void print_usage(FILE *f)
90 {
91         fprintf(f, "Usage: %s [options]\n", progname);
92 }
93
94 /*
95  * Print the help description of a particular command-line option, identified
96  * by its short option name.
97  */
98 static void print_option(int val)
99 {
100         const struct option *tmp[2];
101         const struct helptext *help;
102         const char *argname;
103         char context[32];
104         size_t rc, n = 0;
105         int w;
106
107         /* Find the options and help text corresponding to val. */
108         for (const struct option *o = lopts; o->val; o++) {
109                 if (o->val == val) {
110                         assert(n < sizeof tmp / sizeof tmp[0]);
111                         tmp[n++] = o;
112                 }
113         }
114
115         for (help = helptext; help->val; help++) {
116                 if (help->val == val)
117                         break;
118         }
119
120         /* Prepare translations. */
121         if (!ENABLE_NLS) {
122                 argname = help->argname;
123         } else if (n > 0) {
124                 rc = snprintf(context, sizeof context, "longopt%c%s",
125                               help->text ? '|' : '\0', tmp[0]->name);
126                 assert(rc < sizeof context);
127
128                 if (help->argname)
129                         argname = pgettext_expr(context, help->argname);
130         }
131
132         switch (n) {
133         case 0:
134                 w = printf(_("  -%c"), val);
135                 break;
136         case 1:
137                 w = printf(tmp[0]->has_arg ? _("  -%c, --%s=%s")
138                                            : _("  -%c, --%s"),
139                            val, tmp[0]->name, argname);
140                 break;
141         case 2:
142                 if (tmp[0]->has_arg) {
143                         w = printf(_("  -%c, --%s=%s, --%s=%s"), val,
144                                    tmp[0]->name, argname,
145                                    tmp[1]->name, argname);
146                 } else {
147                         w = printf(_("  -%c, --%s, --%s"), val,
148                                    tmp[0]->name, tmp[1]->name);
149                 }
150                 break;
151         default:
152                 assert(0);
153         }
154
155         if (!help->text) {
156                 putchar('\n');
157                 return;
158         }
159
160         if (w > 18) {
161                 putchar('\n');
162                 w = 0;
163         }
164
165         for (const char *line = gettext(help->text); *line;) {
166                 const char *nl = strchr(line, '\n');
167
168                 if (!nl) {
169                         printf("%*s%s\n", 20-w, "", line);
170                         break;
171                 }
172
173                 printf("%*s%.*s\n", 20-w, "", (int)(nl-line), line);
174                 line = nl+1;
175         }
176 }
177
178 static void print_help(void)
179 {
180         print_usage(stdout);
181
182         puts(_(
183 "This is \"cdecl99\": a command-line tool for parsing and constructing\n"
184 "complicated C declarations.\n"));
185
186         puts(_("Options:"));
187         for (const char *c = sopts; *c; c++) {
188                 if (*c == ':' || *c == '+' || *c == '-')
189                         continue;
190
191                 print_option(*c);
192         }
193         putchar('\n');
194
195         puts(_("For more information, see the cdecl99(1) man page.\n"));
196
197         /*
198          * TRANSLATORS: Please add *another line* indicating where users should
199          * report translation bugs.
200          */
201         printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
202 }
203
204 /*
205  * Format a declaration according to the given function and return a pointer
206  * to the formatted string.  The returned pointer remains valid until the
207  * next call, after which it must not be re-used.
208  *
209  * Returns NULL on failure.
210  */
211 static const char *
212 do_format(size_t func(char *, size_t, struct cdecl *), struct cdecl *decl)
213 {
214         static size_t bufsz;
215         static char *buf;
216
217         size_t rc;
218
219 retry:
220         rc = func(buf, bufsz, decl);
221         if (rc >= bufsz) {
222                 char *tmp;
223
224                 tmp = realloc(buf, rc + 1);
225                 if (!tmp) {
226                         fprintf(stderr, "failed to allocate memory\n");
227                         return NULL;
228                 }
229
230                 buf = tmp;
231                 bufsz = rc + 1;
232                 goto retry;
233         }
234
235         return buf;
236 }
237
238 static int cmd_explain(const char *cmd, const char *arg)
239 {
240         const struct cdecl_error *err;
241         struct cdecl *decl;
242         const char *str;
243         int ret = -1;
244
245         decl = cdecl_parse_decl(arg);
246         if (!decl) {
247                 err = cdecl_get_error();
248                 fprintf(stderr, "%s\n", err->str);
249                 goto out;
250         }
251
252         for (struct cdecl *i = decl; i; i = i->next) {
253                 str = do_format(cdecl_explain, i);
254                 if (!str)
255                         goto out;
256
257                 printf("%s\n", str);
258         }
259
260         ret = 1;
261 out:
262         cdecl_free(decl);
263         return ret;
264 }
265
266 static int cmd_simplify(const char *cmd, const char *arg)
267 {
268         const struct cdecl_error *err;
269         struct cdecl *decl;
270         const char *str;
271         int ret = -1;
272
273         decl = cdecl_parse_decl(arg);
274         if (!decl) {
275                 err = cdecl_get_error();
276                 fprintf(stderr, "%s\n", err->str);
277                 goto out;
278         }
279
280         for (struct cdecl *i = decl; i; i = i->next) {
281                 struct cdecl_declspec *s = i->specifiers;
282
283                 if (i != decl) {
284                         i->specifiers = NULL;
285                         printf(", ");
286                 }
287
288                 str = do_format(cdecl_declare, i);
289                 i->specifiers = s;
290
291                 if (!str)
292                         goto out;
293
294                 printf("%s", str);
295         }
296
297         putchar('\n');
298
299         ret = 1;
300 out:
301         cdecl_free(decl);
302         return ret;
303 }
304
305 static int cmd_declare(const char *cmd, const char *arg)
306 {
307         const struct cdecl_error *err;
308         struct cdecl *decl;
309         const char *str;
310         int ret = -1;
311
312         /* The name of the command is significant here. */
313         decl = cdecl_parse_english(cmd);
314         if (!decl) {
315                 err = cdecl_get_error();
316                 fprintf(stderr, "%s\n", err->str);
317                 goto out;
318         }
319
320         /*
321          * English parses have at most one full declarator, so no loop is
322          * needed here.
323          */
324         str = do_format(cdecl_declare, decl);
325         if (!str)
326                 goto out;
327
328         printf("%s\n", str);
329         ret = 1;
330 out:
331         cdecl_free(decl);
332         return ret;
333 }
334
335 static int cmd_quit(const char *cmd, const char *arg)
336 {
337         return 0;
338 }
339
340 static int cmd_help(const char *cmd, const char *arg);
341
342 static const struct command {
343         char name[16];
344         int (*func)(const char *cmd, const char *arg);
345         const char *blurb;
346 } commands[] = {
347         { "explain",  cmd_explain,  "Explain a C declaration." },
348         { "simplify", cmd_simplify, "Simplify a C declaration." },
349         { "declare",  cmd_declare,  "Construct a C declaration." },
350         { "type",     cmd_declare,  "Construct a C type name." },
351         { "help",     cmd_help,     "Print this list of commands." },
352         { "quit",     cmd_quit,     "Quit the program." },
353         { "exit",     cmd_quit,     NULL }
354 };
355 static const size_t ncommands = sizeof commands / sizeof commands[0];
356
357 static int cmd_help(const char *cmd, const char *arg)
358 {
359         for (size_t i = 0; i < ncommands; i++) {
360                 if (!commands[i].blurb)
361                         continue;
362
363                 printf("%s -- %s\n", commands[i].name, commands[i].blurb);
364         }
365
366         return 1;
367 }
368
369 /*
370  * Ensure that the first n characters of str equal the entire (null-terminated)
371  * string cmd.
372  */
373 static int cmd_cmp(const char *str, const char *cmd, size_t n)
374 {
375         size_t cmdlen = strlen(cmd);
376
377         if (n < cmdlen)
378                 return -1;
379         if (n > cmdlen)
380                 return 1;
381         return memcmp(str, cmd, n);
382 }
383
384 static int run_command(const char *line)
385 {
386         const char *cmd = line + strspn(line, " \t");
387         const char *arg = cmd  + strcspn(cmd, " \t");
388
389         if (cmd[0] == '\0')
390                 return 1;
391
392         for (size_t i = 0; i < ncommands; i++) {
393                 if (cmd_cmp(cmd, commands[i].name, arg-cmd) != 0)
394                         continue;
395
396                 return commands[i].func(cmd, arg);
397         }
398
399         fprintf(stderr, "Undefined command: %.*s\n", (int)(arg-cmd), cmd);
400         return -1;
401 }
402
403 static bool is_blank_line(const char *line)
404 {
405         for (size_t i = 0; line[i]; i++) {
406                 if (!isblank((unsigned char)line[i]))
407                         return false;
408         }
409
410         return true;
411 }
412
413 static int repl(void)
414 {
415         char *line;
416
417         for (; (line = readline("> ")); free(line)) {
418                 if (!is_blank_line(line))
419                         cdecl_add_history(line);
420
421                 if (!run_command(line))
422                         break;
423         }
424
425         free(line);
426         return 0;
427 }
428
429 static int repl_cmdline(int argc, char **argv)
430 {
431         int opt, rc, ret = 0;
432
433         optind = 1;
434         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
435                 if (opt != 'e')
436                         continue;
437
438                 rc = run_command(optarg);
439                 if (rc < 0)
440                         ret = -1;
441                 else if (rc == 0)
442                         break;
443         }
444
445         return ret;
446 }
447
448 static int repl_noninteractive(void)
449 {
450         int rc, ret = 0, saved_errno;
451         char *line = NULL;
452         size_t n;
453
454         while (getline(&line, &n, stdin) >= 0) {
455                 char *c = strchr(line, '\n');
456                 if (c)
457                         *c = '\0';
458
459                 rc = run_command(line);
460                 if (rc < 0)
461                         ret = -1;
462                 else if (rc == 0)
463                         break;
464         }
465
466         saved_errno = errno;
467         free(line);
468
469         if (ferror(stdin)) {
470                 errno = saved_errno;
471                 perror("read error");
472                 return -1;
473         }
474
475         return ret;
476 }
477
478 /* Initialize gettext and setup translated long options. */
479 static void init_i18n(void)
480 {
481         setlocale(LC_ALL, "");
482         bindtextdomain(PACKAGE, LOCALEDIR);
483         textdomain(PACKAGE);
484
485         if (!ENABLE_NLS)
486                 return;
487
488         for (int i = 0, j = NOPTS; i < NOPTS; i++) {
489                 const char *tname = pgettext_expr("longopt", lopts[i].name);
490
491                 if (strcmp(tname, lopts[i].name) != 0) {
492                         lopts[j] = lopts[i];
493                         lopts[j].name = tname;
494                         j++;
495                 }
496         }
497 }
498
499 int main(int argc, char **argv)
500 {
501         bool show_intro = true, interactive = true, execute = false;
502         const char *filename = NULL;
503         int opt, rc;
504
505         if (argc > 0)
506                 progname = argv[0];
507
508         init_i18n();
509
510         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
511                 switch (opt) {
512                 case 'q':
513                         show_intro = false;
514                         break;
515                 case 'b':
516                         interactive = false;
517                         break;
518                 case 'i':
519                         interactive = true;
520                         break;
521                 case 'f':
522                         filename = optarg;
523                         break;
524                 case 'e':
525                         execute = true;
526                         break;
527                 case 'V':
528                         print_version();
529                         return EXIT_SUCCESS;
530                 case 'H':
531                         print_help();
532                         return EXIT_SUCCESS;
533                 default:
534                         print_usage(stderr);
535                         return EXIT_FAILURE;
536                 }
537         }
538
539         /* --filename and --execute imply --batch. */
540         if (filename || execute)
541                 interactive = false;
542
543         /* --batch implies --quiet */
544         if (interactive && show_intro)
545                 print_version();
546
547         /* --execute supersedes --filename */
548         if (filename && !execute) {
549                 if (!freopen(filename, "r", stdin)) {
550                         perror(filename);
551                         return EXIT_FAILURE;
552                 }
553         }
554
555         if (interactive)
556                 rc = repl();
557         else if (execute)
558                 rc = repl_cmdline(argc, argv);
559         else
560                 rc = repl_noninteractive();
561
562         if (rc != 0)
563                 return EXIT_FAILURE;
564         return EXIT_SUCCESS;
565 }