+ for (struct cdecl *i = decl; i; i = i->next) {
+ struct cdecl_declspec *s = i->specifiers;
+
+ if (i != decl) {
+ i->specifiers = NULL;
+ printf(", ");
+ }
+
+ str = do_format(cdecl_declare, i);
+ i->specifiers = s;
+
+ if (!str)
+ goto out;
+
+ printf("%s", str);
+ }
+
+ putchar('\n');
+
+ ret = 1;
+out:
+ cdecl_free(decl);
+ return ret;
+}
+
+static int cmd_declare(const char *cmd, const char *arg)
+{
+ const struct cdecl_error *err;
+ struct cdecl *decl;
+ const char *str;
+ int ret = -1;
+
+ /* The name of the command is significant here. */
+ decl = cdecl_parse_english(cmd);
+ if (!decl) {
+ err = cdecl_get_error();
+ fprintf(stderr, "%s\n", err->str);
+ goto out;
+ }
+
+ /*
+ * English parses have at most one full declarator, so no loop is
+ * needed here.
+ */
+ str = do_format(cdecl_declare, decl);
+ if (!str)
+ goto out;
+
+ printf("%s\n", str);
+ ret = 1;
+out:
+ cdecl_free(decl);
+ return ret;
+}
+
+static int cmd_quit(const char *cmd, const char *arg)
+{
+ return 0;
+}
+
+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);