MATCH FILES, UPDATE, ADD FILES: Improve error messages.
[pspp] / src / language / control / define.c
index 48dda4c7afb0331afa63113d9cc0fc5e36ae7a08..c439c64571b75d2637c3e79df5980584e71ba9a7 100644 (file)
@@ -23,6 +23,7 @@
 #include "language/lexer/macro.h"
 #include "language/lexer/scan.h"
 #include "language/lexer/token.h"
+#include "libpspp/intern.h"
 #include "libpspp/message.h"
 
 #include "gl/xalloc.h"
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
-static bool
-force_macro_id (struct lexer *lexer)
-{
-  return lex_token (lexer) == T_MACRO_ID || lex_force_id (lexer);
-}
-
 static bool
 match_macro_id (struct lexer *lexer, const char *keyword)
 {
@@ -82,8 +77,9 @@ dup_arg_type (struct lexer *lexer, bool *saw_arg_type)
 {
   if (*saw_arg_type)
     {
-      lex_error (lexer, _("Only one of !TOKENS, !CHAREND, !ENCLOSE, or "
-                          "!CMDEND is allowed."));
+      lex_next_error (lexer, -1, -1,
+                      _("Only one of !TOKENS, !CHAREND, !ENCLOSE, or "
+                        "!CMDEND is allowed."));
       return false;
     }
   else
@@ -93,28 +89,110 @@ dup_arg_type (struct lexer *lexer, bool *saw_arg_type)
     }
 }
 
+static bool
+parse_macro_body (struct lexer *lexer, struct macro_tokens *mts)
+{
+  *mts = (struct macro_tokens) { .n = 0 };
+  struct string body = DS_EMPTY_INITIALIZER;
+  struct msg_point start = lex_ofs_start_point (lexer, lex_ofs (lexer));
+  while (!match_macro_id (lexer, "!ENDDEFINE"))
+    {
+      if (lex_token (lexer) != T_STRING)
+        {
+          lex_error (lexer,
+                     _("Syntax error expecting macro body or !ENDDEFINE."));
+          ds_destroy (&body);
+          return false;
+        }
+
+      ds_put_substring (&body, lex_tokss (lexer));
+      ds_put_byte (&body, '\n');
+      lex_get (lexer);
+    }
+
+  struct segmenter segmenter = segmenter_init (lex_get_syntax_mode (lexer),
+                                               true);
+  struct substring p = body.ss;
+  bool ok = true;
+  while (p.length > 0)
+    {
+      enum segment_type type;
+      int seg_len = segmenter_push (&segmenter, p.string,
+                                    p.length, true, &type);
+      assert (seg_len >= 0);
+
+      struct macro_token mt = {
+        .token = { .type = T_STOP },
+        .syntax = ss_head (p, seg_len),
+      };
+      enum tokenize_result result
+        = token_from_segment (type, mt.syntax, &mt.token);
+      ss_advance (&p, seg_len);
+
+      switch (result)
+        {
+        case TOKENIZE_EMPTY:
+          break;
+
+        case TOKENIZE_TOKEN:
+          macro_tokens_add (mts, &mt);
+          break;
+
+        case TOKENIZE_ERROR:
+          {
+            size_t start_offset = mt.syntax.string - body.ss.string;
+            size_t end_offset = start_offset + (mt.syntax.length ? mt.syntax.length - 1 : 0);
+
+            const struct msg_location loc = {
+              .file_name = intern_new_if_nonnull (lex_get_file_name (lexer)),
+              .start = msg_point_advance (start, ss_buffer (body.ss.string, start_offset)),
+              .end = msg_point_advance (start, ss_buffer (body.ss.string, end_offset)),
+              .src = CONST_CAST (struct lex_source *, lex_source (lexer)),
+            };
+            msg_at (SE, &loc, "%s", mt.token.string.string);
+            intern_unref (loc.file_name);
+
+            ok = false;
+          }
+          break;
+        }
+
+      token_uninit (&mt.token);
+    }
+  ds_destroy (&body);
+  return ok;
+}
+
 int
 cmd_define (struct lexer *lexer, struct dataset *ds UNUSED)
 {
-  if (!force_macro_id (lexer))
-    return CMD_FAILURE;
+  /* Parse macro name.
+
+     The macro name is a T_STRING token, even though it's an identifier,
+     because that's the way that the segmenter prevents it from getting
+     macro-expanded. */
+  if (lex_token (lexer) != T_STRING)
+    {
+      lex_error (lexer, _("Syntax error expecting identifier."));
+      return CMD_FAILURE;
+    }
+  const char *name = lex_tokcstr (lexer);
+  if (!id_is_plausible (name + (name[0] == '!')))
+    {
+      lex_error (lexer, _("Syntax error expecting identifier."));
+      return CMD_FAILURE;
+    }
 
-  /* Parse macro name. */
   struct macro *m = xmalloc (sizeof *m);
-  *m = (struct macro) {
-    .name = ss_xstrdup (lex_tokss (lexer)),
-    .location = xmalloc (sizeof *m->location),
-  };
-  *m->location = (struct msg_location) {
-    .file_name = xstrdup_if_nonnull (lex_get_file_name (lexer)),
-    .first_line = lex_get_first_line_number (lexer, 0),
-  };
+  *m = (struct macro) { .name = xstrdup (name) };
+  struct msg_point macro_start = lex_ofs_start_point (lexer, lex_ofs (lexer));
   lex_get (lexer);
 
   if (!lex_force_match (lexer, T_LPAREN))
     goto error;
 
   size_t allocated_params = 0;
+  int keyword_ofs = 0;
   while (!lex_match (lexer, T_RPAREN))
     {
       if (m->n_params >= allocated_params)
@@ -130,8 +208,11 @@ cmd_define (struct lexer *lexer, struct dataset *ds UNUSED)
         {
           if (param_index > 0 && !m->params[param_index - 1].positional)
             {
-              lex_error (lexer, _("Positional parameters must precede "
-                                  "keyword parameters."));
+              lex_next_error (lexer, -1, -1,
+                              _("Positional parameters must precede "
+                                "keyword parameters."));
+              lex_ofs_msg (lexer, SN, keyword_ofs, keyword_ofs,
+                           _("Here is a previous keyword parameter."));
               goto error;
             }
 
@@ -140,6 +221,8 @@ cmd_define (struct lexer *lexer, struct dataset *ds UNUSED)
         }
       else
         {
+          if (keyword_ofs == 0)
+            keyword_ofs = lex_ofs (lexer);
           if (lex_token (lexer) == T_MACRO_ID)
             {
               lex_error (lexer, _("Keyword macro parameter must be named in "
@@ -171,8 +254,9 @@ cmd_define (struct lexer *lexer, struct dataset *ds UNUSED)
             {
               if (saw_default)
                 {
-                  lex_error (lexer,
-                             _("!DEFAULT is allowed only once per argument."));
+                  lex_next_error (
+                    lexer, -1, -1,
+                    _("!DEFAULT is allowed only once per argument."));
                   goto error;
                 }
               saw_default = true;
@@ -219,10 +303,9 @@ cmd_define (struct lexer *lexer, struct dataset *ds UNUSED)
                 goto error;
 
               p->arg_type = ARG_CHAREND;
-              p->charend = (struct token) { .type = T_STOP };
 
               if (!lex_force_match (lexer, T_LPAREN)
-                  || !parse_quoted_token (lexer, &p->charend)
+                  || !parse_quoted_token (lexer, &p->end)
                   || !lex_force_match (lexer, T_RPAREN))
                 goto error;
             }
@@ -232,12 +315,11 @@ cmd_define (struct lexer *lexer, struct dataset *ds UNUSED)
                 goto error;
 
               p->arg_type = ARG_ENCLOSE;
-              p->enclose[0] = p->enclose[1] = (struct token) { .type = T_STOP };
 
               if (!lex_force_match (lexer, T_LPAREN)
-                  || !parse_quoted_token (lexer, &p->enclose[0])
+                  || !parse_quoted_token (lexer, &p->start)
                   || !lex_force_match (lexer, T_COMMA)
-                  || !parse_quoted_token (lexer, &p->enclose[1])
+                  || !parse_quoted_token (lexer, &p->end)
                   || !lex_force_match (lexer, T_RPAREN))
                 goto error;
             }
@@ -262,24 +344,16 @@ cmd_define (struct lexer *lexer, struct dataset *ds UNUSED)
         goto error;
     }
 
-  struct string body = DS_EMPTY_INITIALIZER;
-  while (!match_macro_id (lexer, "!ENDDEFINE"))
-    {
-      if (lex_token (lexer) != T_STRING)
-        {
-          lex_error (lexer, _("Expecting macro body or !ENDDEFINE"));
-          ds_destroy (&body);
-          goto error;
-        }
-
-      ds_put_substring (&body, lex_tokss (lexer));
-      ds_put_byte (&body, '\n');
-      lex_get (lexer);
-    }
-  m->location->last_line = lex_get_last_line_number (lexer, 0);
+  if (!parse_macro_body (lexer, &m->body))
+    goto error;
 
-  macro_tokens_from_string (&m->body, body.ss, lex_get_syntax_mode (lexer));
-  ds_destroy (&body);
+  struct msg_point macro_end = lex_ofs_end_point (lexer, lex_ofs (lexer) - 1);
+  m->location = xmalloc (sizeof *m->location);
+  *m->location = (struct msg_location) {
+    .file_name = intern_new_if_nonnull (lex_get_file_name (lexer)),
+    .start = { .line = macro_start.line },
+    .end = { .line = macro_end.line },
+  };
 
   lex_define_macro (lexer, m);