More error reporting improvemnts.
[pspp] / src / language / lexer / macro.c
index 786aeea54a3429645dff5094fd644a37348e0bd2..8678a0bcdb5f808bcf2f1f872356624706a5b221 100644 (file)
@@ -23,6 +23,7 @@
 #include <stdlib.h>
 
 #include "data/settings.h"
+#include "language/lexer/lexer.h"
 #include "language/lexer/segment.h"
 #include "language/lexer/scan.h"
 #include "libpspp/assertion.h"
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
+struct macro_expansion_stack
+  {
+    const struct macro_expansion_stack *next;
+    const char *name;
+    const char *file_name;
+    int first_line;
+    int last_line;
+  };
+
+static void PRINTF_FORMAT (3, 4)
+macro_error (const struct macro_expansion_stack *stack,
+             const struct macro_token *mt,
+             const char *format, ...)
+{
+  struct msg_stack **ms = NULL;
+  size_t allocated_ms = 0;
+  size_t n_ms = 0;
+
+  for (const struct macro_expansion_stack *p = stack; p; p = p->next)
+    {
+      if (n_ms >= allocated_ms)
+        ms = x2nrealloc (ms, &allocated_ms, sizeof *ms);
+
+      /* TRANSLATORS: These strings are used for explaining the context of an
+         error.  The "While expanding" message appears first, followed by zero
+         or more of the "inside expansion" messages.  `innermost',
+         `next_inner`, etc., are names of macros, and `foobar' is a piece of
+         PSPP syntax:
+
+         foo.sps:12: At `foobar' in the expansion of 'innermost',
+         foo.sps:23: inside the expansion of 'next_inner',
+         foo.sps:34: inside the expansion of 'next_inner2',
+         foo.sps:45: inside the expansion of 'outermost',
+         foo.sps:76: This is the actual error message. */
+      char *description;
+      if (p == stack)
+        {
+          if (mt && mt->representation.length)
+            {
+              char syntax[64];
+              lex_ellipsize (mt->representation, syntax, sizeof syntax);
+              description = xasprintf (_("At `%s' in the expansion of `%s',"),
+                                       syntax, p->name);
+            }
+          else
+            description = xasprintf (_("In the expansion of `%s',"), p->name);
+        }
+      else
+        description = xasprintf (_("inside the expansion of `%s',"), p->name);
+
+      ms[n_ms] = xmalloc (sizeof *ms[n_ms]);
+      *ms[n_ms] = (struct msg_stack) {
+        .location = {
+          .file_name = xstrdup_if_nonnull (p->file_name),
+          .first_line = p->first_line,
+          .last_line = p->last_line,
+        },
+        .description = description,
+      };
+      n_ms++;
+    }
+
+  va_list args;
+  va_start (args, format);
+  char *s = xvasprintf (format, args);
+  va_end (args);
+
+  struct msg *m = xmalloc (sizeof *m);
+  *m = (struct msg) {
+    .category = MSG_C_SYNTAX,
+    .severity = MSG_S_ERROR,
+    .stack = ms,
+    .n_stack = n_ms,
+    .text = s,
+  };
+  msg_emit (m);
+}
+
 void
 macro_token_copy (struct macro_token *dst, const struct macro_token *src)
 {
@@ -129,9 +208,10 @@ macro_tokens_add (struct macro_tokens *mts, const struct macro_token *mt)
   macro_token_copy (macro_tokens_add_uninit (mts), mt);
 }
 
-void
-macro_tokens_from_string (struct macro_tokens *mts, const struct substring src,
-                          enum segmenter_mode mode)
+static void
+macro_tokens_from_string__ (struct macro_tokens *mts, const struct substring src,
+                            enum segmenter_mode mode,
+                            const struct macro_expansion_stack *stack)
 {
   struct state
     {
@@ -179,23 +259,35 @@ macro_tokens_from_string (struct macro_tokens *mts, const struct substring src,
         }
 
       /* We have a token in 'token'. */
+      mt.representation.length = state.body.string - mt.representation.string;
       if (is_scan_type (token->type))
         {
           if (token->type != SCAN_SKIP)
             {
-              printf ("error\n");
-              /* XXX report error */
+              char *s = scan_token_to_error (token);
+              if (stack)
+                {
+                  mt.token.type = T_STRING;
+                  macro_error (stack, &mt, "%s", s);
+                }
+              else
+                msg (SE, "%s", s);
+              free (s);
             }
         }
       else
-        {
-          mt.representation.length = state.body.string - mt.representation.string;
-          macro_tokens_add (mts, &mt);
-        }
+        macro_tokens_add (mts, &mt);
       token_uninit (token);
     }
 }
 
+void
+macro_tokens_from_string (struct macro_tokens *mts, const struct substring src,
+                          enum segmenter_mode mode)
+{
+  macro_tokens_from_string__ (mts, src, mode, NULL);
+}
+
 void
 macro_tokens_print (const struct macro_tokens *mts, FILE *stream)
 {
@@ -328,6 +420,7 @@ macro_destroy (struct macro *m)
     return;
 
   free (m->name);
+  free (m->file_name);
   for (size_t i = 0; i < m->n_params; i++)
     {
       struct macro_param *p = &m->params[i];
@@ -756,17 +849,21 @@ struct parse_macro_function_ctx
     const struct macro_token *input;
     size_t n_input;
     int nesting_countdown;
+    enum segmenter_mode segmenter_mode;
     const struct macro_set *macros;
     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 *,
+macro_expand (const struct macro_tokens *, int nesting_countdown,
+              enum segmenter_mode segmenter_mode, const struct macro_set *,
               const struct macro_expander *, struct string_map *vars,
-              bool *expand, bool *break_, struct macro_tokens *exp);
+              const struct macro_expansion_stack *stack,
+              bool *expand, bool *break_,
+              struct macro_tokens *exp);
 
 static bool
 expand_macro_function (struct parse_macro_function_ctx *ctx,
@@ -840,8 +937,10 @@ parse_function_arg (struct parse_macro_function_ctx *ctx,
         .input = &ctx->input[i],
         .n_input = ctx->n_input - i,
         .nesting_countdown = ctx->nesting_countdown,
+        .segmenter_mode = ctx->segmenter_mode,
         .macros = ctx->macros,
         .me = ctx->me,
+        .stack = ctx->stack,
         .vars = ctx->vars,
         .expand = ctx->expand,
       };
@@ -871,7 +970,8 @@ parse_macro_function (struct parse_macro_function_ctx *ctx,
 
   if (n_tokens < 2 || tokens[1].token.type != T_LPAREN)
     {
-      printf ("`(' expected following %s'\n", function.string);
+      macro_error (ctx->stack, n_tokens > 1 ? &tokens[1] : NULL,
+                   _("`(' expected following %s."), function.string);
       return false;
     }
 
@@ -886,7 +986,9 @@ parse_macro_function (struct parse_macro_function_ctx *ctx,
           *input_consumed = i + 1;
           if (args->n < min_args || args->n > max_args)
             {
-              printf ("Wrong number of arguments to %s.\n", function.string);
+              macro_error (ctx->stack, &tokens[i],
+                           _("Wrong number of arguments to macro function %s."),
+                           function.string);
               goto error;
             }
           return true;
@@ -905,14 +1007,16 @@ parse_macro_function (struct parse_macro_function_ctx *ctx,
         i++;
       else if (tokens[i].token.type != T_RPAREN)
         {
-          printf ("Expecting `,' or `)' in %s invocation.", function.string);
+          macro_error (ctx->stack, &tokens[i],
+                       _("`,' or `)' expected in call to macro function %s."),
+                       function.string);
           goto error;
         }
     }
 
 unexpected_end:
-  printf ("Missing closing parenthesis in arguments to %s.\n",
-          function.string);
+  macro_error (ctx->stack, NULL, _("Missing `)' in call to macro function %s."),
+               function.string);
   /* Fall through. */
 error:
   string_array_destroy (args);
@@ -920,11 +1024,11 @@ error:
 }
 
 static bool
-unquote_string (const char *s, struct string *content)
+unquote_string (const char *s, enum segmenter_mode segmenter_mode,
+                struct string *content)
 {
   struct string_lexer slex;
-  string_lexer_init (&slex, s, strlen (s), SEG_MODE_INTERACTIVE /* XXX */,
-                     true);
+  string_lexer_init (&slex, s, strlen (s), segmenter_mode, true);
 
   struct token token1;
   if (!string_lexer_next (&slex, &token1))
@@ -950,10 +1054,11 @@ unquote_string (const char *s, struct string *content)
 }
 
 static const char *
-unquote_string_in_place (const char *s, struct string *tmp)
+unquote_string_in_place (const char *s, enum segmenter_mode segmenter_mode,
+                         struct string *tmp)
 {
   ds_init_empty (tmp);
-  return unquote_string (s, tmp) ? ds_cstr (tmp) : s;
+  return unquote_string (s, segmenter_mode, tmp) ? ds_cstr (tmp) : s;
 }
 
 static bool
@@ -984,7 +1089,9 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
       int n;
       if (!parse_integer (args.strings[0], &n))
         {
-          printf ("argument to !BLANKS must be non-negative integer (not \"%s\")\n", args.strings[0]);
+          macro_error (ctx->stack, NULL,
+                       _("Argument to !BLANKS must be non-negative integer "
+                         "(not \"%s\")."), args.strings[0]);
           string_array_destroy (&args);
           return false;
         }
@@ -995,17 +1102,19 @@ 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], output))
+        if (!unquote_string (args.strings[i], ctx->segmenter_mode, output))
           ds_put_cstr (output, args.strings[i]);
     }
   else if (parse_macro_function (ctx, &args, ss_cstr ("!head"), 1, 1,
                                  input_consumed))
     {
       struct string tmp;
-      const char *s = unquote_string_in_place (args.strings[0], &tmp);
+      const char *s = unquote_string_in_place (args.strings[0],
+                                               ctx->segmenter_mode, &tmp);
 
       struct macro_tokens mts = { .n = 0 };
-      macro_tokens_from_string (&mts, ss_cstr (s), SEG_MODE_INTERACTIVE /* XXX */);
+      macro_tokens_from_string__ (&mts, ss_cstr (s), ctx->segmenter_mode,
+                                  ctx->stack);
       if (mts.n > 0)
         ds_put_substring (output, mts.mts[0].representation);
       macro_tokens_uninit (&mts);
@@ -1021,7 +1130,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], NULL))
+      if (unquote_string (args.strings[0], ctx->segmenter_mode, NULL))
         ds_put_cstr (output, args.strings[0]);
       else
         {
@@ -1042,7 +1151,10 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
       int start;
       if (!parse_integer (args.strings[1], &start) || start < 1)
         {
-          printf ("second argument to !SUBSTR must be positive integer (not \"%s\")\n", args.strings[1]);
+          macro_error (ctx->stack, NULL,
+                       _("Second argument of !SUBSTR must be "
+                         "positive integer (not \"%s\")."),
+                       args.strings[1]);
           string_array_destroy (&args);
           return false;
         }
@@ -1050,7 +1162,10 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
       int count = INT_MAX;
       if (args.n > 2 && (!parse_integer (args.strings[2], &count) || count < 0))
         {
-          printf ("third argument to !SUBSTR must be non-negative integer (not \"%s\")\n", args.strings[1]);
+          macro_error (ctx->stack, NULL,
+                       _("Third argument of !SUBSTR must be "
+                         "non-negative integer (not \"%s\")."),
+                       args.strings[1]);
           string_array_destroy (&args);
           return false;
         }
@@ -1062,10 +1177,12 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
                                  input_consumed))
     {
       struct string tmp;
-      const char *s = unquote_string_in_place (args.strings[0], &tmp);
+      const char *s = unquote_string_in_place (args.strings[0],
+                                               ctx->segmenter_mode, &tmp);
 
       struct macro_tokens mts = { .n = 0 };
-      macro_tokens_from_string (&mts, ss_cstr (s), SEG_MODE_INTERACTIVE /* XXX */);
+      macro_tokens_from_string__ (&mts, ss_cstr (s), ctx->segmenter_mode,
+                                  ctx->stack);
       if (mts.n > 1)
         {
           struct macro_tokens tail = { .mts = mts.mts + 1, .n = mts.n - 1 };
@@ -1077,14 +1194,15 @@ 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], output))
+      if (!unquote_string (args.strings[0], ctx->segmenter_mode, output))
         ds_put_cstr (output, args.strings[0]);
     }
   else if (parse_macro_function (ctx, &args, ss_cstr ("!upcase"), 1, 1,
                                  input_consumed))
     {
       struct string tmp;
-      const char *s = unquote_string_in_place (args.strings[0], &tmp);
+      const char *s = unquote_string_in_place (args.strings[0],
+                                               ctx->segmenter_mode, &tmp);
       char *upper = utf8_to_upper (s);
       ds_put_cstr (output, upper);
       free (upper);
@@ -1094,11 +1212,15 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
                                  input_consumed))
     {
       struct macro_tokens mts = { .n = 0 };
-      macro_tokens_from_string (&mts, ss_cstr (args.strings[0]),
-                                SEG_MODE_INTERACTIVE /* XXX */);
+      macro_tokens_from_string__ (&mts, ss_cstr (args.strings[0]),
+                                  ctx->segmenter_mode, ctx->stack);
       struct macro_tokens exp = { .n = 0 };
-      macro_expand (&mts, ctx->nesting_countdown - 1, ctx->macros, ctx->me,
-                    ctx->vars, ctx->expand, NULL, &exp);
+      macro_expand (&mts, ctx->nesting_countdown - 1, ctx->segmenter_mode,
+                    ctx->macros, ctx->me, ctx->vars,
+                    &(struct macro_expansion_stack) {
+                      .name = "!EVAL",
+                      .next = ctx->stack,
+                    }, ctx->expand, NULL, &exp);
       macro_tokens_to_representation (&exp, output, NULL, NULL);
       macro_tokens_uninit (&exp);
       macro_tokens_uninit (&mts);
@@ -1120,8 +1242,10 @@ expand_macro_function (struct parse_macro_function_ctx *ctx,
 struct expr_context
   {
     int nesting_countdown;
+    enum segmenter_mode segmenter_mode;
     const struct macro_set *macros;
     const struct macro_expander *me;
+    const struct macro_expansion_stack *stack;
     struct string_map *vars;
     bool *expand;
   };
@@ -1147,7 +1271,8 @@ macro_evaluate_literal (const struct expr_context *ctx,
       if (p >= end || p->token.type != T_RPAREN)
         {
           free (value);
-          printf ("expecting ')' in macro expression\n");
+          macro_error (ctx->stack, p < end ? p : NULL,
+                       _("Expecting ')' in macro expression."));
           return NULL;
         }
       p++;
@@ -1159,15 +1284,18 @@ macro_evaluate_literal (const struct expr_context *ctx,
     .input = p,
     .n_input = end - p,
     .nesting_countdown = ctx->nesting_countdown,
+    .segmenter_mode = ctx->segmenter_mode,
     .macros = ctx->macros,
     .me = ctx->me,
+    .stack = ctx->stack,
     .vars = ctx->vars,
     .expand = ctx->expand,
   };
   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), &unquoted))
+  if (unquote_string (ds_cstr (&function_output), ctx->segmenter_mode,
+                      &unquoted))
     {
       ds_swap (&function_output, &unquoted);
       ds_destroy (&unquoted);
@@ -1241,8 +1369,10 @@ macro_evaluate_relational (const struct expr_context *ctx,
     }
 
   struct string lhs_tmp, rhs_tmp;
-  int cmp = strcmp/*XXX*/ (unquote_string_in_place (lhs, &lhs_tmp),
-                           unquote_string_in_place (rhs, &rhs_tmp));
+  int cmp = strcmp (unquote_string_in_place (lhs, ctx->segmenter_mode,
+                                             &lhs_tmp),
+                    unquote_string_in_place (rhs, ctx->segmenter_mode,
+                                             &rhs_tmp));
   ds_destroy (&lhs_tmp);
   ds_destroy (&rhs_tmp);
 
@@ -1254,7 +1384,7 @@ macro_evaluate_relational (const struct expr_context *ctx,
             : op == T_LT ? cmp < 0
             : op == T_GT ? cmp > 0
             : op == T_LE ? cmp <= 0
-            :/*op == T_GE*/cmp >= 0);
+            : /* T_GE */ cmp >= 0);
 
   *tokens = p;
   return xstrdup (b ? "1" : "0");
@@ -1353,14 +1483,19 @@ 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_expander *me, struct string_map *vars,
-                           bool *expand)
+                           int nesting_countdown,
+                           enum segmenter_mode segmenter_mode,
+                           const struct macro_set *macros,
+                           const struct macro_expander *me,
+                           const struct macro_expansion_stack *stack,
+                           struct string_map *vars, bool *expand)
 {
   const struct expr_context ctx = {
     .nesting_countdown = nesting_countdown,
+    .segmenter_mode = segmenter_mode,
     .macros = macros,
     .me = me,
+    .stack = stack,
     .vars = vars,
     .expand = expand,
   };
@@ -1369,21 +1504,28 @@ macro_evaluate_expression (const struct macro_token **tokens, size_t 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_expander *me, struct string_map *vars,
+                       int nesting_countdown,
+                       enum segmenter_mode segmenter_mode,
+                       const struct macro_set *macros,
+                       const struct macro_expander *me,
+                       const struct macro_expansion_stack *stack,
+                       struct string_map *vars,
                        bool *expand, double *number)
 {
   char *s = macro_evaluate_expression (tokens, n_tokens, nesting_countdown,
-                                       macros, me, vars, expand);
+                                       segmenter_mode, macros, me, stack, vars,
+                                       expand);
   if (!s)
     return false;
 
   struct macro_tokens mts = { .n = 0 };
-  macro_tokens_from_string (&mts, ss_cstr (s), SEG_MODE_INTERACTIVE /* XXX */);
+  macro_tokens_from_string__ (&mts, ss_cstr (s), segmenter_mode, stack);
   if (mts.n != 1 || !token_is_number (&mts.mts[0].token))
     {
       macro_tokens_print (&mts, stdout);
-      printf ("expression must evaluate to a number (not %s)\n", s);
+      macro_error (stack, mts.n > 0 ? &mts.mts[0] : NULL,
+                   _("Macro expression must evaluate to "
+                     "a number (not \"%s\")."), s);
       free (s);
       macro_tokens_uninit (&mts);
       return false;
@@ -1420,8 +1562,11 @@ 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_expander *me, struct string_map *vars,
+                 int nesting_countdown, enum segmenter_mode segmenter_mode,
+                 const struct macro_set *macros,
+                 const struct macro_expander *me,
+                 const struct macro_expansion_stack *stack,
+                 struct string_map *vars,
                  bool *expand, bool *break_, struct macro_tokens *exp)
 {
   const struct macro_token *p = tokens;
@@ -1432,8 +1577,9 @@ macro_expand_if (const struct macro_token *tokens, size_t n_tokens,
 
   p++;
   char *result = macro_evaluate_expression (&p, end - p,
-                                            nesting_countdown, macros, me, vars,
-                                            expand);
+                                            nesting_countdown, segmenter_mode,
+                                            macros, me,
+                                            stack, vars, expand);
   if (!result)
     return 0;
   bool b = strcmp (result, "0");
@@ -1443,7 +1589,8 @@ macro_expand_if (const struct macro_token *tokens, size_t n_tokens,
       || p->token.type != T_MACRO_ID
       || !ss_equals_case (p->token.string, ss_cstr ("!THEN")))
     {
-      printf ("!THEN expected\n");
+      macro_error (stack, p < end ? p : NULL,
+                   _("!THEN expected in macro !IF construct."));
       return 0;
     }
 
@@ -1451,7 +1598,8 @@ macro_expand_if (const struct macro_token *tokens, size_t n_tokens,
   const struct macro_token *end_then = find_ifend_clause (start_then, end);
   if (!end_then)
     {
-      printf ("!ELSE or !IFEND expected\n");
+      macro_error (stack, NULL,
+                   _("!ELSE or !IFEND expected in macro !IF construct."));
       return 0;
     }
 
@@ -1463,7 +1611,8 @@ macro_expand_if (const struct macro_token *tokens, size_t n_tokens,
       if (!end_if
           || !ss_equals_case (end_if->token.string, ss_cstr ("!IFEND")))
         {
-          printf ("!IFEND expected\n");
+          macro_error (stack, end_if ? end_if : NULL,
+                       _("!IFEND expected in macro !IF construct."));
           return 0;
         }
     }
@@ -1497,17 +1646,23 @@ 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, me, vars, expand,
-                    break_, exp);
+      macro_expand (&mts, nesting_countdown, segmenter_mode, macros, me, vars,
+                    &(struct macro_expansion_stack) {
+                      .name = "!IF",
+                      .next = stack,
+                    },
+                    expand, break_, 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_expander *me, struct string_map *vars,
-                 bool *expand)
+                 int nesting_countdown, enum segmenter_mode segmenter_mode,
+                 const struct macro_set *macros,
+                 const struct macro_expander *me,
+                 const struct macro_expansion_stack *stack,
+                 struct string_map *vars, bool *expand)
 {
   const struct macro_token *p = tokens;
   const struct macro_token *end = tokens + n_tokens;
@@ -1518,28 +1673,33 @@ macro_parse_let (const struct macro_token *tokens, size_t n_tokens,
 
   if (p >= end || p->token.type != T_MACRO_ID)
     {
-      printf ("expected macro variable name following !LET\n");
+      macro_error (stack, p < end ? p : NULL,
+                   _("Expected macro variable name following !LET."));
       return 0;
     }
   const struct substring var_name = p->token.string;
   if (is_macro_keyword (var_name)
       || macro_find_parameter_by_name (me->macro, var_name))
     {
-      printf ("cannot use argument name or macro keyword %.*s as !LET variable\n", (int) var_name.length, var_name.string);
+      macro_error (stack, p < end ? p : NULL,
+                   _("Cannot use argument name or macro keyword "
+                     "\"%.*s\" as !LET variable."),
+                   (int) var_name.length, var_name.string);
       return 0;
     }
   p++;
 
   if (p >= end || p->token.type != T_EQUALS)
     {
-      printf ("expected = following !LET\n");
+      macro_error (stack, p < end ? p : NULL,
+                   _("Expected `=' following !LET."));
       return 0;
     }
   p++;
 
-  char *value = macro_evaluate_expression (&p, end - p,
-                                           nesting_countdown, macros, me, vars,
-                                           expand);
+  char *value = macro_evaluate_expression (&p, end - p, nesting_countdown,
+                                           segmenter_mode, macros, me, stack,
+                                           vars, expand);
   if (!value)
     return 0;
 
@@ -1548,7 +1708,8 @@ macro_parse_let (const struct macro_token *tokens, size_t n_tokens,
 }
 
 static const struct macro_token *
-find_doend (const struct macro_token *p, const struct macro_token *end)
+find_doend (const struct macro_expansion_stack *stack,
+            const struct macro_token *p, const struct macro_token *end)
 {
   size_t nesting = 0;
   for (; p < end; p++)
@@ -1565,14 +1726,17 @@ find_doend (const struct macro_token *p, const struct macro_token *end)
           nesting--;
         }
     }
-  printf ("missing !DOEND\n");
+  macro_error (stack, NULL, _("Missing !DOEND."));
   return NULL;
 }
 
 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_expander *me, struct string_map *vars,
+                 int nesting_countdown, enum segmenter_mode segmenter_mode,
+                 const struct macro_set *macros,
+                 const struct macro_expander *me,
+                 const struct macro_expansion_stack *stack,
+                 struct string_map *vars,
                  bool *expand, struct macro_tokens *exp)
 {
   const struct macro_token *p = tokens;
@@ -1584,35 +1748,40 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
 
   if (p >= end || p->token.type != T_MACRO_ID)
     {
-      printf ("expected macro variable name following !DO\n");
+      macro_error (stack, p < end ? p : NULL,
+                   _("Expected macro variable name following !DO."));
       return 0;
     }
   const struct substring var_name = p->token.string;
   if (is_macro_keyword (var_name)
       || macro_find_parameter_by_name (me->macro, var_name))
     {
-      printf ("cannot use argument name or macro keyword %.*s as !DO variable\n", (int) var_name.length, var_name.string);
+      macro_error (stack, p, _("Cannot use argument name or macro "
+                               "keyword as !DO variable."));
       return 0;
     }
   p++;
 
+  struct macro_expansion_stack next_stack = {
+    .name = "!DO", .next = stack,
+  };
   int miterate = settings_get_miterate ();
   if (p < end && p->token.type == T_MACRO_ID
       && ss_equals_case (p->token.string, ss_cstr ("!IN")))
     {
       p++;
-      char *list = macro_evaluate_expression (&p, end - p,
-                                              nesting_countdown, macros, me, vars,
-                                              expand);
+      char *list = macro_evaluate_expression (&p, end - p, nesting_countdown,
+                                              segmenter_mode, macros, me,
+                                              &next_stack, vars, expand);
       if (!list)
         return 0;
 
       struct macro_tokens items = { .n = 0 };
-      macro_tokens_from_string (&items, ss_cstr (list),
-                                SEG_MODE_INTERACTIVE /* XXX */);
+      macro_tokens_from_string__ (&items, ss_cstr (list), segmenter_mode,
+                                  stack);
       free (list);
 
-      const struct macro_token *do_end = find_doend (p, end);
+      const struct macro_token *do_end = find_doend (stack, p, end);
       if (!do_end)
         {
           macro_tokens_uninit (&items);
@@ -1627,15 +1796,19 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
         {
           if (i >= miterate)
             {
-              printf ("exceeded maximum number of iterations %d\n", miterate);
+              macro_error (stack, NULL,
+                           _("!DO loop over list exceeded "
+                             "maximum number of iterations %d.  "
+                             "(Use SET MITERATE to change the limit.)"),
+                           miterate);
               break;
             }
           string_map_replace_nocopy (vars, ss_xstrdup (var_name),
                                      ss_xstrdup (items.mts[i].representation));
 
           bool break_ = false;
-          macro_expand (&inner, nesting_countdown, macros,
-                        me, vars, expand, &break_, exp);
+          macro_expand (&inner, nesting_countdown, segmenter_mode, macros,
+                        me, vars, &next_stack, expand, &break_, exp);
           if (break_)
             break;
         }
@@ -1645,20 +1818,23 @@ 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, me,
+      if (!macro_evaluate_number (&p, end - p, nesting_countdown,
+                                  segmenter_mode, macros, me, &next_stack,
                                   vars, expand, &first))
         return 0;
 
       if (p >= end || p->token.type != T_MACRO_ID
           || !ss_equals_case (p->token.string, ss_cstr ("!TO")))
         {
-          printf ("expecting !TO\n");
+          macro_error (stack, p < end ? p : NULL,
+                       _("Expected !TO in numerical !DO loop."));
           return 0;
         }
       p++;
 
       double last;
-      if (!macro_evaluate_number (&p, end - p, nesting_countdown, macros, me,
+      if (!macro_evaluate_number (&p, end - p, nesting_countdown,
+                                  segmenter_mode, macros, me, &next_stack,
                                   vars, expand, &last))
         return 0;
 
@@ -1667,18 +1843,19 @@ 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, me,
+          if (!macro_evaluate_number (&p, end - p, nesting_countdown,
+                                      segmenter_mode, macros, me, &next_stack,
                                       vars, expand, &by))
             return 0;
 
           if (by == 0.0)
             {
-              printf ("!BY value cannot be zero\n");
+              macro_error (stack, NULL, _("!BY value cannot be zero."));
               return 0;
             }
         }
 
-      const struct macro_token *do_end = find_doend (p, end);
+      const struct macro_token *do_end = find_doend (stack, p, end);
       if (!do_end)
         return 0;
       const struct macro_tokens inner = {
@@ -1695,8 +1872,11 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
             {
               if (i++ > miterate)
                 {
-                  printf ("exceeded maximum number of iterations %d\n",
-                          miterate);
+                  macro_error (stack, NULL,
+                               _("Numerical !DO loop exceeded "
+                                 "maximum number of iterations %d.  "
+                                 "(Use SET MITERATE to change the limit.)"),
+                               miterate);
                   break;
                 }
 
@@ -1706,8 +1886,8 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
                                          xstrdup (index_s));
 
               bool break_ = false;
-              macro_expand (&inner, nesting_countdown, macros,
-                            me, vars, expand, &break_, exp);
+              macro_expand (&inner, nesting_countdown, segmenter_mode, macros,
+                            me, vars, &next_stack, expand, &break_, exp);
               if (break_)
                 break;
             }
@@ -1717,20 +1897,25 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
     }
   else
     {
-      printf ("expecting = or !IN in !DO loop\n");
+      macro_error (stack, p < end ? p : NULL,
+                   _("Expected `=' or !IN in !DO loop."));
       return 0;
     }
 }
 
 static void
-macro_expand (const struct macro_tokens *mts,
-              int nesting_countdown, const struct macro_set *macros,
+macro_expand (const struct macro_tokens *mts, int nesting_countdown,
+              enum segmenter_mode segmenter_mode,
+              const struct macro_set *macros,
               const struct macro_expander *me, struct string_map *vars,
+              const struct macro_expansion_stack *stack,
               bool *expand, bool *break_, struct macro_tokens *exp)
 {
   if (nesting_countdown <= 0)
     {
-      printf ("maximum nesting level exceeded\n");
+      macro_error (stack, NULL, _("Maximum nesting level %d exceeded.  "
+                                  "(Use SET MNEST to change the limit.)"),
+                   settings_get_mnest ());
       for (size_t i = 0; i < mts->n; i++)
         macro_tokens_add (exp, &mts->mts[i]);
       return;
@@ -1751,10 +1936,13 @@ macro_expand (const struct macro_tokens *mts,
           if (param)
             {
               const struct macro_tokens *arg = me->args[param - me->macro->params];
-              //macro_tokens_print (arg, stdout);
               if (*expand && param->expand_arg)
-                macro_expand (arg, nesting_countdown, macros, NULL, NULL,
-                              expand, break_, exp);
+                macro_expand (arg, nesting_countdown, segmenter_mode,
+                              macros, NULL, NULL,
+                              &(struct macro_expansion_stack) {
+                                .name = param->name,
+                                .next = stack,
+                              }, expand, break_, exp);
               else
                 for (size_t i = 0; i < arg->n; i++)
                   macro_tokens_add (exp, &arg->mts[i]);
@@ -1771,8 +1959,12 @@ macro_expand (const struct macro_tokens *mts,
 
                   const struct macro_tokens *arg = me->args[j];
                   if (*expand && param->expand_arg)
-                    macro_expand (arg, nesting_countdown, macros, NULL, NULL,
-                                  expand, break_, exp);
+                    macro_expand (arg, nesting_countdown, segmenter_mode,
+                                  macros, NULL, NULL,
+                                  &(struct macro_expansion_stack) {
+                                    .name = "!*",
+                                    .next = stack,
+                                  }, expand, break_, exp);
                   else
                     for (size_t k = 0; k < arg->n; k++)
                       macro_tokens_add (exp, &arg->mts[k]);
@@ -1782,8 +1974,9 @@ macro_expand (const struct macro_tokens *mts,
             }
 
           size_t n = macro_expand_if (&mts->mts[i], mts->n - i,
-                                      nesting_countdown, macros, me, vars,
-                                      expand, break_, exp);
+                                      nesting_countdown, segmenter_mode,
+                                      macros, me, stack,
+                                      vars, expand, break_, exp);
           if (n > 0)
             {
               i += n - 1;
@@ -1797,8 +1990,8 @@ macro_expand (const struct macro_tokens *mts,
                                                  token->string.length);
           if (value)
             {
-              macro_tokens_from_string (exp, ss_cstr (value),
-                                        SEG_MODE_INTERACTIVE /* XXX */);
+              macro_tokens_from_string__ (exp, ss_cstr (value), segmenter_mode,
+                                          stack);
               continue;
             }
         }
@@ -1816,8 +2009,15 @@ macro_expand (const struct macro_tokens *mts,
           if (retval > 0)
             {
               i += retval - 1;
-              macro_expand (&subme->macro->body, nesting_countdown - 1, macros,
-                            subme, NULL, expand, break_, exp);
+              macro_expand (&subme->macro->body, nesting_countdown - 1,
+                            segmenter_mode, macros, subme, NULL,
+                            &(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,
+                              .next = stack,
+                            }, expand, break_, exp);
               macro_expander_destroy (subme);
               continue;
             }
@@ -1834,7 +2034,7 @@ macro_expand (const struct macro_tokens *mts,
       if (ss_equals_case (token->string, ss_cstr ("!break")))
         {
           if (!break_)
-            printf ("!BREAK outside !DO\n");
+            macro_error (stack, mt, _("!BREAK outside !DO."));
           else
             {
               *break_ = true;
@@ -1846,8 +2046,10 @@ macro_expand (const struct macro_tokens *mts,
         .input = &mts->mts[i],
         .n_input = mts->n - i,
         .nesting_countdown = nesting_countdown,
+        .segmenter_mode = segmenter_mode,
         .macros = macros,
         .me = me,
+        .stack = stack,
         .vars = vars,
         .expand = expand,
       };
@@ -1857,16 +2059,16 @@ macro_expand (const struct macro_tokens *mts,
         {
           i += function_consumed - 1;
 
-          macro_tokens_from_string (exp, function_output.ss,
-                                    SEG_MODE_INTERACTIVE /* XXX */);
+          macro_tokens_from_string__ (exp, function_output.ss, segmenter_mode,
+                                      stack);
           ds_destroy (&function_output);
 
           continue;
         }
 
       size_t n = macro_parse_let (&mts->mts[i], mts->n - i,
-                                  nesting_countdown, macros, me, vars,
-                                  expand);
+                                  nesting_countdown, segmenter_mode,
+                                  macros, me, stack, vars, expand);
       if (n > 0)
         {
           i += n - 1;
@@ -1874,8 +2076,8 @@ macro_expand (const struct macro_tokens *mts,
         }
 
       n = macro_expand_do (&mts->mts[i], mts->n - i,
-                           nesting_countdown, macros, me, vars,
-                           expand, exp);
+                           nesting_countdown, segmenter_mode,
+                           macros, me, stack, vars, expand, exp);
       if (n > 0)
         {
           i += n - 1;
@@ -1894,23 +2096,18 @@ macro_expand (const struct macro_tokens *mts,
 }
 
 void
-macro_expander_get_expansion (struct macro_expander *me, struct macro_tokens *exp)
+macro_expander_get_expansion (struct macro_expander *me,
+                              enum segmenter_mode segmenter_mode,
+                              struct macro_tokens *exp)
 {
-#if 0
-  for (size_t i = 0; i < me->macro->n_params; i++)
-    {
-      printf ("%s:\n", me->macro->params[i].name);
-      macro_tokens_print (me->args[i], stdout);
-    }
-#endif
-
   bool expand = true;
-  macro_expand (&me->macro->body, settings_get_mnest (),
-                me->macros, me, NULL, &expand, NULL, exp);
-
-#if 0
-  printf ("expansion:\n");
-  macro_tokens_print (exp, stdout);
-#endif
+  struct macro_expansion_stack stack = {
+    .name = me->macro->name,
+    .file_name = me->macro->file_name,
+    .first_line = me->macro->first_line,
+    .last_line = me->macro->last_line,
+  };
+  macro_expand (&me->macro->body, settings_get_mnest (), segmenter_mode,
+                me->macros, me, NULL, &stack, &expand, NULL, exp);
 }