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