+#if HAVE_READLINE_READLINE_H
+# include <readline/readline.h>
+#endif
+#if HAVE_RL_ADD_HISTORY && HAVE_READLINE_HISTORY_H
+# include <readline/history.h>
+
+/* call add_history only if the line is non-blank */
+static void do_add_history(const char *line)
+{
+ if (line[strspn(line, " \t")])
+ add_history(line);
+}
+#else
+static void do_add_history(const char *line)
+{
+}
+#endif
+
+static const char *progname = "cdecl99";
+static bool batch_mode;
+
+void print_error(const char *fmt, ...)
+{
+ va_list(ap);
+
+ if (batch_mode)
+ fprintf(stderr, "%s: ", progname);
+ fprintf(stderr, "%s", _("error: "));
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+}
+
+static void print_version(void)
+{
+ const char *copysign = copyright_symbol(locale_charset());
+
+ puts(PACKAGE_STRING);
+ printf("Copyright %s 2023 Nick Bowler.\n", copysign);
+ puts("License GPLv3+: GNU GPL version 3 or any later version.");
+ puts("This is free software: you are free to change and redistribute it.");
+ puts("There is NO WARRANTY, to the extent permitted by law.");
+}
+
+static void print_usage(FILE *f)
+{
+ fprintf(f, _("Usage: %s [options]\n"), progname);
+ if (f != stdout)
+ fprintf(f, _("Try %s --help for more information.\n"),
+ progname);
+}
+
+static void print_help(const struct option *lopts)
+{
+ struct lopt_help help = {0};
+ const struct option *opt;
+
+ print_usage(stdout);
+
+ puts(_("This is \"cdecl99\": a command-line tool for parsing and constructing\n"
+ "complicated C declarations."));
+ putchar('\n');
+
+ puts(_("Options:"));
+ for (opt = lopts; opt->name; opt++) {
+ if (!lopt_get_help(opt, &help))
+ continue;
+
+ help_print_option(opt, help.arg, help.desc, 20);
+ }
+ putchar('\n');
+
+ puts(_("For more information, see the cdecl99(1) man page."));
+ putchar('\n');
+
+ /*
+ * TRANSLATORS: Please add *another line* indicating where users should
+ * report translation bugs.
+ */
+ printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+}
+
+static int do_getline(char **linebuf, size_t *n)
+{
+ ssize_t rc;
+
+ if ((rc = getline(linebuf, n, stdin)) < 0) {
+ if (ferror(stdin))
+ print_error("%s", strerror(errno));
+ return 0;
+ }
+
+ if (rc-- && (*linebuf)[rc] == '\n')
+ (*linebuf)[rc] = '\0';
+ return 1;
+}
+
+static int do_readline(char **linebuf, size_t *n, bool batch)
+{
+#if !HAVE_READLINE
+ if (!batch) {
+ fputs("> ", stdout);
+ fflush(stdout);
+ }
+
+ return do_getline(linebuf, n);
+#else
+ if (batch)
+ return do_getline(linebuf, n);
+
+ free(*linebuf);
+ if (!(*linebuf = readline("> ")))
+ return 0;
+
+ do_add_history(*linebuf);
+ return 1;
+#endif
+}
+
+static int repl(bool batch)
+{
+ char *line = NULL;
+ bool fail = 0;
+ size_t n;
+
+ while (do_readline(&line, &n, batch)) {
+ int rc = run_command(line, batch);
+ if (rc > 0)
+ break;
+ if (rc < 0)
+ fail = batch;
+ }
+
+ free(line);
+ return fail ? EXIT_FAILURE : 0;
+}
+
+static int repl_cmdline(unsigned count, char **commands)