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