+\f
+const struct macro *
+macro_set_find (const struct macro_set *set, const char *name)
+{
+ struct macro *macro;
+
+ HMAP_FOR_EACH_WITH_HASH (macro, struct macro, hmap_node,
+ utf8_hash_case_string (name, 0), &set->macros)
+ {
+ if (!utf8_strcasecmp (macro->name, name))
+ return macro;
+ }
+
+ return NULL;
+}
+\f
+enum me_state
+ {
+ ME_START,
+
+ /* Accumulating tokens in me->params toward the end of any type of
+ argument. */
+ ME_ARG,
+
+ /* Expecting the opening delimiter of an ARG_ENCLOSE argument. */
+ ME_ENCLOSE,
+
+ /* Expecting a keyword for a keyword argument. */
+ ME_KEYWORD,
+
+ /* Expecting an equal sign for a keyword argument. */
+ ME_EQUALS,
+ };
+
+
+struct macro_expander
+ {
+ const struct macro_set *macros;
+
+ enum me_state state;
+ size_t n_tokens;
+
+ const struct macro *macro;
+ struct tokens **args;
+ size_t arg_index;
+ };
+
+static int
+me_finished (struct macro_expander *me)
+{
+ for (size_t i = 0; i < me->macro->n_params; i++)
+ if (!me->args[i])
+ {
+ me->args[i] = xmalloc (sizeof *me->args[i]);
+ tokens_copy (me->args[i], &me->macro->params[i].def);
+ }
+ return me->n_tokens;
+}
+
+static int
+me_next_arg (struct macro_expander *me)
+{
+ if (me->arg_index >= me->macro->n_params)
+ {
+ assert (!me->macro->n_params);
+ return me_finished (me);
+ }
+ else if (!me->macro->params[me->arg_index].name)
+ {
+ me->arg_index++;
+ if (me->arg_index >= me->macro->n_params)
+ return me_finished (me);
+ else
+ {
+ if (!me->macro->params[me->arg_index].name)
+ me->state = ME_ARG;
+ else
+ me->state = ME_KEYWORD;
+ return 0;
+ }
+ }
+ else
+ {
+ for (size_t i = 0; i < me->macro->n_params; i++)
+ if (!me->args[i])
+ {
+ me->state = ME_KEYWORD;
+ return 0;
+ }
+ return me_finished (me);
+ }
+}
+
+static int
+me_add_start (struct macro_expander *me, const struct token *token)
+{
+ if (token->type != T_ID && token->type != T_MACRO_ID)
+ return -1;
+
+ me->macro = macro_set_find (me->macros, token->string.string);
+ if (!me->macro)
+ return -1;
+
+ me->n_tokens = 1;
+ me->args = xcalloc (me->macro->n_params, sizeof *me->args);
+ me->arg_index = 0;
+ return me_next_arg (me);
+}
+
+static int
+me_add_arg (struct macro_expander *me, const struct token *token)
+{
+ me->n_tokens++;
+
+ struct tokens **ap = &me->args[me->arg_index];
+ if (!*ap)
+ *ap = xzalloc (sizeof **ap);
+ struct tokens *a = *ap;
+ const struct macro_param *p = &me->macro->params[me->arg_index];
+ if (p->arg_type == ARG_N_TOKENS)
+ {
+ tokens_add (a, token);
+ if (a->n >= p->n_tokens)
+ return me_next_arg (me);
+ return 0;
+ }
+ else if (p->arg_type == ARG_CMDEND)
+ {
+ if (token->type == T_ENDCMD || token->type == T_STOP)
+ return me_next_arg (me);
+ tokens_add (a, token);
+ return 0;
+ }
+ else
+ {
+ const struct token *end
+ = p->arg_type == ARG_CMDEND ? &p->charend : &p->enclose[1];
+ if (token_equal (token, end))
+ return me_next_arg (me);
+ tokens_add (a, token);
+ return 0;
+ }
+}
+
+static int
+me_error (struct macro_expander *me)
+{
+ me->state = ME_START;
+ return -1;
+}
+
+static int
+me_expected (struct macro_expander *me, const struct token *token,
+ const struct token *wanted)
+{
+ const struct macro_param *p = &me->macro->params[me->arg_index];
+ char *param_name = (p->name
+ ? xstrdup (p->name)
+ : xasprintf ("%zu", me->arg_index));
+ char *actual = token_to_string (token);
+ if (!actual)
+ actual = xstrdup ("<eof>");
+ char *expected = token_to_string (wanted);
+ msg (SE, _("Found `%s' while expecting `%s' reading argument %s "
+ "in call to macro %s."),
+ actual, expected, param_name, me->macro->name);
+ free (expected);
+ free (actual);
+ free (param_name);
+
+ return me_error (me);
+}
+
+static int
+me_enclose (struct macro_expander *me, const struct token *token)
+{
+ me->n_tokens++;
+
+ const struct macro_param *p = &me->macro->params[me->arg_index];
+ if (token_equal (&p->enclose[0], token))
+ {
+ me->state = ME_ARG;
+ return 0;
+ }
+
+ return me_expected (me, token, &p->enclose[0]);
+}
+
+static int
+me_keyword (struct macro_expander *me, const struct token *token)
+{
+ if (token->type != T_ID)
+ return me_finished (me);
+
+ for (size_t i = 0; i < me->macro->n_params; i++)
+ {
+ const struct macro_param *p = &me->macro->params[i];
+ if (p->name && ss_equals_case (ss_cstr (p->name), token->string))
+ {
+ me->arg_index = i;
+ if (me->args[i])
+ {
+ msg (SE,
+ _("Argument %s multiply specified in call to macro %s."),
+ p->name, me->macro->name);
+ return me_error (me);
+ }
+
+ me->n_tokens++;
+ me->state = ME_EQUALS;
+ return 0;
+ }
+ }
+
+ return me_finished (me);
+}
+
+static int
+me_equals (struct macro_expander *me, const struct token *token)
+{
+ me->n_tokens++;
+
+ if (token->type == T_EQUALS)
+ {
+ me->state = ME_ARG;
+ return 0;
+ }
+
+ const struct token equals = { .type = T_EQUALS };
+ return me_expected (me, token, &equals);
+}
+
+int
+macro_expander_add (struct macro_expander *me, const struct token *token)
+{
+ switch (me->state)
+ {
+ case ME_START:
+ return me_add_start (me, token);
+
+ case ME_ARG:
+ return me_add_arg (me, token);
+
+ case ME_ENCLOSE:
+ return me_enclose (me, token);
+
+ case ME_KEYWORD:
+ return me_keyword (me, token);
+
+ case ME_EQUALS:
+ return me_equals (me, token);
+
+ default:
+ NOT_REACHED ();
+ }
+}