message: Introduce underlining for error message regions.
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 6 Dec 2021 06:03:31 +0000 (22:03 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 9 Dec 2021 04:14:55 +0000 (20:14 -0800)
This will allow for the upcoming matrix language implementation
to give better error messages that clearly show the erroneous
part of the source.

15 files changed:
src/language/control/define.c
src/language/control/repeat.c
src/language/data-io/data-parser.c
src/language/data-io/data-reader.c
src/language/data-io/matrix-data.c
src/language/lexer/lexer.c
src/language/lexer/lexer.h
src/libpspp/message.c
src/libpspp/message.h
src/ui/gui/psppire.c
src/ui/gui/spreadsheet-test.c
src/ui/terminal/main.c
tests/language/lexer/lexer.at
tests/output/pivot-table-test.c
utilities/pspp-output.c

index f33e885925a807b67071d1832b2de8442f3c7845..e1d9622ff4067b882a92a8bc259c1bdf8dac2025 100644 (file)
@@ -109,14 +109,8 @@ cmd_define (struct lexer *lexer, struct dataset *ds UNUSED)
     }
 
   struct macro *m = xmalloc (sizeof *m);
-  *m = (struct macro) {
-    .name = xstrdup (name),
-    .location = xmalloc (sizeof *m->location),
-  };
-  *m->location = (struct msg_location) {
-    .file_name = intern_new_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))
@@ -282,7 +276,14 @@ cmd_define (struct lexer *lexer, struct dataset *ds UNUSED)
       ds_put_byte (&body, '\n');
       lex_get (lexer);
     }
-  m->location->last_line = lex_get_last_line_number (lexer, 0);
+
+  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 },
+  };
 
   macro_tokens_from_string (&m->body, body.ss, lex_get_syntax_mode (lexer));
   ds_destroy (&body);
index 86dd36f7f0c8a878d2eac529d152723ec86c1040..8876c8fb9e2efeb9bbe9a90a5b284616e83d801c 100644 (file)
@@ -253,7 +253,6 @@ parse_commands (struct lexer *lexer, struct hmap *dummies)
   struct string input;
   size_t n_values;
   char *file_name;
-  int line_number;
   bool ok;
   size_t i;
 
@@ -261,7 +260,7 @@ parse_commands (struct lexer *lexer, struct hmap *dummies)
     file_name = xstrdup (lex_get_file_name (lexer));
   else
     file_name = NULL;
-  line_number = lex_get_first_line_number (lexer, 0);
+  int line_number = lex_ofs_start_point (lexer, lex_ofs (lexer)).line;
 
   ds_init_empty (&input);
   while (lex_is_string (lexer))
index 09af7542bb5750510f402b6ce3ee90bb6e833c01..c42daba4f1caa4b5e8ce08ae66819d51d4ddff70 100644 (file)
@@ -500,10 +500,8 @@ parse_error (const struct dfm_reader *reader, const struct field *field,
   struct msg_location *location = xmalloc (sizeof *location);
   *location = (struct msg_location) {
     .file_name = intern_new (dfm_get_file_name (reader)),
-    .first_line = line_number,
-    .last_line = line_number + 1,
-    .first_column = first_column,
-    .last_column = last_column,
+    .start = { .line = line_number, .column = first_column },
+    .end = { .line = line_number, .column = last_column - 1 },
   };
   struct msg *m = xmalloc (sizeof *m);
   *m = (struct msg) {
index 9a9cafa7f9bff9aeb597f3ed49126545f1d4a121..89e84da0b37f13c28ac97941e1e3ab943b43ac6c 100644 (file)
@@ -717,10 +717,18 @@ dfm_get_file_name (const struct dfm_reader *r)
 int
 dfm_get_line_number (const struct dfm_reader *r)
 {
-  enum fh_referent referent = fh_get_referent (r->fh);
-  return (referent == FH_REF_FILE ? r->line_number
-          : referent == FH_REF_INLINE ? lex_get_first_line_number (r->lexer, 0)
-          : -1);
+  switch (fh_get_referent (r->fh))
+    {
+    case FH_REF_FILE:
+      return r->line_number;
+
+    case FH_REF_INLINE:
+      return lex_ofs_start_point (r->lexer, lex_ofs (r->lexer)).line;
+
+    case FH_REF_DATASET:
+    default:
+      return -1;
+    }
 }
 \f
 /* BEGIN DATA...END DATA procedure. */
index 31d6b710b0ba68a54402d33e50e559b9f581bf2e..fbf82c084cb40ad5933f325a9ccb8b682220c221 100644 (file)
@@ -253,12 +253,13 @@ parse_msg (struct dfm_reader *reader, const struct substring *token,
 
   int line_number = dfm_get_line_number (reader);
   struct msg_location *location = xmalloc (sizeof *location);
+  int last_column = (first_column && token->length
+                     ? first_column + token->length - 1
+                     : 0);
   *location = (struct msg_location) {
     .file_name = intern_new (dfm_get_file_name (reader)),
-    .first_line = line_number,
-    .last_line = line_number + 1,
-    .first_column = first_column,
-    .last_column = first_column ? first_column + token->length : 0,
+    .start = { .line = line_number, .column = first_column },
+    .end = { .line = line_number, .column = last_column },
   };
   struct msg *m = xmalloc (sizeof *m);
   *m = (struct msg) {
index 805d6fa4965829187087a1574553986f55105707..329003406bf0281b0d01f99db81372bd4b5649b0 100644 (file)
@@ -69,7 +69,6 @@ struct lex_token
        call. */
     size_t token_pos;           /* Offset into src->buffer of token start. */
     size_t token_len;           /* Length of source for token in bytes. */
-    int first_line;             /* Line number at token_pos. */
 
     /* For a token obtained through macro expansion, this is just this token.
 
@@ -81,6 +80,18 @@ struct lex_token
     size_t *ref_cnt;        /* Number of lex_tokens that refer to macro_rep. */
   };
 
+static struct msg_point lex_token_start_point (const struct lex_source *,
+                                               const struct lex_token *);
+static struct msg_point lex_token_end_point (const struct lex_source *,
+                                             const struct lex_token *);
+
+/* Source offset of the last byte in TOKEN. */
+static size_t
+lex_token_end (const struct lex_token *token)
+{
+  return token->token_pos + MAX (token->token_len, 1) - 1;
+}
+
 static void
 lex_token_destroy (struct lex_token *t)
 {
@@ -207,6 +218,14 @@ lex_stage_shift (struct lex_stage *dst, struct lex_stage *src, size_t n)
 struct lex_source
   {
     struct ll ll;               /* In lexer's list of sources. */
+
+    /* Reference count:
+
+       - One for struct lexer.
+
+       - One for each struct msg_location that references this source. */
+    size_t n_refs;
+
     struct lex_reader *reader;
     struct lexer *lexer;
     struct segmenter segmenter;
@@ -221,7 +240,10 @@ struct lex_source
     size_t journal_pos;         /* First byte not yet output to journal. */
     size_t seg_pos;             /* First byte not yet scanned as token. */
 
-    int n_newlines;             /* Number of new-lines up to seg_pos. */
+    /* Offset into 'buffer' of starts of lines. */
+    size_t *lines;
+    size_t n_lines, allocated_lines;
+
     bool suppress_next_newline;
 
     /* Tokens.
@@ -251,7 +273,6 @@ struct lex_source
 
 static struct lex_source *lex_source_create (struct lexer *,
                                              struct lex_reader *);
-static void lex_source_destroy (struct lex_source *);
 
 /* Lexer. */
 struct lexer
@@ -320,7 +341,10 @@ lex_destroy (struct lexer *lexer)
       struct lex_source *source, *next;
 
       ll_for_each_safe (source, next, struct lex_source, ll, &lexer->sources)
-        lex_source_destroy (source);
+        {
+          ll_remove (&source->ll);
+          lex_source_unref (source);
+        }
       macro_set_destroy (lexer->macros);
       free (lexer);
     }
@@ -375,7 +399,8 @@ lex_get (struct lexer *lexer)
   while (src->parse_ofs == src->n_parse)
     if (!lex_source_get_parse (src))
       {
-        lex_source_destroy (src);
+        ll_remove (&src->ll);
+        lex_source_unref (src);
         src = lex_source__ (lexer);
         if (src == NULL)
           return;
@@ -1025,23 +1050,18 @@ lex_next__ (const struct lexer *lexer_, int n)
 }
 
 static const struct lex_token *
-lex_source_next__ (const struct lex_source *src_, int n)
+lex_source_ofs__ (const struct lex_source *src_, int ofs)
 {
   struct lex_source *src = CONST_CAST (struct lex_source *, src_);
 
-  if (n < 0)
+  if (ofs < 0)
     {
-      if (-n <= src->parse_ofs)
-        return src->parse[src->parse_ofs - (-n)];
-      else
-        {
-          static const struct lex_token endcmd_token
-            = { .token = { .type = T_ENDCMD } };
-          return &endcmd_token;
-        }
+      static const struct lex_token endcmd_token
+        = { .token = { .type = T_ENDCMD } };
+      return &endcmd_token;
     }
 
-  while (src->n_parse - src->parse_ofs <= n)
+  while (ofs >= src->n_parse)
     {
       if (src->n_parse > 0)
         {
@@ -1053,7 +1073,13 @@ lex_source_next__ (const struct lex_source *src_, int n)
       lex_source_get_parse (src);
     }
 
-  return src->parse[src->parse_ofs + n];
+  return src->parse[ofs];
+}
+
+static const struct lex_token *
+lex_source_next__ (const struct lex_source *src, int n)
+{
+  return lex_source_ofs__ (src, n + src->parse_ofs);
 }
 
 /* Returns the "struct token" of the token N after the current one in LEXER.
@@ -1114,6 +1140,85 @@ lex_next_tokss (const struct lexer *lexer, int n)
   return lex_next (lexer, n)->string;
 }
 
+/* Returns the offset of the current token within the command being parsed in
+   LEXER.  This is 0 for the first token in a command, 1 for the second, and so
+   on.  The return value is useful later for referring to this token in calls
+   to lex_ofs_*(). */
+int
+lex_ofs (const struct lexer *lexer)
+{
+  struct lex_source *src = lex_source__ (lexer);
+  return src ? src->parse_ofs : 0;
+}
+
+/* Returns the token within LEXER's current command with offset OFS.  Use
+   lex_ofs() to find out the offset of the current token. */
+const struct token *
+lex_ofs_token (const struct lexer *lexer_, int ofs)
+{
+  struct lexer *lexer = CONST_CAST (struct lexer *, lexer_);
+  struct lex_source *src = lex_source__ (lexer);
+
+  if (src != NULL)
+    return &lex_source_next__ (src, ofs - src->parse_ofs)->token;
+  else
+    {
+      static const struct token stop_token = { .type = T_STOP };
+      return &stop_token;
+    }
+}
+
+/* Allocates and returns a new struct msg_location that spans tokens with
+   offsets OFS0 through OFS1, inclusive, within the current command in
+   LEXER.  See lex_ofs() for an explanation of token offsets.
+
+   The caller owns and must eventually free the returned object. */
+struct msg_location *
+lex_ofs_location (const struct lexer *lexer, int ofs0, int ofs1)
+{
+  int ofs = lex_ofs (lexer);
+  return lex_get_location (lexer, ofs0 - ofs, ofs1 - ofs);
+}
+
+/* Returns a msg_point for the first character in the token with offset OFS,
+   where offset 0 is the first token in the command currently being parsed, 1
+   the second token, and so on.  These are absolute offsets, not relative to
+   the token currently being parsed within the command.
+
+   Returns zeros for a T_STOP token.
+ */
+struct msg_point
+lex_ofs_start_point (const struct lexer *lexer, int ofs)
+{
+  const struct lex_source *src = lex_source__ (lexer);
+  return (src
+          ? lex_token_start_point (src, lex_source_ofs__ (src, ofs))
+          : (struct msg_point) { 0, 0 });
+}
+
+/* Returns a msg_point for the last character, inclusive, in the token with
+   offset OFS, where offset 0 is the first token in the command currently being
+   parsed, 1 the second token, and so on.  These are absolute offsets, not
+   relative to the token currently being parsed within the command.
+
+   Returns zeros for a T_STOP token.
+
+   Most of the time, a single token is wholly within a single line of syntax,
+   so that the start and end point for a given offset have the same line
+   number.  There are two exceptions: a T_STRING token can be made up of
+   multiple segments on adjacent lines connected with "+" punctuators, and a
+   T_NEG_NUM token can consist of a "-" on one line followed by the number on
+   the next.
+ */
+struct msg_point
+lex_ofs_end_point (const struct lexer *lexer, int ofs)
+{
+  const struct lex_source *src = lex_source__ (lexer);
+  return (src
+          ? lex_token_end_point (src, lex_source_ofs__ (src, ofs))
+          : (struct msg_point) { 0, 0 });
+}
+
 /* Returns the text of the syntax in tokens N0 ahead of the current one,
    through N1 ahead of the current one, inclusive.  (For example, if N0 and N1
    are both zero, this requests the syntax for the current token.)  The caller
@@ -1205,55 +1310,58 @@ lex_match_phrase (struct lexer *lexer, const char *s)
   return n > 0;
 }
 
+/* Returns the 1-based line number of the source text at the byte OFFSET in
+   SRC. */
 static int
-count_newlines (char *s, size_t length)
-{
-  int n_newlines = 0;
-  char *newline;
-
-  while ((newline = memchr (s, '\n', length)) != NULL)
-    {
-      n_newlines++;
-      length -= (newline + 1) - s;
-      s = newline + 1;
-    }
-
-  return n_newlines;
-}
-
-static int
-lex_token_get_last_line_number (const struct lex_source *src,
-                                const struct lex_token *token)
+lex_source_ofs_to_line_number (const struct lex_source *src, size_t offset)
 {
-  if (token->first_line == 0)
-    return 0;
-  else
+  size_t lo = 0;
+  size_t hi = src->n_lines;
+  for (;;)
     {
-      char *token_str = &src->buffer[token->token_pos];
-      return token->first_line + count_newlines (token_str, token->token_len) + 1;
+      size_t mid = (lo + hi) / 2;
+      if (mid + 1 >= src->n_lines)
+        return src->n_lines;
+      else if (offset >= src->lines[mid + 1])
+        lo = mid;
+      else if (offset < src->lines[mid])
+        hi = mid;
+      else
+        return mid + 1;
     }
 }
 
+/* Returns the 1-based column number of the source text at the byte OFFSET in
+   SRC. */
 static int
-lex_token_get_column__ (const struct lex_source *src, size_t offset)
+lex_source_ofs_to_column_number (const struct lex_source *src, size_t offset)
 {
   const char *newline = memrchr (src->buffer, '\n', offset);
   size_t line_ofs = newline ? newline - src->buffer + 1 : 0;
   return utf8_count_columns (&src->buffer[line_ofs], offset - line_ofs) + 1;
 }
 
-static int
-lex_token_get_first_column (const struct lex_source *src,
-                            const struct lex_token *token)
+static struct msg_point
+lex_source_ofs_to_point__ (const struct lex_source *src, size_t offset)
 {
-  return lex_token_get_column__ (src, token->token_pos);
+  return (struct msg_point) {
+    .line = lex_source_ofs_to_line_number (src, offset),
+    .column = lex_source_ofs_to_column_number (src, offset),
+  };
 }
 
-static int
-lex_token_get_last_column (const struct lex_source *src,
-                           const struct lex_token *token)
+static struct msg_point
+lex_token_start_point (const struct lex_source *src,
+                       const struct lex_token *token)
 {
-  return lex_token_get_column__ (src, token->token_pos + token->token_len);
+  return lex_source_ofs_to_point__ (src, token->token_pos);
+}
+
+static struct msg_point
+lex_token_end_point (const struct lex_source *src,
+                     const struct lex_token *token)
+{
+  return lex_source_ofs_to_point__ (src, lex_token_end (token));
 }
 
 static struct msg_location
@@ -1263,10 +1371,8 @@ lex_token_location (const struct lex_source *src,
 {
   return (struct msg_location) {
     .file_name = intern_new_if_nonnull (src->reader->file_name),
-    .first_line = t0->first_line,
-    .last_line = lex_token_get_last_line_number (src, t1),
-    .first_column = lex_token_get_first_column (src, t0),
-    .last_column = lex_token_get_last_column (src, t1),
+    .start = lex_token_start_point (src, t0),
+    .end = lex_token_end_point (src, t1),
   };
 }
 
@@ -1287,62 +1393,6 @@ lex_source_get_location (const struct lex_source *src, int n0, int n1)
                                 lex_source_next__ (src, n1));
 }
 
-/* Returns the 1-based line number of the start of the syntax that represents
-   the token N after the current one in LEXER.  Returns 0 for a T_STOP token or
-   if the token is drawn from a source that does not have line numbers. */
-int
-lex_get_first_line_number (const struct lexer *lexer, int n)
-{
-  const struct lex_source *src = lex_source__ (lexer);
-  return src ? lex_source_next__ (src, n)->first_line : 0;
-}
-
-/* Returns the 1-based line number of the end of the syntax that represents the
-   token N after the current one in LEXER, plus 1.  Returns 0 for a T_STOP
-   token or if the token is drawn from a source that does not have line
-   numbers.
-
-   Most of the time, a single token is wholly within a single line of syntax,
-   but there are two exceptions: a T_STRING token can be made up of multiple
-   segments on adjacent lines connected with "+" punctuators, and a T_NEG_NUM
-   token can consist of a "-" on one line followed by the number on the next.
- */
-int
-lex_get_last_line_number (const struct lexer *lexer, int n)
-{
-  const struct lex_source *src = lex_source__ (lexer);
-  return src ? lex_token_get_last_line_number (src,
-                                               lex_source_next__ (src, n)) : 0;
-}
-
-/* Returns the 1-based column number of the start of the syntax that represents
-   the token N after the current one in LEXER.  Returns 0 for a T_STOP
-   token.
-
-   Column numbers are measured according to the width of characters as shown in
-   a typical fixed-width font, in which CJK characters have width 2 and
-   combining characters have width 0.  */
-int
-lex_get_first_column (const struct lexer *lexer, int n)
-{
-  const struct lex_source *src = lex_source__ (lexer);
-  return src ? lex_token_get_first_column (src, lex_source_next__ (src, n)) : 0;
-}
-
-/* Returns the 1-based column number of the end of the syntax that represents
-   the token N after the current one in LEXER, plus 1.  Returns 0 for a T_STOP
-   token.
-
-   Column numbers are measured according to the width of characters as shown in
-   a typical fixed-width font, in which CJK characters have width 2 and
-   combining characters have width 0.  */
-int
-lex_get_last_column (const struct lexer *lexer, int n)
-{
-  const struct lex_source *src = lex_source__ (lexer);
-  return src ? lex_token_get_last_column (src, lex_source_next__ (src, n)) : 0;
-}
-
 /* Returns the name of the syntax file from which the current command is drawn.
    Returns NULL for a T_STOP token or if the command's source does not have
    line numbers.
@@ -1362,26 +1412,15 @@ lex_get_file_name (const struct lexer *lexer)
    must eventually free the location (with msg_location_destroy()). */
 struct msg_location *
 lex_get_location (const struct lexer *lexer, int n0, int n1)
-{
-  struct msg_location *loc = lex_get_lines (lexer, n0, n1);
-  loc->first_column = lex_get_first_column (lexer, n0);
-  loc->last_column = lex_get_last_column (lexer, n1);
-  return loc;
-}
-
-/* Returns a newly allocated msg_location for the syntax that represents tokens
-   with 0-based offsets N0...N1, inclusive, from the current token.  The
-   location only covers the tokens' lines, not the columns.  The caller must
-   eventually free the location (with msg_location_destroy()). */
-struct msg_location *
-lex_get_lines (const struct lexer *lexer, int n0, int n1)
 {
   struct msg_location *loc = xmalloc (sizeof *loc);
   *loc = (struct msg_location) {
     .file_name = intern_new_if_nonnull (lex_get_file_name (lexer)),
-    .first_line = lex_get_first_line_number (lexer, n0),
-    .last_line = lex_get_last_line_number (lexer, n1),
+    .start = lex_ofs_start_point (lexer, n0 + lex_ofs (lexer)),
+    .end = lex_ofs_end_point (lexer, n1 + lex_ofs (lexer)),
+    .src = lex_source__ (lexer),
   };
+  lex_source_ref (loc->src);
   return loc;
 }
 
@@ -1435,7 +1474,7 @@ lex_interactive_reset (struct lexer *lexer)
     {
       src->length = 0;
       src->journal_pos = src->seg_pos = 0;
-      src->n_newlines = 0;
+      src->n_lines = 0;
       src->suppress_next_newline = false;
       src->segmenter = segmenter_init (segmenter_get_mode (&src->segmenter),
                                        false);
@@ -1470,7 +1509,10 @@ lex_discard_noninteractive (struct lexer *lexer)
 
       for (; src != NULL && src->reader->error != LEX_ERROR_TERMINAL;
            src = lex_source__ (lexer))
-        lex_source_destroy (src);
+        {
+          ll_remove (&src->ll);
+          lex_source_unref (src);
+        }
     }
 }
 \f
@@ -1694,10 +1736,6 @@ lex_source_try_get_pp (struct lex_source *src)
   token->macro_rep = NULL;
   token->ref_cnt = NULL;
   token->token_pos = src->seg_pos;
-  if (src->reader->line_number > 0)
-    token->first_line = src->reader->line_number + src->n_newlines;
-  else
-    token->first_line = 0;
 
   /* Extract a segment. */
   const char *segment;
@@ -1721,7 +1759,12 @@ lex_source_try_get_pp (struct lex_source *src)
   token->token_len = seg_len;
   src->seg_pos += seg_len;
   if (seg_type == SEG_NEWLINE)
-    src->n_newlines++;
+    {
+      if (src->n_lines >= src->allocated_lines)
+        src->lines = x2nrealloc (src->lines, &src->allocated_lines,
+                                 sizeof *src->lines);
+      src->lines[src->n_lines++] = src->seg_pos;
+    }
 
   /* Get a token from the segment. */
   enum tokenize_result result = token_from_segment (
@@ -1839,11 +1882,9 @@ lex_source_try_get_merge (const struct lex_source *src_)
         }
 
       const struct lex_token *t = lex_stage_nth (&src->pp, ofs);
-      size_t start = t->token_pos;
-      size_t end = t->token_pos + t->token_len;
       const struct macro_token mt = {
         .token = t->token,
-        .syntax = ss_buffer (&src->buffer[start], end - start),
+        .syntax = ss_buffer (&src->buffer[t->token_pos], t->token_len),
       };
       const struct msg_location loc = lex_token_location (src, t, t);
       n_call = macro_call_add (mc, &mt, &loc);
@@ -1892,7 +1933,6 @@ lex_source_try_get_merge (const struct lex_source *src_)
             .token = expansion.mts[i].token,
             .token_pos = c0->token_pos,
             .token_len = (c1->token_pos + c1->token_len) - c0->token_pos,
-            .first_line = c0->first_line,
             .macro_rep = macro_rep,
             .ofs = ofs[i],
             .len = len[i],
@@ -1968,7 +2008,6 @@ lex_source_get_parse (struct lex_source *src)
             .token = out,
             .token_pos = first->token_pos,
             .token_len = (last->token_pos - first->token_pos) + last->token_len,
-            .first_line = first->first_line,
 
             /* This works well if all the tokens were not expanded from macros,
                or if they came from the same macro expansion.  It just gives up
@@ -2019,11 +2058,19 @@ lex_source_clear_parse (struct lex_source *src)
 static struct lex_source *
 lex_source_create (struct lexer *lexer, struct lex_reader *reader)
 {
+  size_t allocated_lines = 4;
+  size_t *lines = xmalloc (allocated_lines * sizeof *lines);
+  *lines = 0;
+
   struct lex_source *src = xmalloc (sizeof *src);
   *src = (struct lex_source) {
+    .n_refs = 1,
     .reader = reader,
     .segmenter = segmenter_init (reader->syntax, false),
     .lexer = lexer,
+    .lines = lines,
+    .n_lines = 1,
+    .allocated_lines = allocated_lines,
   };
 
   lex_source_push_endcmd__ (src);
@@ -2031,9 +2078,42 @@ lex_source_create (struct lexer *lexer, struct lex_reader *reader)
   return src;
 }
 
-static void
-lex_source_destroy (struct lex_source *src)
+void
+lex_set_message_handler (struct lexer *lexer,
+                         void (*output_msg) (const struct msg *,
+                                             struct lexer *))
+{
+  struct msg_handler msg_handler = {
+    .output_msg = (void (*)(const struct msg *, void *)) output_msg,
+    .aux = lexer,
+    .lex_source_ref = lex_source_ref,
+    .lex_source_unref = lex_source_unref,
+    .lex_source_get_line = lex_source_get_line,
+  };
+  msg_set_handler (&msg_handler);
+}
+
+void
+lex_source_ref (const struct lex_source *src_)
 {
+  struct lex_source *src = CONST_CAST (struct lex_source *, src_);
+  if (src)
+    {
+      assert (src->n_refs > 0);
+      src->n_refs++;
+    }
+}
+
+void
+lex_source_unref (struct lex_source *src)
+{
+  if (!src)
+    return;
+
+  assert (src->n_refs > 0);
+  if (--src->n_refs > 0)
+    return;
+
   char *file_name = src->reader->file_name;
   char *encoding = src->reader->encoding;
   if (src->reader->class->destroy != NULL)
@@ -2041,11 +2121,11 @@ lex_source_destroy (struct lex_source *src)
   free (file_name);
   free (encoding);
   free (src->buffer);
+  free (src->lines);
   lex_stage_uninit (&src->pp);
   lex_stage_uninit (&src->merge);
   lex_source_clear_parse (src);
   free (src->parse);
-  ll_remove (&src->ll);
   free (src);
 }
 \f
@@ -2221,3 +2301,14 @@ static struct lex_reader_class lex_string_reader_class =
     lex_string_read,
     lex_string_close
   };
+\f
+struct substring
+lex_source_get_line (const struct lex_source *src, int line)
+{
+  if (line < 1 || line > src->n_lines)
+    return ss_empty ();
+
+  size_t ofs = src->lines[line - 1];
+  size_t end = line >= src->n_lines ? src->length : src->lines[line];
+  return ss_buffer (&src->buffer[ofs], end - ofs);
+}
index 1282b6946b732e451ca080cd3b1cf50e621557a1..5493db9b6a29175c7b522911c4782f70cf191a65 100644 (file)
 #include "language/lexer/segment.h"
 #include "libpspp/cast.h"
 #include "libpspp/compiler.h"
+#include "libpspp/message.h"
 #include "libpspp/prompt.h"
+#include "libpspp/str.h"
 
 struct lexer;
+struct lex_source;
 struct macro;
 
 /* Handling of errors. */
@@ -148,18 +151,20 @@ const char *lex_next_tokcstr (const struct lexer *, int n);
 double lex_next_tokval (const struct lexer *, int n);
 struct substring lex_next_tokss (const struct lexer *, int n);
 
+/* Looking at the current command, including lookahead and lookbehind. */
+int lex_ofs (const struct lexer *);
+const struct token *lex_ofs_token (const struct lexer *, int ofs);
+struct msg_location *lex_ofs_location (const struct lexer *, int ofs0, int ofs1);
+struct msg_point lex_ofs_start_point (const struct lexer *, int ofs);
+struct msg_point lex_ofs_end_point (const struct lexer *, int ofs);
+
 /* Token representation. */
 char *lex_next_representation (const struct lexer *, int n0, int n1);
 bool lex_next_is_from_macro (const struct lexer *, int n);
 
 /* Current position. */
-int lex_get_first_line_number (const struct lexer *, int n);
-int lex_get_last_line_number (const struct lexer *, int n);
-int lex_get_first_column (const struct lexer *, int n);
-int lex_get_last_column (const struct lexer *, int n);
 const char *lex_get_file_name (const struct lexer *);
 struct msg_location *lex_get_location (const struct lexer *, int n0, int n1);
-struct msg_location *lex_get_lines (const struct lexer *, int n0, int n1);
 const char *lex_get_encoding (const struct lexer *);
 
 /* Issuing errors. */
@@ -195,4 +200,12 @@ void lex_discard_rest_of_command (struct lexer *);
 void lex_interactive_reset (struct lexer *);
 void lex_discard_noninteractive (struct lexer *);
 
+/* Source code access. */
+void lex_set_message_handler (struct lexer *,
+                              void (*output_msg) (const struct msg *,
+                                                  struct lexer *));
+void lex_source_ref (const struct lex_source *);
+void lex_source_unref (struct lex_source *);
+struct substring lex_source_get_line (const struct lex_source *, int line);
+
 #endif /* lexer.h */
index faeb83a449d3e3058507e163d9bd7049192ea917..83c7320168eef5a8ca4cf68baaa6c129bd98dd0d 100644 (file)
@@ -42,8 +42,7 @@
 #define _(msgid) gettext (msgid)
 
 /* Message handler as set by msg_set_handler(). */
-static void (*msg_handler)  (const struct msg *, void *aux);
-static void *msg_aux;
+static struct msg_handler msg_handler = { .output_msg = NULL };
 
 /* Disables emitting messages if positive. */
 static int messages_disabled;
@@ -52,12 +51,14 @@ static int messages_disabled;
 
 
 void
-vmsg (enum msg_class class, const char *format, va_list args)
+vmsg (enum msg_class class, const struct msg_location *location,
+      const char *format, va_list args)
 {
   struct msg *m = xmalloc (sizeof *m);
   *m = (struct msg) {
     .category = msg_class_to_category (class),
     .severity = msg_class_to_severity (class),
+    .location = msg_location_dup (location),
     .text = xvasprintf (format, args),
   };
   msg_emit (m);
@@ -70,11 +71,21 @@ msg (enum msg_class class, const char *format, ...)
 {
   va_list args;
   va_start (args, format);
-  vmsg (class, format, args);
+  vmsg (class, NULL, format, args);
   va_end (args);
 }
 
-
+/* Outputs error message in CLASS, with text FORMAT, formatted with printf.
+   LOCATION is the reported location for the message. */
+void
+msg_at (enum msg_class class, const struct msg_location *location,
+        const char *format, ...)
+{
+  va_list args;
+  va_start (args, format);
+  vmsg (class, location, format, args);
+  va_end (args);
+}
 
 void
 msg_error (int errnum, const char *format, ...)
@@ -95,13 +106,10 @@ msg_error (int errnum, const char *format, ...)
   msg_emit (m);
 }
 
-
-
 void
-msg_set_handler (void (*handler) (const struct msg *, void *aux), void *aux)
+msg_set_handler (const struct msg_handler *handler)
 {
-  msg_handler = handler;
-  msg_aux = aux;
+  msg_handler = *handler;
 }
 \f
 /* msg_location. */
@@ -109,6 +117,8 @@ msg_set_handler (void (*handler) (const struct msg *, void *aux), void *aux)
 void
 msg_location_uninit (struct msg_location *loc)
 {
+  if (msg_handler.lex_source_unref)
+    msg_handler.lex_source_unref (loc->src);
   intern_unref (loc->file_name);
 }
 
@@ -122,6 +132,48 @@ msg_location_destroy (struct msg_location *loc)
     }
 }
 
+static int
+msg_point_compare_3way (const struct msg_point *a, const struct msg_point *b)
+{
+  return (!a->line ? 1
+          : !b->line ? -1
+          : a->line > b->line ? 1
+          : a->line < b->line ? -1
+          : !a->column ? 1
+          : !b->column ? -1
+          : a->column > b->column ? 1
+          : a->column < b->column ? -1
+          : 0);
+}
+
+void
+msg_location_remove_columns (struct msg_location *location)
+{
+  location->start.column = 0;
+  location->end.column = 0;
+}
+
+void
+msg_location_merge (struct msg_location **dstp, const struct msg_location *src)
+{
+  struct msg_location *dst = *dstp;
+  if (!dst)
+    {
+      *dstp = msg_location_dup (src);
+      return;
+    }
+
+  if (dst->file_name != src->file_name)
+    {
+      /* Failure. */
+      return;
+    }
+  if (msg_point_compare_3way (&dst->start, &src->start) > 0)
+    dst->start = src->start;
+  if (msg_point_compare_3way (&dst->end, &src->end) < 0)
+    dst->end = src->end;
+}
+
 struct msg_location *
 msg_location_dup (const struct msg_location *src)
 {
@@ -129,13 +181,11 @@ msg_location_dup (const struct msg_location *src)
     return NULL;
 
   struct msg_location *dst = xmalloc (sizeof *dst);
-  *dst = (struct msg_location) {
-    .file_name = intern_new_if_nonnull (src->file_name),
-    .first_line = src->first_line,
-    .last_line = src->last_line,
-    .first_column = src->first_column,
-    .last_column = src->last_column,
-  };
+  *dst = *src;
+  if (src->file_name)
+    dst->file_name = intern_ref (src->file_name);
+  if (msg_handler.lex_source_ref && src->src)
+    msg_handler.lex_source_ref (dst->src);
   return dst;
 }
 
@@ -143,8 +193,8 @@ bool
 msg_location_is_empty (const struct msg_location *loc)
 {
   return !loc || (!loc->file_name
-                  && loc->first_line <= 0
-                  && loc->first_column <= 0);
+                  && loc->start.line <= 0
+                  && loc->start.column <= 0);
 }
 
 void
@@ -156,10 +206,10 @@ msg_location_format (const struct msg_location *loc, struct string *s)
   if (loc->file_name)
     ds_put_cstr (s, loc->file_name);
 
-  int l1 = loc->first_line;
-  int l2 = MAX (loc->first_line, loc->last_line - 1);
-  int c1 = loc->first_column;
-  int c2 = MAX (loc->first_column, loc->last_column - 1);
+  int l1 = loc->start.line;
+  int l2 = MAX (l1, loc->end.line);
+  int c1 = loc->start.column;
+  int c2 = MAX (c1, loc->end.column);
 
   if (l1 > 0)
     {
@@ -316,6 +366,47 @@ msg_to_string (const struct msg *m)
 
   ds_put_cstr (&s, m->text);
 
+  const struct msg_location *loc = m->location;
+  if (m->category != MSG_C_GENERAL
+      && loc->src && loc->start.line && loc->start.column
+      && msg_handler.lex_source_get_line)
+    {
+      int l0 = loc->start.line;
+      int l1 = loc->end.line;
+      int nl = l1 - l0;
+      for (int ln = l0; ln <= l1; ln++)
+        {
+          if (nl > 3 && ln == l0 + 2)
+            {
+              ds_put_cstr (&s, "\n  ... |");
+              ln = l1;
+            }
+
+          struct substring line = msg_handler.lex_source_get_line (
+            loc->src, ln);
+          ss_rtrim (&line, ss_cstr ("\n\r"));
+
+          ds_put_format (&s, "\n%5d | ", ln);
+          ds_put_substring (&s, line);
+
+          int c0 = ln == l0 ? loc->start.column : 1;
+          int c1 = ln == l1 ? loc->end.column : ss_utf8_count_columns (line);
+          if (c0 > 0 && c1 >= c0)
+            {
+              ds_put_cstr (&s, "\n      |");
+              ds_put_byte_multiple (&s, ' ', c0);
+              if (ln == l0)
+                {
+                  ds_put_byte (&s, '^');
+                  if (c1 > c0)
+                    ds_put_byte_multiple (&s, '~', c1 - c0);
+                }
+              else
+                ds_put_byte_multiple (&s, '-', c1 - c0 + 1);
+            }
+        }
+    }
+
   return ds_cstr (&s);
 }
 \f
@@ -380,8 +471,8 @@ ship_message (const struct msg *m)
       return;
 
   stack[n++] = m;
-  if (msg_handler && n <= 1)
-    msg_handler (m, msg_aux);
+  if (msg_handler.output_msg && n <= 1)
+    msg_handler.output_msg (m, msg_handler.aux);
   else
     fprintf (stderr, "%s\n", m->text);
   n--;
index a99b0818e3438fd6514be202a46c3d6e0f6f8185..813febe82a07a3906315937999a8886cc05d6fd0 100644 (file)
@@ -72,19 +72,53 @@ msg_class_from_category_and_severity (enum msg_category category,
   return category * 3 + severity;
 }
 
+/* A line number and column number within a source file.  Both are 1-based.  If
+   only a line number is available, 'column' is zero.  If neither is available,
+   'line' and 'column' are zero.
+
+   Column numbers are measured according to the width of characters as shown in
+   a typical fixed-width font, in which CJK characters have width 2 and
+   combining characters have width 0.  */
+struct msg_point
+  {
+    int line;
+    int column;
+  };
+
+/* Location of the cause of an error. */
 struct msg_location
   {
-    const char *file_name;      /* Interned file name, or NULL. */
-    int first_line;             /* 1-based line number, or 0 if none. */
-    int last_line;              /* 1-based exclusive last line (0=none). */
-    int first_column;           /* 1-based first column, or 0 if none. */
-    int last_column;            /* 1-based exclusive last column (0=none). */
+    /* Interned file name, or NULL. */
+    const char *file_name;
+
+    /* Nonnull if this came from a source file. */
+    struct lex_source *src;
+
+    /* The starting and ending point of the cause.  One of:
+
+       - Both empty, with all their members zero.
+
+       - A range of lines, with 0 < start.line <= end.line and start.column =
+         end.column = 0.
+
+       - A range of columns spanning one or more lines.  If it's on a single
+         line, then start.line = end.line and 0 < start.column <= end.column.
+         If it's across multiple lines, then 0 < start.line < end.line and the
+         column members are both positive.
+
+       Both 'start' and 'end' are inclusive, line-wise and column-wise.
+    */
+    struct msg_point start, end;
   };
 
 void msg_location_uninit (struct msg_location *);
 void msg_location_destroy (struct msg_location *);
 struct msg_location *msg_location_dup (const struct msg_location *);
 
+void msg_location_remove_columns (struct msg_location *);
+
+void msg_location_merge (struct msg_location **, const struct msg_location *);
+
 bool msg_location_is_empty (const struct msg_location *);
 void msg_location_format (const struct msg_location *, struct string *);
 
@@ -110,8 +144,17 @@ struct msg
   };
 
 /* Initialization. */
-void msg_set_handler (void (*handler) (const struct msg *, void *lexer),
-                      void *aux);
+struct msg_handler
+  {
+    void (*output_msg) (const struct msg *, void *aux);
+    void *aux;
+
+    void (*lex_source_ref) (const struct lex_source *);
+    void (*lex_source_unref) (struct lex_source *);
+    struct substring (*lex_source_get_line) (const struct lex_source *,
+                                             int line);
+  };
+void msg_set_handler (const struct msg_handler *);
 
 /* Working with messages. */
 struct msg *msg_dup (const struct msg *);
@@ -119,10 +162,14 @@ void msg_destroy(struct msg *);
 char *msg_to_string (const struct msg *);
 
 /* Emitting messages. */
-void vmsg (enum msg_class class, const char *format, va_list args)
-     PRINTF_FORMAT (2, 0);
+void vmsg (enum msg_class, const struct msg_location *,
+           const char *format, va_list args)
+     PRINTF_FORMAT (3, 0);
 void msg (enum msg_class, const char *format, ...)
      PRINTF_FORMAT (2, 3);
+void msg_at (enum msg_class, const struct msg_location *,
+             const char *format, ...)
+     PRINTF_FORMAT (3, 4);
 void msg_emit (struct msg *);
 
 void msg_error (int errnum, const char *format, ...)
index 7344b9816146f867b7804eee824ebe169601fe61..e03a84b433bad7eb752c666abeac23d36384a16d 100644 (file)
@@ -142,9 +142,8 @@ psppire_quit (GApplication *app)
 
 \f
 static void
-handle_msg (const struct msg *m_, void *lexer_)
+handle_msg (const struct msg *m_, struct lexer *lexer)
 {
-  struct lexer *lexer = lexer_;
   struct msg m = {
     .category = m_->category,
     .severity = m_->severity,
@@ -165,10 +164,9 @@ handle_msg (const struct msg *m_, void *lexer_)
 void
 psppire_set_lexer (struct lexer *lexer)
 {
-  msg_set_handler (handle_msg, lexer);
+  lex_set_message_handler (lexer, handle_msg);
 }
 
-
 GtkWindow *
 psppire_preload_file (const gchar *file, GtkWindow *victim)
 {
index 995872f4370fab582744610bb47034b50401897a..dfdd95c0ae8e119a5127c1c11e22131a9fa1f264 100644 (file)
@@ -122,7 +122,7 @@ main (int argc, char *argv[])
   if (argc < 2)
     g_error ("Usage: prog file\n");
 
-  msg_set_handler (print_msg, 0);
+  msg_set_handler (&(struct msg_handler) { .output_msg = print_msg });
 
   stuff.sp = NULL;
 
index f43bd5dc11556c2ed05fade994654c7c8d989860..d2336472bfb98a0b19d6bc10a8f8864dc38788a6 100644 (file)
@@ -65,7 +65,7 @@ static void add_syntax_reader (struct lexer *, const char *file_name,
                                const char *encoding, enum segmenter_mode);
 static void bug_handler(int sig);
 static void fpu_init (void);
-static void output_msg (const struct msg *, void *);
+static void output_msg (const struct msg *, struct lexer *);
 
 /* Program entry point. */
 int
@@ -109,7 +109,7 @@ main (int argc, char **argv)
   terminal_opts_done (terminal_opts, argc, argv);
   argv_parser_destroy (parser);
 
-  msg_set_handler (output_msg, lexer);
+  lex_set_message_handler (lexer, output_msg);
   session_set_default_syntax_encoding (the_session, syntax_encoding);
 
   /* Add syntax files to source stream. */
@@ -215,17 +215,21 @@ bug_handler(int sig)
 }
 
 static void
-output_msg (const struct msg *m_, void *lexer_)
+output_msg (const struct msg *m_, struct lexer *lexer)
 {
-  struct lexer *lexer = lexer_;
+  struct msg_location *location = m_->location;
+  if (!location && lexer)
+    {
+      location = lex_get_location (lexer, 0, 0);
+      msg_location_remove_columns (location);
+    }
+
   struct msg m = {
     .category = m_->category,
     .severity = m_->severity,
     .stack = m_->stack,
     .n_stack = m_->n_stack,
-    .location = (m_->location ? m_->location
-                 : lexer ? lex_get_lines (lexer, 0, 0)
-                 : NULL),
+    .location = location,
     .command_name = output_get_uppercase_command_name (),
     .text = m_->text,
   };
index c572e5fd864ad8ac042aaf0c983f3b3bd425e066..725c26065706a0ac419a4458de3d0131ba8dea69 100644 (file)
@@ -72,7 +72,7 @@ lexer.sps:9.4: error: Syntax error at `.': expecting command name.
 
 lexer.sps:10.1: error: Syntax error at `^': Bad character `^' in input.
 
-lexer.sps:11.1: error: Syntax error at `�': Bad character U+FFFD in input.
+lexer.sps:11.1-11.2: error: Syntax error at `�': Bad character U+FFFD in input.
 ])
 AT_CLEANUP
 
index 9e91bfe3bf4e3c8e19f0f8b4b3e373005e51fd06..7aef458d2479fa4f44451f9b3af67e3fd69357d0 100644 (file)
@@ -53,7 +53,7 @@ static const char *output_base = "render";
 static const char *parse_options (int argc, char **argv);
 static void usage (void) NO_RETURN;
 static void read_table (struct lexer *);
-static void output_msg (const struct msg *, void *);
+static void output_msg (const struct msg *, struct lexer *);
 
 int
 main (int argc, char **argv)
@@ -74,7 +74,7 @@ main (int argc, char **argv)
     exit (1);
 
   struct lexer *lexer = lex_create ();
-  msg_set_handler (output_msg, lexer);
+  lex_set_message_handler (lexer, output_msg);
   lex_include (lexer, reader);
   lex_get (lexer);
 
@@ -1230,9 +1230,8 @@ read_table (struct lexer *lexer)
 }
 
 static void
-output_msg (const struct msg *m_, void *lexer_)
+output_msg (const struct msg *m_, struct lexer *lexer)
 {
-  struct lexer *lexer = lexer_;
   struct msg m = {
     .category = m_->category,
     .severity = m_->severity,
index 3a695efa299d98dfde590e9f17c3d702cdb03b3c..8e80faad511af37fcac25ed366526fb36d48c8ef 100644 (file)
@@ -808,7 +808,7 @@ int
 main (int argc, char **argv)
 {
   set_program_name (argv[0]);
-  msg_set_handler (emit_msg, NULL);
+  msg_set_handler (&(struct msg_handler) { .output_msg = emit_msg });
   settings_init ();
   i18n_init ();