+
+static int cmd_help(const char *cmd, const char *arg);
+
+static const struct command {
+ char name[16];
+ int (*func)(const char *cmd, const char *arg);
+ const char *blurb;
+} commands[] = {
+ { "explain", cmd_explain, "Explain a C declaration." },
+ { "simplify", cmd_simplify, "Simplify a C declaration." },
+ { "declare", cmd_declare, "Construct a C declaration." },
+ { "type", cmd_declare, "Construct a C type name." },
+ { "help", cmd_help, "Print this list of commands." },
+ { "quit", cmd_quit, "Quit the program." },
+ { "exit", cmd_quit, NULL }
+};
+static const size_t ncommands = sizeof commands / sizeof commands[0];
+
+static int cmd_help(const char *cmd, const char *arg)
+{
+ for (size_t i = 0; i < ncommands; i++) {
+ if (!commands[i].blurb)
+ continue;
+
+ printf("%s -- %s\n", commands[i].name, commands[i].blurb);
+ }
+
+ return 1;
+}
+
+/*
+ * Ensure that the first n characters of str equal the entire (null-terminated)
+ * string cmd.
+ */
+static int cmd_cmp(const char *str, const char *cmd, size_t n)
+{
+ size_t cmdlen = strlen(cmd);
+
+ if (n < cmdlen)
+ return -1;
+ if (n > cmdlen)
+ return 1;
+ return memcmp(str, cmd, n);
+}
+
+static int run_command(const char *line)
+{
+ const char *cmd = line + strspn(line, " \t");
+ const char *arg = cmd + strcspn(cmd, " \t");
+
+ if (cmd[0] == '\0')
+ return 1;
+
+ for (size_t i = 0; i < ncommands; i++) {
+ if (cmd_cmp(cmd, commands[i].name, arg-cmd) != 0)
+ continue;
+
+ return commands[i].func(cmd, arg);
+ }
+
+ fprintf(stderr, "Undefined command: %.*s\n", (int)(arg-cmd), cmd);
+ return -1;
+}
+
+static bool is_blank_line(const char *line)
+{
+ for (size_t i = 0; line[i]; i++) {
+ if (!isblank((unsigned char)line[i]))
+ return false;
+ }
+
+ return true;
+}
+
+static int repl(void)
+{
+ char *line;
+
+ for (; (line = readline("> ")); free(line)) {
+ if (!is_blank_line(line))
+ cdecl_add_history(line);
+
+ if (!run_command(line))
+ break;
+ }
+
+ free(line);
+ return 0;
+}
+
+static int repl_cmdline(int argc, char **argv)
+{
+ int opt, rc, ret = 0;
+
+ optind = 1;
+ while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
+ if (opt != 'e')
+ continue;
+
+ rc = run_command(optarg);
+ if (rc < 0)
+ ret = -1;
+ else if (rc == 0)
+ break;
+ }
+
+ return ret;
+}
+
+static int repl_noninteractive(void)
+{
+ int rc, ret = 0, saved_errno;
+ char *line = NULL;
+ size_t n;
+
+ while (getline(&line, &n, stdin) >= 0) {
+ char *c = strchr(line, '\n');
+ if (c)
+ *c = '\0';
+
+ rc = run_command(line);
+ if (rc < 0)
+ ret = -1;
+ else if (rc == 0)
+ break;
+ }
+
+ saved_errno = errno;
+ free(line);
+
+ if (ferror(stdin)) {
+ errno = saved_errno;
+ perror("read error");
+ return -1;
+ }
+
+ return ret;
+}
+
+/* Initialize gettext and setup translated long options. */
+static void init_i18n(void)
+{
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+
+ if (!ENABLE_NLS)
+ return;
+
+ for (int i = 0, j = NOPTS; i < NOPTS; i++) {
+ const char *tname = pgettext_expr("longopt", lopts[i].name);
+
+ if (strcmp(tname, lopts[i].name) != 0) {
+ lopts[j] = lopts[i];
+ lopts[j].name = tname;
+ j++;
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ bool show_intro = true, interactive = true, execute = false;
+ const char *filename = NULL;
+ int opt, rc;
+
+ if (argc > 0)
+ progname = argv[0];
+
+ init_i18n();
+
+ while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
+ switch (opt) {
+ case 'q':
+ show_intro = false;
+ break;
+ case 'b':
+ interactive = false;
+ break;
+ case 'i':
+ interactive = true;
+ break;
+ case 'f':
+ filename = optarg;
+ break;
+ case 'e':
+ execute = true;
+ break;
+ case 'V':
+ print_version();
+ return EXIT_SUCCESS;
+ case 'H':
+ print_help();
+ return EXIT_SUCCESS;
+ default:
+ print_usage(stderr);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* --filename and --execute imply --batch. */
+ if (filename || execute)
+ interactive = false;
+
+ /* --batch implies --quiet */
+ if (interactive && show_intro)
+ print_version();
+
+ /* --execute supersedes --filename */
+ if (filename && !execute) {
+ if (!freopen(filename, "r", stdin)) {
+ perror(filename);
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (interactive)
+ rc = repl();
+ else if (execute)
+ rc = repl_cmdline(argc, argv);
+ else
+ rc = repl_noninteractive();
+
+ if (rc != 0)
+ return EXIT_FAILURE;
+ return EXIT_SUCCESS;
+}