]> git.draconx.ca Git - cdecl99.git/blob - src/cdecl99.c
c70834fbb98eeaa015bdff0caa73adda63793fbf
[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 "readline.h"
28 #include "cdecl.h"
29
30 #define N_(x) x
31 #define PN_(c, x) x
32
33 static const char *progname = "cdecl99";
34 static const char sopts[] = "qbif:e:VH";
35 #define RAW_LOPTS \
36         { PN_("longopt", "quiet"),       0, NULL, 'q' }, \
37         { PN_("longopt", "batch"),       0, NULL, 'b' }, \
38         { PN_("longopt", "interactive"), 0, NULL, 'i' }, \
39         { PN_("longopt", "file"),        1, NULL, 'f' }, \
40         { PN_("longopt", "execute"),     1, NULL, 'e' }, \
41         { PN_("longopt", "version"),     0, NULL, 'V' }, \
42         { PN_("longopt", "help"),        0, NULL, 'H' }
43
44 /*
45  * With NLS, we need a buffer big enough to store the translated options.
46  * The translations will be filled in at program startup.
47  */
48 enum { NOPTS = sizeof (struct option[]){RAW_LOPTS} / sizeof (struct option) };
49 static struct option lopts[NOPTS + !!ENABLE_NLS * NOPTS + 1] = { RAW_LOPTS };
50
51 static void print_version(void)
52 {
53         puts(PACKAGE_STRING);
54         /* TRANSLATORS: (C) must *always* be translated as ©. */
55         printf("Copyright %s 2011 Nick Bowler.\n", gettext("(C)"));
56         puts("License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.");
57         puts("This is free software: you are free to change and redistribute it.");
58         puts("There is NO WARRANTY, to the extent permitted by law.");
59 }
60
61 static void print_usage(FILE *f)
62 {
63         fprintf(f, "Usage: %s [options]\n", progname);
64 }
65
66 static void print_help(void)
67 {
68         print_usage(stdout);
69         puts("Detailed help coming soon.");
70 }
71
72 /*
73  * Format a declaration according to the given function and return a pointer
74  * to the formatted string.  The returned pointer remains valid until the
75  * next call, after which it must not be re-used.
76  *
77  * Returns NULL on failure.
78  */
79 static const char *
80 do_format(size_t func(char *, size_t, struct cdecl *), struct cdecl *decl)
81 {
82         static size_t bufsz;
83         static char *buf;
84
85         size_t rc;
86
87 retry:
88         rc = func(buf, bufsz, decl);
89         if (rc >= bufsz) {
90                 char *tmp;
91
92                 tmp = realloc(buf, rc + 1);
93                 if (!tmp) {
94                         fprintf(stderr, "failed to allocate memory\n");
95                         return NULL;
96                 }
97
98                 buf = tmp;
99                 bufsz = rc + 1;
100                 goto retry;
101         }
102
103         return buf;
104 }
105
106 static int cmd_explain(const char *cmd, const char *arg)
107 {
108         const struct cdecl_error *err;
109         struct cdecl *decl;
110         const char *str;
111         int ret = -1;
112
113         decl = cdecl_parse_decl(arg);
114         if (!decl) {
115                 err = cdecl_get_error();
116                 fprintf(stderr, "%s\n", err->str);
117                 goto out;
118         }
119
120         for (struct cdecl *i = decl; i; i = i->next) {
121                 str = do_format(cdecl_explain, i);
122                 if (!str)
123                         goto out;
124
125                 printf("%s\n", str);
126         }
127
128         ret = 1;
129 out:
130         cdecl_free(decl);
131         return ret;
132 }
133
134 static int cmd_simplify(const char *cmd, const char *arg)
135 {
136         const struct cdecl_error *err;
137         struct cdecl *decl;
138         const char *str;
139         int ret = -1;
140
141         decl = cdecl_parse_decl(arg);
142         if (!decl) {
143                 err = cdecl_get_error();
144                 fprintf(stderr, "%s\n", err->str);
145                 goto out;
146         }
147
148         for (struct cdecl *i = decl; i; i = i->next) {
149                 struct cdecl_declspec *s = i->specifiers;
150
151                 if (i != decl) {
152                         i->specifiers = NULL;
153                         printf(", ");
154                 }
155
156                 str = do_format(cdecl_declare, i);
157                 i->specifiers = s;
158
159                 if (!str)
160                         goto out;
161
162                 printf("%s", str);
163         }
164
165         putchar('\n');
166
167         ret = 1;
168 out:
169         cdecl_free(decl);
170         return ret;
171 }
172
173 static int cmd_declare(const char *cmd, const char *arg)
174 {
175         const struct cdecl_error *err;
176         struct cdecl *decl;
177         const char *str;
178         int ret = -1;
179
180         /* The name of the command is significant here. */
181         decl = cdecl_parse_english(cmd);
182         if (!decl) {
183                 err = cdecl_get_error();
184                 fprintf(stderr, "%s\n", err->str);
185                 goto out;
186         }
187
188         /*
189          * English parses have at most one full declarator, so no loop is
190          * needed here.
191          */
192         str = do_format(cdecl_declare, decl);
193         if (!str)
194                 goto out;
195
196         printf("%s\n", str);
197         ret = 1;
198 out:
199         cdecl_free(decl);
200         return ret;
201 }
202
203 static int cmd_quit(const char *cmd, const char *arg)
204 {
205         return 0;
206 }
207
208 static int cmd_help(const char *cmd, const char *arg);
209
210 static const struct command {
211         char name[16];
212         int (*func)(const char *cmd, const char *arg);
213         const char *blurb;
214 } commands[] = {
215         { "explain",  cmd_explain,  "Explain a C declaration." },
216         { "simplify", cmd_simplify, "Simplify a C declaration." },
217         { "declare",  cmd_declare,  "Construct a C declaration." },
218         { "type",     cmd_declare,  "Construct a C type name." },
219         { "help",     cmd_help,     "Print this list of commands." },
220         { "quit",     cmd_quit,     "Quit the program." },
221         { "exit",     cmd_quit,     NULL }
222 };
223 static const size_t ncommands = sizeof commands / sizeof commands[0];
224
225 static int cmd_help(const char *cmd, const char *arg)
226 {
227         for (size_t i = 0; i < ncommands; i++) {
228                 if (!commands[i].blurb)
229                         continue;
230
231                 printf("%s -- %s\n", commands[i].name, commands[i].blurb);
232         }
233
234         return 1;
235 }
236
237 /*
238  * Ensure that the first n characters of str equal the entire (null-terminated)
239  * string cmd.
240  */
241 static int cmd_cmp(const char *str, const char *cmd, size_t n)
242 {
243         size_t cmdlen = strlen(cmd);
244
245         if (n < cmdlen)
246                 return -1;
247         if (n > cmdlen)
248                 return 1;
249         return memcmp(str, cmd, n);
250 }
251
252 static int run_command(const char *line)
253 {
254         const char *cmd = line + strspn(line, " \t");
255         const char *arg = cmd  + strcspn(cmd, " \t");
256
257         if (cmd[0] == '\0')
258                 return 1;
259
260         for (size_t i = 0; i < ncommands; i++) {
261                 if (cmd_cmp(cmd, commands[i].name, arg-cmd) != 0)
262                         continue;
263
264                 return commands[i].func(cmd, arg);
265         }
266
267         fprintf(stderr, "Undefined command: %.*s\n", (int)(arg-cmd), cmd);
268         return -1;
269 }
270
271 static int repl(void)
272 {
273         char *line;
274
275         for (; (line = readline("> ")); free(line)) {
276                 if (!run_command(line))
277                         break;
278         }
279
280         free(line);
281         return 0;
282 }
283
284 static int repl_cmdline(int argc, char **argv)
285 {
286         int opt, rc, ret = 0;
287
288         optind = 1;
289         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
290                 if (opt != 'e')
291                         continue;
292
293                 rc = run_command(optarg);
294                 if (rc < 0)
295                         ret = -1;
296                 else if (rc == 0)
297                         break;
298         }
299
300         return ret;
301 }
302
303 static int repl_noninteractive(void)
304 {
305         int rc, ret = 0, saved_errno;
306         char *line = NULL;
307         size_t n;
308
309         while (getline(&line, &n, stdin) >= 0) {
310                 char *c = strchr(line, '\n');
311                 if (c)
312                         *c = '\0';
313
314                 rc = run_command(line);
315                 if (rc < 0)
316                         ret = -1;
317                 else if (rc == 0)
318                         break;
319         }
320
321         saved_errno = errno;
322         free(line);
323
324         if (ferror(stdin)) {
325                 errno = saved_errno;
326                 perror("read error");
327                 return -1;
328         }
329
330         return ret;
331 }
332
333 /* Initialize gettext and setup translated long options. */
334 static void init_i18n(void)
335 {
336         setlocale(LC_ALL, "");
337         bindtextdomain(PACKAGE, LOCALEDIR);
338         textdomain(PACKAGE);
339
340         if (!ENABLE_NLS)
341                 return;
342
343         for (int i = 0; i < NOPTS; i++) {
344                 lopts[i+NOPTS] = lopts[i];
345                 lopts[i+NOPTS].name = pgettext_expr("longopt", lopts[i].name);
346         }
347 }
348
349 int main(int argc, char **argv)
350 {
351         bool show_intro = true, interactive = true, execute = false;
352         const char *filename = NULL;
353         int opt, rc;
354
355         if (argc > 0)
356                 progname = argv[0];
357
358         init_i18n();
359
360         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
361                 switch (opt) {
362                 case 'q':
363                         show_intro = false;
364                         break;
365                 case 'b':
366                         interactive = false;
367                         break;
368                 case 'i':
369                         interactive = true;
370                         break;
371                 case 'f':
372                         filename = optarg;
373                         break;
374                 case 'e':
375                         execute = true;
376                         break;
377                 case 'V':
378                         print_version();
379                         return EXIT_SUCCESS;
380                 case 'H':
381                         print_help();
382                         return EXIT_SUCCESS;
383                 default:
384                         print_usage(stderr);
385                         return EXIT_FAILURE;
386                 }
387         }
388
389         /* --filename and --execute imply --batch. */
390         if (filename || execute)
391                 interactive = false;
392
393         /* --batch implies --quiet */
394         if (interactive && show_intro)
395                 print_version();
396
397         /* --execute supersedes --filename */
398         if (filename && !execute) {
399                 if (!freopen(filename, "r", stdin)) {
400                         perror(filename);
401                         return EXIT_FAILURE;
402                 }
403         }
404
405         if (interactive)
406                 rc = repl();
407         else if (execute)
408                 rc = repl_cmdline(argc, argv);
409         else
410                 rc = repl_noninteractive();
411
412         if (rc != 0)
413                 return EXIT_FAILURE;
414         return EXIT_SUCCESS;
415 }