]> git.draconx.ca Git - cdecl99.git/blob - src/cdecl99.c
f7eed62acbd94bf0a74dc7d0d73398dc6dc05704
[cdecl99.git] / src / cdecl99.c
1 /*
2  * Command line utility for making sense of C declarations.
3  * Copyright © 2011-2012, 2020-2023 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 <https://www.gnu.org/licenses/>.
17  */
18
19 #include <config.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <stdbool.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <locale.h>
27 #include <assert.h>
28 #include <stdarg.h>
29
30 #include <getopt.h>
31 #include <gettext.h>
32 #include <localcharset.h>
33 #include <mbswidth.h>
34
35 #include "cdecl99.h"
36 #include "cdecl.h"
37 #include "help.h"
38 #include "xtra.h"
39 #include "copysym.h"
40 #include "options.h"
41
42 #if HAVE_READLINE_READLINE_H
43 #  include <readline/readline.h>
44 #endif
45 #if HAVE_RL_ADD_HISTORY && HAVE_READLINE_HISTORY_H
46 #  include <readline/history.h>
47
48 /* call add_history only if the line is non-blank */
49 static void do_add_history(const char *line)
50 {
51         if (line[strspn(line, " \t")])
52                 add_history(line);
53 }
54 #else
55 static void do_add_history(const char *line)
56 {
57 }
58 #endif
59
60 static const char *progname = "cdecl99";
61 static bool interactive = true;
62
63 void print_error(const char *fmt, ...)
64 {
65         va_list(ap);
66
67         if (!interactive)
68                 fprintf(stderr, "%s: ", progname);
69         fprintf(stderr, "%s", _("error: "));
70
71         va_start(ap, fmt);
72         vfprintf(stderr, fmt, ap);
73         va_end(ap);
74
75         fprintf(stderr, "\n");
76 }
77
78 static void print_version(void)
79 {
80         const char *copysign = copyright_symbol(locale_charset());
81
82         puts(PACKAGE_STRING);
83         printf("Copyright %s 2023 Nick Bowler.\n", copysign);
84         puts("License GPLv3+: GNU GPL version 3 or any later version.");
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         if (f != stdout)
93                 fprintf(f, _("Try %s --help for more information.\n"),
94                            progname);
95 }
96
97 static void print_help(const struct option *lopts)
98 {
99         struct lopt_help help = {0};
100         const struct option *opt;
101
102         print_usage(stdout);
103
104         puts(_("This is \"cdecl99\": a command-line tool for parsing and constructing\n"
105                "complicated C declarations."));
106         putchar('\n');
107
108         puts(_("Options:"));
109         for (opt = lopts; opt->name; opt++) {
110                 if (!lopt_get_help(opt, &help))
111                         continue;
112
113                 help_print_option(opt, help.arg, help.desc, 20);
114         }
115         putchar('\n');
116
117         puts(_("For more information, see the cdecl99(1) man page."));
118         putchar('\n');
119
120         /*
121          * TRANSLATORS: Please add *another line* indicating where users should
122          * report translation bugs.
123          */
124         printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
125 }
126
127 static int repl_cmdline(unsigned count, char **commands)
128 {
129         int ret = 0;
130         unsigned i;
131
132         for (i = 0; i < count; i++) {
133                 int rc = run_command(commands[i], false);
134                 if (rc < 0)
135                         ret = -1;
136                 else if (rc > 0)
137                         break;
138         }
139
140         return ret;
141 }
142
143 static int do_getline(char **linebuf, size_t *n)
144 {
145         ssize_t rc;
146
147         if ((rc = getline(linebuf, n, stdin)) < 0) {
148                 if (ferror(stdin))
149                         print_error("%s", strerror(errno));
150                 return 0;
151         }
152
153         if (rc-- && (*linebuf)[rc] == '\n')
154                 (*linebuf)[rc] = '\0';
155         return 1;
156 }
157
158 static int do_readline(char **linebuf, size_t *n, int interactive)
159 {
160 #if !HAVE_READLINE
161         if (interactive) {
162                 fputs("> ", stdout);
163                 fflush(stdout);
164         }
165
166         return do_getline(linebuf, n);
167 #else
168         if (!interactive)
169                 return do_getline(linebuf, n);
170
171         free(*linebuf);
172         if (!(*linebuf = readline("> ")))
173                 return 0;
174
175         do_add_history(*linebuf);
176         return 1;
177 #endif
178 }
179
180 static int repl(int interactive)
181 {
182         char *line = NULL;
183         int ret = 0;
184         size_t n;
185
186         while (do_readline(&line, &n, interactive)) {
187                 int rc = run_command(line, interactive);
188                 if (rc > 0)
189                         break;
190                 else if (rc < 0)
191                         ret = -!interactive;
192         }
193
194         free(line);
195         return ret;
196 }
197
198 /* Initialize gettext */
199 static void init_i18n(void)
200 {
201         if (!ENABLE_NLS)
202                 return;
203
204         setlocale(LC_ALL, "");
205         bindtextdomain(PACKAGE, LOCALEDIR);
206         textdomain(PACKAGE);
207 }
208
209 enum {
210         INIT_EXIT_SUCCESS = -1,
211         INIT_EXIT_FAILURE = -2
212 };
213
214 static int initialize(int argc, char **argv)
215 {
216         int i, opt, quiet = 0, execute = 0;
217         const char *filename = NULL;
218
219         XTRA_PACKED_LOPTS(lopts);
220
221         if (argc > 0)
222                 progname = argv[0];
223
224         init_i18n();
225
226         while ((opt = getopt_long(argc, argv, SOPT_STRING, lopts, 0)) != -1) {
227                 switch (opt) {
228                 case 'q':
229                         quiet = 1;
230                         break;
231                 case 'b':
232                         interactive = false;
233                         break;
234                 case 'i':
235                         interactive = true;
236                         break;
237                 case 'f':
238                         filename = optarg;
239                         break;
240                 case 'e':
241                         argv[execute++] = optarg;
242                         break;
243                 case 'V':
244                         print_version();
245                         return INIT_EXIT_SUCCESS;
246                 case 'H':
247                         print_help(lopts);
248                         return INIT_EXIT_SUCCESS;
249                 default:
250                         print_usage(stderr);
251                         return INIT_EXIT_FAILURE;
252                 }
253         }
254
255         if (optind < argc) {
256                 fprintf(stderr, "%s: ", progname);
257                 fprintf(stderr, _("excess command-line arguments:"));
258                 for (i = optind; i < argc; i++) {
259                         fprintf(stderr, " %s", argv[i]);
260                 }
261                 fprintf(stderr, "\n");
262                 print_usage(stderr);
263                 return INIT_EXIT_FAILURE;
264         }
265
266         /* --filename and --execute imply --batch. */
267         if (filename || execute)
268                 interactive = false;
269
270         /* --batch implies --quiet */
271         if (interactive && !quiet)
272                 print_version();
273
274         /* --execute supersedes --filename */
275         if (filename && !execute) {
276                 if (!freopen(filename, "r", stdin)) {
277                         print_error("failed to open %s: %s", filename,
278                                                         strerror(errno));
279                         return INIT_EXIT_FAILURE;
280                 }
281         }
282
283         return execute;
284 }
285
286 int main(int argc, char **argv)
287 {
288         int rc, execute;
289
290         switch ((execute = initialize(argc, argv))) {
291         case INIT_EXIT_SUCCESS: return EXIT_SUCCESS;
292         case INIT_EXIT_FAILURE: return EXIT_FAILURE;
293         }
294
295         if (execute)
296                 rc = repl_cmdline(execute, argv);
297         else
298                 rc = repl(interactive);
299
300         if (rc != 0)
301                 return EXIT_FAILURE;
302         return EXIT_SUCCESS;
303 }