Get rid of expr_context.
[pspp] / src / language / lexer / macro.c
index 3346692e105848ace204231ff7d4685ead0932af..3b17d90f38a977220d77492ec83321e4c1c5ec91 100644 (file)
@@ -32,7 +32,7 @@
 #include "libpspp/message.h"
 #include "libpspp/str.h"
 #include "libpspp/string-array.h"
-#include "libpspp/string-map.h"
+#include "libpspp/stringi-map.h"
 #include "libpspp/stringi-set.h"
 
 #include "gl/c-ctype.h"
@@ -536,7 +536,7 @@ macro_set_add (struct macro_set *set, struct macro *m)
   hmap_insert (&set->macros, &m->hmap_node, hash_macro_name (m->name));
 }
 \f
-/* Macro expander. */
+/* Macro call parsing.. */
 
 enum mc_state
   {
@@ -572,8 +572,6 @@ struct macro_call
     enum mc_state state;
     size_t n_tokens;
     const struct macro_param *param; /* Parameter currently being parsed. */
-
-    enum segmenter_mode segmenter_mode;
   };
 
 /* Completes macro expansion by initializing arguments that weren't supplied to
@@ -863,6 +861,20 @@ macro_call_add (struct macro_call *mc, const struct macro_token *mt)
       NOT_REACHED ();
     }
 }
+\f
+/* Macro expansion. */
+
+struct macro_expander
+  {
+    const struct macro_set *macros;
+    const struct macro *macro;
+    struct macro_tokens **args;
+    enum segmenter_mode segmenter_mode;
+    struct stringi_map *vars;
+    bool *expand;
+    bool *break_;
+    int nesting_countdown;
+  };
 
 /* Each argument to a macro function is one of:
 
@@ -882,21 +894,14 @@ struct parse_macro_function_ctx
   {
     const struct macro_token *input;
     size_t n_input;
-    int nesting_countdown;
-    const struct macro_set *macros;
-    const struct macro_call *mc;
+    const struct macro_expander *me;
     const struct macro_expansion_stack *stack;
-    struct string_map *vars;
-    bool *expand;
   };
 
 static void
-macro_expand (const struct macro_tokens *, int nesting_countdown,
-              const struct macro_set *,
-              const struct macro_call *, struct string_map *vars,
-              const struct macro_expansion_stack *stack,
-              bool *expand, bool *break_,
-              struct macro_tokens *exp);
+macro_expand (const struct macro_tokens *, const struct macro_expander *,
+              const struct macro_expansion_stack *,
+              struct macro_tokens *);
 
 static bool
 expand_macro_function (struct parse_macro_function_ctx *ctx,
@@ -919,14 +924,14 @@ parse_function_arg (struct parse_macro_function_ctx *ctx,
 {
   const struct macro_token *tokens = ctx->input;
   const struct token *token = &tokens[i].token;
-  if (token->type == T_MACRO_ID)
+  if (token->type == T_MACRO_ID && ctx->me->macro)
     {
       const struct macro_param *param = macro_find_parameter_by_name (
-        ctx->mc->macro, token->string);
+        ctx->me->macro, token->string);
       if (param)
         {
-          size_t param_idx = param - ctx->mc->macro->params;
-          const struct macro_tokens *marg = ctx->mc->args[param_idx];
+          size_t param_idx = param - ctx->me->macro->params;
+          const struct macro_tokens *marg = ctx->me->args[param_idx];
           for (size_t i = 0; i < marg->n; i++)
             {
               if (i)
@@ -938,12 +943,12 @@ parse_function_arg (struct parse_macro_function_ctx *ctx,
 
       if (is_bang_star (ctx->input, ctx->n_input, i))
         {
-          for (size_t i = 0; i < ctx->mc->macro->n_params; i++)
+          for (size_t i = 0; i < ctx->me->macro->n_params; i++)
             {
-              if (!ctx->mc->macro->params[i].positional)
+              if (!ctx->me->macro->params[i].positional)
                 break;
 
-              const struct macro_tokens *marg = ctx->mc->args[i];
+              const struct macro_tokens *marg = ctx->me->args[i];
               for (size_t j = 0; j < marg->n; j++)
                 {
                   if (i || j)
@@ -954,27 +959,20 @@ parse_function_arg (struct parse_macro_function_ctx *ctx,
           return 2;
         }
 
-      if (ctx->vars)
+      const char *value = stringi_map_find__ (ctx->me->vars,
+                                              token->string.string,
+                                              token->string.length);
+      if (value)
         {
-          const char *value = string_map_find__ (ctx->vars,
-                                                 token->string.string,
-                                                 token->string.length);
-          if (value)
-            {
-              ds_put_cstr (farg, value);
-              return 1;
-            }
+          ds_put_cstr (farg, value);
+          return 1;
         }
 
       struct parse_macro_function_ctx subctx = {
         .input = &ctx->input[i],
         .n_input = ctx->n_input - i,
-        .nesting_countdown = ctx->nesting_countdown,
-        .macros = ctx->macros,
-        .mc = ctx->mc,
+        .me = ctx->me,
         .stack = ctx->stack,
-        .vars = ctx->vars,
-        .expand = ctx->expand,
       };
       size_t subinput_consumed;
       if (expand_macro_function (&subctx, farg, &subinput_consumed))
@@ -1134,7 +1132,7 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
                                  input_consumed))
     {
       for (size_t i = 0; i < args.n; i++)
-        if (!unquote_string (args.strings[i], ctx->mc->segmenter_mode, output))
+        if (!unquote_string (args.strings[i], ctx->me->segmenter_mode, output))
           ds_put_cstr (output, args.strings[i]);
     }
   else if (parse_macro_function (ctx, &args, ss_cstr ("!HEAD"), 1, 1,
@@ -1142,10 +1140,10 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
     {
       struct string tmp;
       const char *s = unquote_string_in_place (args.strings[0],
-                                               ctx->mc->segmenter_mode, &tmp);
+                                               ctx->me->segmenter_mode, &tmp);
 
       struct macro_tokens mts = { .n = 0 };
-      macro_tokens_from_string__ (&mts, ss_cstr (s), ctx->mc->segmenter_mode,
+      macro_tokens_from_string__ (&mts, ss_cstr (s), ctx->me->segmenter_mode,
                                   ctx->stack);
       if (mts.n > 0)
         ds_put_substring (output, mts.mts[0].representation);
@@ -1162,7 +1160,7 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
   else if (parse_macro_function (ctx, &args, ss_cstr ("!QUOTE"), 1, 1,
                                  input_consumed))
     {
-      if (unquote_string (args.strings[0], ctx->mc->segmenter_mode, NULL))
+      if (unquote_string (args.strings[0], ctx->me->segmenter_mode, NULL))
         ds_put_cstr (output, args.strings[0]);
       else
         {
@@ -1210,10 +1208,10 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
     {
       struct string tmp;
       const char *s = unquote_string_in_place (args.strings[0],
-                                               ctx->mc->segmenter_mode, &tmp);
+                                               ctx->me->segmenter_mode, &tmp);
 
       struct macro_tokens mts = { .n = 0 };
-      macro_tokens_from_string__ (&mts, ss_cstr (s), ctx->mc->segmenter_mode,
+      macro_tokens_from_string__ (&mts, ss_cstr (s), ctx->me->segmenter_mode,
                                   ctx->stack);
       if (mts.n > 1)
         {
@@ -1226,7 +1224,7 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
   else if (parse_macro_function (ctx, &args, ss_cstr ("!UNQUOTE"), 1, 1,
                                  input_consumed))
     {
-      if (!unquote_string (args.strings[0], ctx->mc->segmenter_mode, output))
+      if (!unquote_string (args.strings[0], ctx->me->segmenter_mode, output))
         ds_put_cstr (output, args.strings[0]);
     }
   else if (parse_macro_function (ctx, &args, ss_cstr ("!UPCASE"), 1, 1,
@@ -1234,7 +1232,7 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
     {
       struct string tmp;
       const char *s = unquote_string_in_place (args.strings[0],
-                                               ctx->mc->segmenter_mode, &tmp);
+                                               ctx->me->segmenter_mode, &tmp);
       char *upper = utf8_to_upper (s);
       ds_put_cstr (output, upper);
       free (upper);
@@ -1245,14 +1243,15 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
     {
       struct macro_tokens mts = { .n = 0 };
       macro_tokens_from_string__ (&mts, ss_cstr (args.strings[0]),
-                                  ctx->mc->segmenter_mode, ctx->stack);
+                                  ctx->me->segmenter_mode, ctx->stack);
       struct macro_tokens exp = { .n = 0 };
-      macro_expand (&mts, ctx->nesting_countdown - 1,
-                    ctx->macros, ctx->mc, ctx->vars,
+      struct macro_expander subme = *ctx->me;
+      subme.break_ = NULL;
+      macro_expand (&mts, &subme,
                     &(struct macro_expansion_stack) {
                       .name = "!EVAL",
                       .next = ctx->stack,
-                    }, ctx->expand, NULL, &exp);
+                    }, &exp);
       macro_tokens_to_representation (&exp, output, NULL, NULL);
       macro_tokens_uninit (&exp);
       macro_tokens_uninit (&mts);
@@ -1271,22 +1270,14 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
   return true;
 }
 
-struct expr_context
-  {
-    int nesting_countdown;
-    const struct macro_set *macros;
-    const struct macro_call *mc;
-    const struct macro_expansion_stack *stack;
-    struct string_map *vars;
-    bool *expand;
-  };
-
-static char *macro_evaluate_or (const struct expr_context *ctx,
+static char *macro_evaluate_or (const struct macro_expander *me,
+                                const struct macro_expansion_stack *stack,
                                 const struct macro_token **tokens,
                                 const struct macro_token *end);
 
 static char *
-macro_evaluate_literal (const struct expr_context *ctx,
+macro_evaluate_literal (const struct macro_expander *me,
+                        const struct macro_expansion_stack *stack,
                         const struct macro_token **tokens,
                         const struct macro_token *end)
 {
@@ -1296,13 +1287,13 @@ macro_evaluate_literal (const struct expr_context *ctx,
   if (p->token.type == T_LPAREN)
     {
       p++;
-      char *value = macro_evaluate_or (ctx, &p, end);
+      char *value = macro_evaluate_or (me, stack, &p, end);
       if (!value)
         return NULL;
       if (p >= end || p->token.type != T_RPAREN)
         {
           free (value);
-          macro_error (ctx->stack, p < end ? p : NULL,
+          macro_error (stack, p < end ? p : NULL,
                        _("Expecting ')' in macro expression."));
           return NULL;
         }
@@ -1312,25 +1303,21 @@ macro_evaluate_literal (const struct expr_context *ctx,
     }
   else if (p->token.type == T_RPAREN)
     {
-      macro_error (ctx->stack, p, _("Expecting literal or function invocation "
-                                    "in macro expression."));
+      macro_error (stack, p, _("Expecting literal or function invocation "
+                               "in macro expression."));
       return NULL;
     }
 
   struct parse_macro_function_ctx fctx = {
     .input = p,
     .n_input = end - p,
-    .nesting_countdown = ctx->nesting_countdown,
-    .macros = ctx->macros,
-    .mc = ctx->mc,
-    .stack = ctx->stack,
-    .vars = ctx->vars,
-    .expand = ctx->expand,
+    .me = me,
+    .stack = stack,
   };
   struct string function_output = DS_EMPTY_INITIALIZER;
   size_t function_consumed = parse_function_arg (&fctx, 0, &function_output);
   struct string unquoted = DS_EMPTY_INITIALIZER;
-  if (unquote_string (ds_cstr (&function_output), ctx->mc->segmenter_mode,
+  if (unquote_string (ds_cstr (&function_output), me->segmenter_mode,
                       &unquoted))
     {
       ds_swap (&function_output, &unquoted);
@@ -1380,12 +1367,13 @@ parse_relational_op (const struct macro_token *mt)
 }
 
 static char *
-macro_evaluate_relational (const struct expr_context *ctx,
+macro_evaluate_relational (const struct macro_expander *me,
+                           const struct macro_expansion_stack *stack,
                            const struct macro_token **tokens,
                            const struct macro_token *end)
 {
   const struct macro_token *p = *tokens;
-  char *lhs = macro_evaluate_literal (ctx, &p, end);
+  char *lhs = macro_evaluate_literal (me, stack, &p, end);
   if (!lhs)
     return NULL;
 
@@ -1397,7 +1385,7 @@ macro_evaluate_relational (const struct expr_context *ctx,
     }
   p++;
 
-  char *rhs = macro_evaluate_literal (ctx, &p, end);
+  char *rhs = macro_evaluate_literal (me, stack, &p, end);
   if (!rhs)
     {
       free (lhs);
@@ -1405,9 +1393,9 @@ macro_evaluate_relational (const struct expr_context *ctx,
     }
 
   struct string lhs_tmp, rhs_tmp;
-  int cmp = strcmp (unquote_string_in_place (lhs, ctx->mc->segmenter_mode,
+  int cmp = strcmp (unquote_string_in_place (lhs, me->segmenter_mode,
                                              &lhs_tmp),
-                    unquote_string_in_place (rhs, ctx->mc->segmenter_mode,
+                    unquote_string_in_place (rhs, me->segmenter_mode,
                                              &rhs_tmp));
   ds_destroy (&lhs_tmp);
   ds_destroy (&rhs_tmp);
@@ -1427,7 +1415,8 @@ macro_evaluate_relational (const struct expr_context *ctx,
 }
 
 static char *
-macro_evaluate_not (const struct expr_context *ctx,
+macro_evaluate_not (const struct macro_expander *me,
+                    const struct macro_expansion_stack *stack,
                     const struct macro_token **tokens,
                     const struct macro_token *end)
 {
@@ -1442,7 +1431,7 @@ macro_evaluate_not (const struct expr_context *ctx,
       negations++;
     }
 
-  char *operand = macro_evaluate_relational (ctx, &p, end);
+  char *operand = macro_evaluate_relational (me, stack, &p, end);
   if (!operand || !negations)
     {
       *tokens = p;
@@ -1456,12 +1445,13 @@ macro_evaluate_not (const struct expr_context *ctx,
 }
 
 static char *
-macro_evaluate_and (const struct expr_context *ctx,
+macro_evaluate_and (const struct macro_expander *me,
+                    const struct macro_expansion_stack *stack,
                     const struct macro_token **tokens,
                     const struct macro_token *end)
 {
   const struct macro_token *p = *tokens;
-  char *lhs = macro_evaluate_not (ctx, &p, end);
+  char *lhs = macro_evaluate_not (me, stack, &p, end);
   if (!lhs)
     return NULL;
 
@@ -1470,7 +1460,7 @@ macro_evaluate_and (const struct expr_context *ctx,
              || ss_equals (p->representation, ss_cstr ("&"))))
     {
       p++;
-      char *rhs = macro_evaluate_not (ctx, &p, end);
+      char *rhs = macro_evaluate_not (me, stack, &p, end);
       if (!rhs)
         {
           free (lhs);
@@ -1487,12 +1477,13 @@ macro_evaluate_and (const struct expr_context *ctx,
 }
 
 static char *
-macro_evaluate_or (const struct expr_context *ctx,
+macro_evaluate_or (const struct macro_expander *me,
+                   const struct macro_expansion_stack *stack,
                    const struct macro_token **tokens,
                    const struct macro_token *end)
 {
   const struct macro_token *p = *tokens;
-  char *lhs = macro_evaluate_and (ctx, &p, end);
+  char *lhs = macro_evaluate_and (me, stack, &p, end);
   if (!lhs)
     return NULL;
 
@@ -1501,7 +1492,7 @@ macro_evaluate_or (const struct expr_context *ctx,
              || ss_equals (p->representation, ss_cstr ("|"))))
     {
       p++;
-      char *rhs = macro_evaluate_and (ctx, &p, end);
+      char *rhs = macro_evaluate_and (me, stack, &p, end);
       if (!rhs)
         {
           free (lhs);
@@ -1519,39 +1510,24 @@ macro_evaluate_or (const struct expr_context *ctx,
 
 static char *
 macro_evaluate_expression (const struct macro_token **tokens, size_t n_tokens,
-                           int nesting_countdown,
-                           const struct macro_set *macros,
-                           const struct macro_call *mc,
-                           const struct macro_expansion_stack *stack,
-                           struct string_map *vars, bool *expand)
+                           const struct macro_expander *me,
+                           const struct macro_expansion_stack *stack)
 {
-  const struct expr_context ctx = {
-    .nesting_countdown = nesting_countdown,
-    .macros = macros,
-    .mc = mc,
-    .stack = stack,
-    .vars = vars,
-    .expand = expand,
-  };
-  return macro_evaluate_or (&ctx, tokens, *tokens + n_tokens);
+  return macro_evaluate_or (me, stack, tokens, *tokens + n_tokens);
 }
 
 static bool
 macro_evaluate_number (const struct macro_token **tokens, size_t n_tokens,
-                       int nesting_countdown,
-                       const struct macro_set *macros,
-                       const struct macro_call *mc,
+                       const struct macro_expander *me,
                        const struct macro_expansion_stack *stack,
-                       struct string_map *vars,
-                       bool *expand, double *number)
+                       double *number)
 {
-  char *s = macro_evaluate_expression (tokens, n_tokens, nesting_countdown,
-                                       macros, mc, stack, vars, expand);
+  char *s = macro_evaluate_expression (tokens, n_tokens, me, stack);
   if (!s)
     return false;
 
   struct macro_tokens mts = { .n = 0 };
-  macro_tokens_from_string__ (&mts, ss_cstr (s), mc->segmenter_mode, stack);
+  macro_tokens_from_string__ (&mts, ss_cstr (s), me->segmenter_mode, stack);
   if (mts.n != 1 || !token_is_number (&mts.mts[0].token))
     {
       macro_error (stack, mts.n > 0 ? &mts.mts[0] : NULL,
@@ -1593,11 +1569,9 @@ find_ifend_clause (const struct macro_token *p, const struct macro_token *end)
 
 static size_t
 macro_expand_if (const struct macro_token *tokens, size_t n_tokens,
-                 int nesting_countdown, const struct macro_set *macros,
-                 const struct macro_call *mc,
+                 const struct macro_expander *me,
                  const struct macro_expansion_stack *stack,
-                 struct string_map *vars,
-                 bool *expand, bool *break_, struct macro_tokens *exp)
+                 struct macro_tokens *exp)
 {
   const struct macro_token *p = tokens;
   const struct macro_token *end = tokens + n_tokens;
@@ -1606,10 +1580,7 @@ macro_expand_if (const struct macro_token *tokens, size_t n_tokens,
     return 0;
 
   p++;
-  char *result = macro_evaluate_expression (&p, end - p,
-                                            nesting_countdown,
-                                            macros, mc,
-                                            stack, vars, expand);
+  char *result = macro_evaluate_expression (&p, end - p, me, stack);
   if (!result)
     return 0;
   bool b = strcmp (result, "0");
@@ -1676,22 +1647,19 @@ macro_expand_if (const struct macro_token *tokens, size_t n_tokens,
         .mts = CONST_CAST (struct macro_token *, start),
         .n = n,
       };
-      macro_expand (&mts, nesting_countdown, macros, mc, vars,
-                    &(struct macro_expansion_stack) {
+      macro_expand (&mts, me, &(struct macro_expansion_stack) {
                       .name = "!IF",
                       .next = stack,
                     },
-                    expand, break_, exp);
+                    exp);
     }
   return (end_if + 1) - tokens;
 }
 
 static size_t
 macro_parse_let (const struct macro_token *tokens, size_t n_tokens,
-                 int nesting_countdown, const struct macro_set *macros,
-                 const struct macro_call *mc,
-                 const struct macro_expansion_stack *stack,
-                 struct string_map *vars, bool *expand)
+                 const struct macro_expander *me,
+                 const struct macro_expansion_stack *stack)
 {
   const struct macro_token *p = tokens;
   const struct macro_token *end = tokens + n_tokens;
@@ -1708,7 +1676,7 @@ macro_parse_let (const struct macro_token *tokens, size_t n_tokens,
     }
   const struct substring var_name = p->token.string;
   if (is_macro_keyword (var_name)
-      || macro_find_parameter_by_name (mc->macro, var_name))
+      || (me->macro && macro_find_parameter_by_name (me->macro, var_name)))
     {
       macro_error (stack, p < end ? p : NULL,
                    _("Cannot use argument name or macro keyword "
@@ -1726,12 +1694,11 @@ macro_parse_let (const struct macro_token *tokens, size_t n_tokens,
     }
   p++;
 
-  char *value = macro_evaluate_expression (&p, end - p, nesting_countdown,
-                                           macros, mc, stack, vars, expand);
+  char *value = macro_evaluate_expression (&p, end - p, me, stack);
   if (!value)
     return 0;
 
-  string_map_replace_nocopy (vars, ss_xstrdup (var_name), value);
+  stringi_map_replace_nocopy (me->vars, ss_xstrdup (var_name), value);
   return p - tokens;
 }
 
@@ -1760,11 +1727,9 @@ find_doend (const struct macro_expansion_stack *stack,
 
 static size_t
 macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
-                 int nesting_countdown, const struct macro_set *macros,
-                 const struct macro_call *mc,
+                 const struct macro_expander *me,
                  const struct macro_expansion_stack *stack,
-                 struct string_map *vars,
-                 bool *expand, struct macro_tokens *exp)
+                 struct macro_tokens *exp)
 {
   const struct macro_token *p = tokens;
   const struct macro_token *end = tokens + n_tokens;
@@ -1781,7 +1746,7 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
     }
   const struct substring var_name = p->token.string;
   if (is_macro_keyword (var_name)
-      || macro_find_parameter_by_name (mc->macro, var_name))
+      || (me->macro && macro_find_parameter_by_name (me->macro, var_name)))
     {
       macro_error (stack, p, _("Cannot use argument name or macro "
                                "keyword as !DO variable."));
@@ -1797,14 +1762,12 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
       && ss_equals_case (p->token.string, ss_cstr ("!IN")))
     {
       p++;
-      char *list = macro_evaluate_expression (&p, end - p, nesting_countdown,
-                                              macros, mc, &next_stack, vars,
-                                              expand);
+      char *list = macro_evaluate_expression (&p, end - p, me, &next_stack);
       if (!list)
         return 0;
 
       struct macro_tokens items = { .n = 0 };
-      macro_tokens_from_string__ (&items, ss_cstr (list), mc->segmenter_mode,
+      macro_tokens_from_string__ (&items, ss_cstr (list), me->segmenter_mode,
                                   stack);
       free (list);
 
@@ -1819,7 +1782,12 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
         .mts = CONST_CAST (struct macro_token *, p),
         .n = do_end - p
       };
-      for (size_t i = 0; i < items.n; i++)
+
+      bool break_ = false;
+      struct macro_expander subme = *me;
+      subme.break_ = &break_;
+
+      for (size_t i = 0; i < items.n && !break_; i++)
         {
           if (i >= miterate)
             {
@@ -1830,14 +1798,10 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
                            miterate);
               break;
             }
-          string_map_replace_nocopy (vars, ss_xstrdup (var_name),
-                                     ss_xstrdup (items.mts[i].representation));
+          stringi_map_replace_nocopy (me->vars, ss_xstrdup (var_name),
+                                      ss_xstrdup (items.mts[i].representation));
 
-          bool break_ = false;
-          macro_expand (&inner, nesting_countdown, macros,
-                        mc, vars, &next_stack, expand, &break_, exp);
-          if (break_)
-            break;
+          macro_expand (&inner, &subme, &next_stack, exp);
         }
       return do_end - tokens + 1;
     }
@@ -1845,9 +1809,7 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
     {
       p++;
       double first;
-      if (!macro_evaluate_number (&p, end - p, nesting_countdown,
-                                  macros, mc, &next_stack,
-                                  vars, expand, &first))
+      if (!macro_evaluate_number (&p, end - p, me, &next_stack, &first))
         return 0;
 
       if (p >= end || p->token.type != T_MACRO_ID
@@ -1860,9 +1822,7 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
       p++;
 
       double last;
-      if (!macro_evaluate_number (&p, end - p, nesting_countdown,
-                                  macros, mc, &next_stack,
-                                  vars, expand, &last))
+      if (!macro_evaluate_number (&p, end - p, me, &next_stack, &last))
         return 0;
 
       double by = 1.0;
@@ -1870,9 +1830,7 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
           && ss_equals_case (p->token.string, ss_cstr ("!BY")))
         {
           p++;
-          if (!macro_evaluate_number (&p, end - p, nesting_countdown,
-                                      macros, mc, &next_stack,
-                                      vars, expand, &by))
+          if (!macro_evaluate_number (&p, end - p, me, &next_stack, &by))
             return 0;
 
           if (by == 0.0)
@@ -1890,11 +1848,15 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
         .n = do_end - p
       };
 
+      bool break_ = false;
+      struct macro_expander subme = *me;
+      subme.break_ = &break_;
+
       if ((by > 0 && first <= last) || (by < 0 && first >= last))
         {
           int i = 0;
           for (double index = first;
-               by > 0 ? (index <= last) : (index >= last);
+               by > 0 ? (index <= last) : (index >= last) && !break_;
                index += by)
             {
               if (i++ > miterate)
@@ -1909,15 +1871,10 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
 
               char index_s[DBL_BUFSIZE_BOUND];
               c_dtoastr (index_s, sizeof index_s, 0, 0, index);
-              string_map_replace_nocopy (vars, ss_xstrdup (var_name),
-                                         xstrdup (index_s));
-
-              bool break_ = false;
-              macro_expand (&inner, nesting_countdown,
-                            macros, mc, vars, &next_stack, expand, &break_,
-                            exp);
-              if (break_)
-                break;
+              stringi_map_replace_nocopy (me->vars, ss_xstrdup (var_name),
+                                          xstrdup (index_s));
+
+              macro_expand (&inner, &subme, &next_stack, exp);
             }
         }
 
@@ -1932,13 +1889,12 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
 }
 
 static void
-macro_expand (const struct macro_tokens *mts, int nesting_countdown,
-              const struct macro_set *macros,
-              const struct macro_call *mc, struct string_map *vars,
+macro_expand (const struct macro_tokens *mts,
+              const struct macro_expander *me,
               const struct macro_expansion_stack *stack,
-              bool *expand, bool *break_, struct macro_tokens *exp)
+              struct macro_tokens *exp)
 {
-  if (nesting_countdown <= 0)
+  if (me->nesting_countdown <= 0)
     {
       macro_error (stack, NULL, _("Maximum nesting level %d exceeded.  "
                                   "(Use SET MNEST to change the limit.)"),
@@ -1948,28 +1904,37 @@ macro_expand (const struct macro_tokens *mts, int nesting_countdown,
       return;
     }
 
-  struct string_map own_vars = STRING_MAP_INITIALIZER (own_vars);
-  if (!vars)
-    vars = &own_vars;
-
-  for (size_t i = 0; i < mts->n && (!break_ || !*break_); i++)
+  for (size_t i = 0; i < mts->n && (!me->break_ || !*me->break_); i++)
     {
       const struct macro_token *mt = &mts->mts[i];
       const struct token *token = &mt->token;
-      if (token->type == T_MACRO_ID && mc)
+      if (token->type == T_MACRO_ID && me->macro)
         {
           const struct macro_param *param = macro_find_parameter_by_name (
-            mc->macro, token->string);
+            me->macro, token->string);
           if (param)
             {
-              const struct macro_tokens *arg = mc->args[param - mc->macro->params];
-              if (*expand && param->expand_arg)
-                macro_expand (arg, nesting_countdown,
-                              macros, NULL, NULL,
-                              &(struct macro_expansion_stack) {
-                                .name = param->name,
-                                .next = stack,
-                              }, expand, break_, exp);
+              const struct macro_tokens *arg
+                = me->args[param - me->macro->params];
+              if (*me->expand && param->expand_arg)
+                {
+                  struct stringi_map vars = STRINGI_MAP_INITIALIZER (vars);
+                  struct macro_expander subme = {
+                    .macros = me->macros,
+                    .macro = NULL,
+                    .args = NULL,
+                    .segmenter_mode = me->segmenter_mode,
+                    .expand = me->expand,
+                    .break_ = NULL,
+                    .vars = &vars,
+                    .nesting_countdown = me->nesting_countdown,
+                  };
+                  macro_expand (arg, &subme, &(struct macro_expansion_stack) {
+                                  .name = param->name,
+                                  .next = stack,
+                                }, exp);
+                  stringi_map_destroy (&vars);
+                }
               else
                 for (size_t i = 0; i < arg->n; i++)
                   macro_tokens_add (exp, &arg->mts[i]);
@@ -1978,20 +1943,33 @@ macro_expand (const struct macro_tokens *mts, int nesting_countdown,
 
           if (is_bang_star (mts->mts, mts->n, i))
             {
-              for (size_t j = 0; j < mc->macro->n_params; j++)
+              for (size_t j = 0; j < me->macro->n_params; j++)
                 {
-                  const struct macro_param *param = &mc->macro->params[j];
+                  const struct macro_param *param = &me->macro->params[j];
                   if (!param->positional)
                     break;
 
-                  const struct macro_tokens *arg = mc->args[j];
-                  if (*expand && param->expand_arg)
-                    macro_expand (arg, nesting_countdown,
-                                  macros, NULL, NULL,
-                                  &(struct macro_expansion_stack) {
-                                    .name = "!*",
-                                    .next = stack,
-                                  }, expand, break_, exp);
+                  const struct macro_tokens *arg = me->args[j];
+                  if (*me->expand && param->expand_arg)
+                    {
+                      struct stringi_map vars = STRINGI_MAP_INITIALIZER (vars);
+                      struct macro_expander subme = {
+                        .macros = me->macros,
+                        .macro = NULL,
+                        .args = NULL,
+                        .segmenter_mode = me->segmenter_mode,
+                        .expand = me->expand,
+                        .break_ = NULL,
+                        .vars = &vars,
+                        .nesting_countdown = me->nesting_countdown,
+                      };
+                      macro_expand (arg, &subme,
+                                    &(struct macro_expansion_stack) {
+                                      .name = "!*",
+                                      .next = stack,
+                                    }, exp);
+                      stringi_map_destroy (&vars);
+                    }
                   else
                     for (size_t k = 0; k < arg->n; k++)
                       macro_tokens_add (exp, &arg->mts[k]);
@@ -2000,10 +1978,8 @@ macro_expand (const struct macro_tokens *mts, int nesting_countdown,
               continue;
             }
 
-          size_t n = macro_expand_if (&mts->mts[i], mts->n - i,
-                                      nesting_countdown,
-                                      macros, mc, stack,
-                                      vars, expand, break_, exp);
+          size_t n = macro_expand_if (&mts->mts[i], mts->n - i, me, stack,
+                                      exp);
           if (n > 0)
             {
               i += n - 1;
@@ -2011,45 +1987,58 @@ macro_expand (const struct macro_tokens *mts, int nesting_countdown,
             }
         }
 
-      if (token->type == T_MACRO_ID && vars)
+      if (token->type == T_MACRO_ID)
         {
-          const char *value = string_map_find__ (vars, token->string.string,
-                                                 token->string.length);
+          const char *value = stringi_map_find__ (me->vars,
+                                                  token->string.string,
+                                                  token->string.length);
           if (value)
             {
               macro_tokens_from_string__ (exp, ss_cstr (value),
-                                          mc->segmenter_mode, stack);
+                                          me->segmenter_mode, stack);
               continue;
             }
         }
 
-      if (*expand)
+      if (*me->expand)
         {
-          struct macro_call *subme;
-          int retval = macro_call_create (macros, token, &subme);
+          struct macro_call *submc;
+          int retval = macro_call_create (me->macros, token, &submc);
           for (size_t j = 1; !retval; j++)
             {
-              const struct macro_token endcmd = { .token = { .type = T_ENDCMD } };
+              const struct macro_token endcmd
+                = { .token = { .type = T_ENDCMD } };
               retval = macro_call_add (
-                subme, i + j < mts->n ? &mts->mts[i + j] : &endcmd);
+                submc, i + j < mts->n ? &mts->mts[i + j] : &endcmd);
             }
           if (retval > 0)
             {
               i += retval - 1;
-              macro_expand (&subme->macro->body, nesting_countdown - 1,
-                            macros, subme, NULL,
+              struct stringi_map vars = STRINGI_MAP_INITIALIZER (vars);
+              struct macro_expander subme = {
+                .macros = submc->macros,
+                .macro = submc->macro,
+                .args = submc->args,
+                .segmenter_mode = me->segmenter_mode,
+                .expand = me->expand,
+                .break_ = NULL,
+                .vars = &vars,
+                .nesting_countdown = me->nesting_countdown - 1,
+              };
+              macro_expand (&submc->macro->body, &subme,
                             &(struct macro_expansion_stack) {
-                              .name = subme->macro->name,
-                              .file_name = subme->macro->file_name,
-                              .first_line = subme->macro->first_line,
-                              .last_line = subme->macro->last_line,
+                              .name = submc->macro->name,
+                              .file_name = submc->macro->file_name,
+                              .first_line = submc->macro->first_line,
+                              .last_line = submc->macro->last_line,
                               .next = stack,
-                            }, expand, break_, exp);
-              macro_call_destroy (subme);
+                            }, exp);
+              macro_call_destroy (submc);
+              stringi_map_destroy (&vars);
               continue;
             }
 
-          macro_call_destroy (subme);
+          macro_call_destroy (submc);
         }
 
       if (token->type != T_MACRO_ID)
@@ -2060,11 +2049,11 @@ macro_expand (const struct macro_tokens *mts, int nesting_countdown,
 
       if (ss_equals_case (token->string, ss_cstr ("!break")))
         {
-          if (!break_)
+          if (!me->break_)
             macro_error (stack, mt, _("!BREAK outside !DO."));
           else
             {
-              *break_ = true;
+              *me->break_ = true;
               break;
             }
         }
@@ -2072,12 +2061,8 @@ macro_expand (const struct macro_tokens *mts, int nesting_countdown,
       struct parse_macro_function_ctx ctx = {
         .input = &mts->mts[i],
         .n_input = mts->n - i,
-        .nesting_countdown = nesting_countdown,
-        .macros = macros,
-        .mc = mc,
+        .me = me,
         .stack = stack,
-        .vars = vars,
-        .expand = expand,
       };
       struct string function_output = DS_EMPTY_INITIALIZER;
       size_t function_consumed;
@@ -2086,24 +2071,20 @@ macro_expand (const struct macro_tokens *mts, int nesting_countdown,
           i += function_consumed - 1;
 
           macro_tokens_from_string__ (exp, function_output.ss,
-                                      mc->segmenter_mode, stack);
+                                      me->segmenter_mode, stack);
           ds_destroy (&function_output);
 
           continue;
         }
 
-      size_t n = macro_parse_let (&mts->mts[i], mts->n - i,
-                                  nesting_countdown,
-                                  macros, mc, stack, vars, expand);
+      size_t n = macro_parse_let (&mts->mts[i], mts->n - i, me, stack);
       if (n > 0)
         {
           i += n - 1;
           continue;
         }
 
-      n = macro_expand_do (&mts->mts[i], mts->n - i,
-                           nesting_countdown, macros, mc, stack,
-                           vars, expand, exp);
+      n = macro_expand_do (&mts->mts[i], mts->n - i, me, stack, exp);
       if (n > 0)
         {
           i += n - 1;
@@ -2111,14 +2092,12 @@ macro_expand (const struct macro_tokens *mts, int nesting_countdown,
         }
 
       if (ss_equals_case (token->string, ss_cstr ("!onexpand")))
-        *expand = true;
+        *me->expand = true;
       else if (ss_equals_case (token->string, ss_cstr ("!offexpand")))
-        *expand = false;
+        *me->expand = false;
       else
         macro_tokens_add (exp, mt);
     }
-  if (vars == &own_vars)
-    string_map_destroy (&own_vars);
 }
 
 void
@@ -2126,16 +2105,28 @@ macro_call_expand (struct macro_call *mc, enum segmenter_mode segmenter_mode,
                    struct macro_tokens *exp)
 {
   assert (mc->state == MC_FINISHED);
-  mc->segmenter_mode = segmenter_mode;
 
   bool expand = true;
+  struct stringi_map vars = STRINGI_MAP_INITIALIZER (vars);
+  struct macro_expander me = {
+    .macros = mc->macros,
+    .macro = mc->macro,
+    .args = mc->args,
+    .segmenter_mode = segmenter_mode,
+    .expand = &expand,
+    .break_ = NULL,
+    .vars = &vars,
+    .nesting_countdown = settings_get_mnest (),
+  };
+
   struct macro_expansion_stack stack = {
     .name = mc->macro->name,
     .file_name = mc->macro->file_name,
     .first_line = mc->macro->first_line,
     .last_line = mc->macro->last_line,
   };
-  macro_expand (&mc->macro->body, settings_get_mnest (),
-                mc->macros, mc, NULL, &stack, &expand, NULL, exp);
+  macro_expand (&mc->macro->body, &me, &stack, exp);
+
+  stringi_map_destroy (&vars);
 }