message: Introduce underlining for error message regions.
[pspp] / src / language / lexer / lexer.c
index 2218caadd7e4c525453b3d3d855bc0f0daf5a377..329003406bf0281b0d01f99db81372bd4b5649b0 100644 (file)
@@ -28,7 +28,6 @@
 #include <unictype.h>
 #include <unistd.h>
 #include <unistr.h>
-#include <uniwidth.h>
 
 #include "language/command.h"
 #include "language/lexer/macro.h"
@@ -39,6 +38,7 @@
 #include "libpspp/cast.h"
 #include "libpspp/deque.h"
 #include "libpspp/i18n.h"
+#include "libpspp/intern.h"
 #include "libpspp/ll.h"
 #include "libpspp/message.h"
 #include "libpspp/misc.h"
@@ -66,13 +66,9 @@ struct lex_token
        location of the token in terms of the lex_source's buffer.
 
        For a token produced through macro expansion, this is the entire macro
-       call.
-
-       src->tail <= line_pos <= token_pos <= src->head. */
-    size_t token_pos;           /* Start of token. */
+       call. */
+    size_t token_pos;           /* Offset into src->buffer of token start. */
     size_t token_len;           /* Length of source for token in bytes. */
-    size_t line_pos;            /* Start of line containing token_pos. */
-    int first_line;             /* Line number at token_pos. */
 
     /* For a token obtained through macro expansion, this is just this token.
 
@@ -84,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)
 {
@@ -114,7 +122,6 @@ static void lex_stage_uninit (struct lex_stage *);
 static size_t lex_stage_count (const struct lex_stage *);
 static bool lex_stage_is_empty (const struct lex_stage *);
 
-static struct lex_token *lex_stage_last (struct lex_stage *);
 static struct lex_token *lex_stage_first (struct lex_stage *);
 static struct lex_token *lex_stage_nth (struct lex_stage *, size_t ofs);
 
@@ -154,14 +161,6 @@ lex_stage_count (const struct lex_stage *stage)
   return deque_count (&stage->deque);
 }
 
-/* Returns the last token in STAGE, which must be nonempty.  The last token is
-   the one accessed with the greatest lookahead. */
-static struct lex_token *
-lex_stage_last (struct lex_stage *stage)
-{
-  return stage->tokens[deque_front (&stage->deque, 0)];
-}
-
 /* Returns the first token in STAGE, which must be nonempty.
    The first token is the one accessed with the least lookahead. */
 static struct lex_token *
@@ -189,11 +188,18 @@ lex_stage_push_last (struct lex_stage *stage, struct lex_token *token)
   stage->tokens[deque_push_front (&stage->deque)] = token;
 }
 
+/* Removes and returns the first token from STAGE. */
+static struct lex_token *
+lex_stage_take_first (struct lex_stage *stage)
+{
+  return stage->tokens[deque_pop_back (&stage->deque)];
+}
+
 /* Removes the first token from STAGE and uninitializes it. */
 static void
 lex_stage_pop_first (struct lex_stage *stage)
 {
-  lex_token_destroy (stage->tokens[deque_pop_back (&stage->deque)]);
+  lex_token_destroy (lex_stage_take_first (stage));
 }
 
 /* Removes the first N tokens from SRC, appending them to DST as the last
@@ -202,10 +208,7 @@ static void
 lex_stage_shift (struct lex_stage *dst, struct lex_stage *src, size_t n)
 {
   for (size_t i = 0; i < n; i++)
-    {
-      lex_stage_push_last (dst, lex_stage_first (src));
-      deque_pop_back (&src->deque);
-    }
+    lex_stage_push_last (dst, lex_stage_take_first (src));
 }
 
 /* A source of tokens, corresponding to a syntax file.
@@ -215,23 +218,32 @@ 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;
     bool eof;                   /* True if T_STOP was read from 'reader'. */
 
     /* Buffer of UTF-8 bytes. */
-    char *buffer;
+    char *buffer;               /* Source file contents. */
+    size_t length;              /* Number of bytes filled. */
     size_t allocated;           /* Number of bytes allocated. */
-    size_t tail;                /* &buffer[0] offset into UTF-8 source. */
-    size_t head;                /* &buffer[head - tail] offset into source. */
 
-    /* Positions in source file, tail <= pos <= head for each member here. */
+    /* Offsets into 'buffer'. */
     size_t journal_pos;         /* First byte not yet output to journal. */
     size_t seg_pos;             /* First byte not yet scanned as token. */
-    size_t line_pos;            /* First byte of line containing seg_pos. */
 
-    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.
@@ -246,17 +258,21 @@ struct lex_source
          in 'merge'.
 
        - merge: Tokens that need to pass through scan_merge() to end up in
-         'lookahead'.
+         'parse'.
 
-       - lookahead: Tokens available to the client for parsing. */
+       - parse: Tokens available to the client for parsing.
+
+      'pp' and 'merge' store tokens only temporarily until they pass into
+      'parse'.  Tokens then live in 'parse' until the command is fully
+      consumed, at which time they are freed together. */
     struct lex_stage pp;
     struct lex_stage merge;
-    struct lex_stage lookahead;
+    struct lex_token **parse;
+    size_t n_parse, allocated_parse, parse_ofs;
   };
 
 static struct lex_source *lex_source_create (struct lexer *,
                                              struct lex_reader *);
-static void lex_source_destroy (struct lex_source *);
 
 /* Lexer. */
 struct lexer
@@ -270,8 +286,10 @@ static char *lex_source_get_syntax__ (const struct lex_source *,
                                       int n0, int n1);
 static const struct lex_token *lex_next__ (const struct lexer *, int n);
 static void lex_source_push_endcmd__ (struct lex_source *);
+static void lex_source_push_parse (struct lex_source *, struct lex_token *);
+static void lex_source_clear_parse (struct lex_source *);
 
-static bool lex_source_get_lookahead (struct lex_source *);
+static bool lex_source_get_parse (struct lex_source *);
 static void lex_source_error_valist (struct lex_source *, int n0, int n1,
                                      const char *format, va_list)
    PRINTF_FORMAT (4, 0);
@@ -323,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);
     }
@@ -367,18 +388,32 @@ lex_get (struct lexer *lexer)
   if (src == NULL)
     return;
 
-  if (!lex_stage_is_empty (&src->lookahead))
-    lex_stage_pop_first (&src->lookahead);
+  if (src->parse_ofs < src->n_parse)
+    {
+      if (src->parse[src->parse_ofs]->token.type == T_ENDCMD)
+        lex_source_clear_parse (src);
+      else
+        src->parse_ofs++;
+    }
 
-  while (lex_stage_is_empty (&src->lookahead))
-    if (!lex_source_get_lookahead (src))
+  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;
       }
 }
+
+/* Advances LEXER by N tokens. */
+void
+lex_get_n (struct lexer *lexer, size_t n)
+{
+  while (n-- > 0)
+    lex_get (lexer);
+}
 \f
 /* Issuing errors. */
 
@@ -564,7 +599,8 @@ lex_next_error_valist (struct lexer *lexer, int n0, int n1,
           ds_put_cstr (&s, ": ");
           ds_put_vformat (&s, format, args);
         }
-      ds_put_byte (&s, '.');
+      if (ds_last (&s) != '.')
+        ds_put_byte (&s, '.');
       msg (SE, "%s", ds_cstr (&s));
       ds_destroy (&s);
     }
@@ -821,9 +857,14 @@ lex_force_int (struct lexer *lexer)
 bool
 lex_force_int_range (struct lexer *lexer, const char *name, long min, long max)
 {
+  bool is_number = lex_is_number (lexer);
   bool is_integer = lex_is_integer (lexer);
-  bool too_small = is_integer && lex_integer (lexer) < min;
-  bool too_big = is_integer && lex_integer (lexer) > max;
+  bool too_small = (is_integer ? lex_integer (lexer) < min
+                    : is_number ? lex_number (lexer) < min
+                    : false);
+  bool too_big = (is_integer ? lex_integer (lexer) > max
+                  : is_number ? lex_number (lexer) > max
+                  : false);
   if (is_integer && !too_small && !too_big)
     return true;
 
@@ -883,6 +924,14 @@ lex_force_int_range (struct lexer *lexer, const char *name, long min, long max)
               else
                 lex_error (lexer, _("Expected positive integer."));
             }
+          else
+            {
+              if (name)
+                lex_error (lexer, _("Expected integer %ld or greater for %s."),
+                           min, name);
+              else
+                lex_error (lexer, _("Expected integer %ld or greater."), min);
+            }
         }
       else if (report_upper_bound)
         {
@@ -1001,22 +1050,36 @@ 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_);
-  while (lex_stage_count (&src->lookahead) <= n)
+
+  if (ofs < 0)
+    {
+      static const struct lex_token endcmd_token
+        = { .token = { .type = T_ENDCMD } };
+      return &endcmd_token;
+    }
+
+  while (ofs >= src->n_parse)
     {
-      if (!lex_stage_is_empty (&src->lookahead))
+      if (src->n_parse > 0)
         {
-          const struct lex_token *t = lex_stage_last (&src->lookahead);
+          const struct lex_token *t = src->parse[src->n_parse - 1];
           if (t->token.type == T_STOP || t->token.type == T_ENDCMD)
             return t;
         }
 
-      lex_source_get_lookahead (src);
+      lex_source_get_parse (src);
     }
 
-  return lex_stage_nth (&src->lookahead, 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.
@@ -1077,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
@@ -1123,110 +1265,103 @@ lex_tokens_match (const struct token *actual, const struct token *expected)
     }
 }
 
-/* If LEXER is positioned at the sequence of tokens that may be parsed from S,
-   skips it and returns true.  Otherwise, returns false.
-
-   S may consist of an arbitrary sequence of tokens, e.g. "KRUSKAL-WALLIS",
-   "2SLS", or "END INPUT PROGRAM".  Identifiers may be abbreviated to their
-   first three letters. */
-bool
-lex_match_phrase (struct lexer *lexer, const char *s)
+static size_t
+lex_at_phrase__ (struct lexer *lexer, const char *s)
 {
   struct string_lexer slex;
   struct token token;
-  int i;
 
-  i = 0;
+  size_t i = 0;
   string_lexer_init (&slex, s, strlen (s), SEG_MODE_INTERACTIVE, true);
   while (string_lexer_next (&slex, &token))
     {
       bool match = lex_tokens_match (lex_next (lexer, i++), &token);
       token_uninit (&token);
       if (!match)
-        return false;
+        return 0;
     }
-
-  while (i-- > 0)
-    lex_get (lexer);
-  return true;
+  return i;
 }
 
-static int
-count_newlines (char *s, size_t length)
+/* If LEXER is positioned at the sequence of tokens that may be parsed from S,
+   returns true.  Otherwise, returns false.
+
+   S may consist of an arbitrary sequence of tokens, e.g. "KRUSKAL-WALLIS",
+   "2SLS", or "END INPUT PROGRAM".  Identifiers may be abbreviated to their
+   first three letters. */
+bool
+lex_at_phrase (struct lexer *lexer, const char *s)
 {
-  int n_newlines = 0;
-  char *newline;
+  return lex_at_phrase__ (lexer, s) > 0;
+}
 
-  while ((newline = memchr (s, '\n', length)) != NULL)
-    {
-      n_newlines++;
-      length -= (newline + 1) - s;
-      s = newline + 1;
-    }
+/* If LEXER is positioned at the sequence of tokens that may be parsed from S,
+   skips it and returns true.  Otherwise, returns false.
 
-  return n_newlines;
+   S may consist of an arbitrary sequence of tokens, e.g. "KRUSKAL-WALLIS",
+   "2SLS", or "END INPUT PROGRAM".  Identifiers may be abbreviated to their
+   first three letters. */
+bool
+lex_match_phrase (struct lexer *lexer, const char *s)
+{
+  size_t n = lex_at_phrase__ (lexer, s);
+  if (n > 0)
+    lex_get_n (lexer, n);
+  return n > 0;
 }
 
+/* Returns the 1-based line number of the source text at the byte OFFSET in
+   SRC. */
 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 - src->tail];
-      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
-count_columns (const char *s_, size_t length)
+lex_source_ofs_to_column_number (const struct lex_source *src, size_t offset)
 {
-  const uint8_t *s = CHAR_CAST (const uint8_t *, s_);
-  int columns;
-  size_t ofs;
-  int mblen;
-
-  columns = 0;
-  for (ofs = 0; ofs < length; ofs += mblen)
-    {
-      ucs4_t uc;
-
-      mblen = u8_mbtouc (&uc, s + ofs, length - ofs);
-      if (uc != '\t')
-        {
-          int width = uc_width (uc, "UTF-8");
-          if (width > 0)
-            columns += width;
-        }
-      else
-        columns = ROUND_UP (columns + 1, 8);
-    }
-
-  return columns + 1;
+  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 count_columns (&src->buffer[token->line_pos - src->tail],
-                        token->token_pos - token->line_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)
 {
-  char *start, *end, *newline;
+  return lex_source_ofs_to_point__ (src, token->token_pos);
+}
 
-  start = &src->buffer[token->line_pos - src->tail];
-  end = &src->buffer[(token->token_pos + token->token_len) - src->tail];
-  newline = memrchr (start, '\n', end - start);
-  if (newline != NULL)
-    start = newline + 1;
-  return count_columns (start, end - start);
+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
@@ -1235,11 +1370,9 @@ lex_token_location (const struct lex_source *src,
                     const struct lex_token *t1)
 {
   return (struct msg_location) {
-    .file_name = 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),
+    .file_name = intern_new_if_nonnull (src->reader->file_name),
+    .start = lex_token_start_point (src, t0),
+    .end = lex_token_end_point (src, t1),
   };
 }
 
@@ -1260,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.
@@ -1335,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 = xstrdup_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),
+    .file_name = intern_new_if_nonnull (lex_get_file_name (lexer)),
+    .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;
 }
 
@@ -1406,15 +1472,15 @@ lex_interactive_reset (struct lexer *lexer)
   struct lex_source *src = lex_source__ (lexer);
   if (src != NULL && src->reader->error == LEX_ERROR_TERMINAL)
     {
-      src->head = src->tail = 0;
-      src->journal_pos = src->seg_pos = src->line_pos = 0;
-      src->n_newlines = 0;
+      src->length = 0;
+      src->journal_pos = src->seg_pos = 0;
+      src->n_lines = 0;
       src->suppress_next_newline = false;
       src->segmenter = segmenter_init (segmenter_get_mode (&src->segmenter),
                                        false);
       lex_stage_clear (&src->pp);
       lex_stage_clear (&src->merge);
-      lex_stage_clear (&src->lookahead);
+      lex_source_clear_parse (src);
       lex_source_push_endcmd__ (src);
     }
 }
@@ -1439,58 +1505,22 @@ lex_discard_noninteractive (struct lexer *lexer)
     {
       lex_stage_clear (&src->pp);
       lex_stage_clear (&src->merge);
-      lex_stage_clear (&src->lookahead);
+      lex_source_clear_parse (src);
 
       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
-static size_t
-lex_source_max_tail__ (const struct lex_source *src_)
-{
-  struct lex_source *src = CONST_CAST (struct lex_source *, src_);
-
-  assert (src->seg_pos >= src->line_pos);
-  size_t max_tail = MIN (src->journal_pos, src->line_pos);
-
-  /* Use the oldest token also. */
-  struct lex_stage *stages[] = { &src->lookahead, &src->merge, &src->pp };
-  for (size_t i = 0; i < sizeof stages / sizeof *stages; i++)
-    if (!lex_stage_is_empty (stages[i]))
-      {
-        struct lex_token *first = lex_stage_first (stages[i]);
-        assert (first->token_pos >= first->line_pos);
-        return MIN (max_tail, first->line_pos);
-      }
-
-  return max_tail;
-}
-
 static void
 lex_source_expand__ (struct lex_source *src)
 {
-  if (src->head - src->tail >= src->allocated)
-    {
-      size_t max_tail = lex_source_max_tail__ (src);
-      if (max_tail > src->tail)
-        {
-          /* Advance the tail, freeing up room at the head. */
-          memmove (src->buffer, src->buffer + (max_tail - src->tail),
-                   src->head - max_tail);
-          src->tail = max_tail;
-        }
-      else
-        {
-          /* Buffer is completely full.  Expand it. */
-          src->buffer = x2realloc (src->buffer, &src->allocated);
-        }
-    }
-  else
-    {
-      /* There's space available at the head of the buffer.  Nothing to do. */
-    }
+  if (src->length >= src->allocated)
+    src->buffer = x2realloc (src->buffer, &src->allocated);
 }
 
 static void
@@ -1500,10 +1530,10 @@ lex_source_read__ (struct lex_source *src)
     {
       lex_source_expand__ (src);
 
-      size_t head_ofs = src->head - src->tail;
-      size_t space = src->allocated - head_ofs;
+      size_t space = src->allocated - src->length;
       enum prompt_style prompt = segmenter_get_prompt (&src->segmenter);
-      size_t n = src->reader->class->read (src->reader, &src->buffer[head_ofs],
+      size_t n = src->reader->class->read (src->reader,
+                                           &src->buffer[src->length],
                                            space, prompt);
       assert (n <= space);
 
@@ -1511,14 +1541,13 @@ lex_source_read__ (struct lex_source *src)
         {
           /* End of input. */
           src->reader->eof = true;
-          lex_source_expand__ (src);
           return;
         }
 
-      src->head += n;
+      src->length += n;
     }
-  while (!memchr (&src->buffer[src->seg_pos - src->tail], '\n',
-                  src->head - src->seg_pos));
+  while (!memchr (&src->buffer[src->seg_pos], '\n',
+                  src->length - src->seg_pos));
 }
 
 static struct lex_source *
@@ -1562,8 +1591,7 @@ lex_source_get_syntax__ (const struct lex_source *src, int n0, int n1)
         {
           size_t start = first->token_pos;
           size_t end = last->token_pos + last->token_len;
-          ds_put_substring (&s, ss_buffer (&src->buffer[start - src->tail],
-                                           end - start));
+          ds_put_substring (&s, ss_buffer (&src->buffer[start], end - start));
         }
       else
         {
@@ -1607,7 +1635,7 @@ lex_source_get_macro_call (struct lex_source *src, int n0, int n1)
   size_t start = token0->token_pos;
   size_t end = token1->token_pos + token1->token_len;
 
-  return ss_buffer (&src->buffer[start - src->tail], end - start);
+  return ss_buffer (&src->buffer[start], end - start);
 }
 
 static void
@@ -1677,8 +1705,7 @@ static void
 lex_get_error (struct lex_source *src, const struct lex_token *token)
 {
   char syntax[64];
-  str_ellipsize (ss_buffer (&src->buffer[token->token_pos - src->tail],
-                            token->token_len),
+  str_ellipsize (ss_buffer (&src->buffer[token->token_pos], token->token_len),
                  syntax, sizeof syntax);
 
   struct string s = DS_EMPTY_INITIALIZER;
@@ -1708,12 +1735,7 @@ lex_source_try_get_pp (struct lex_source *src)
   token->token = (struct token) { .type = T_STOP };
   token->macro_rep = NULL;
   token->ref_cnt = NULL;
-  token->line_pos = src->line_pos;
   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,9 +1743,9 @@ lex_source_try_get_pp (struct lex_source *src)
   int seg_len;
   for (;;)
     {
-      segment = &src->buffer[src->seg_pos - src->tail];
+      segment = &src->buffer[src->seg_pos];
       seg_len = segmenter_push (&src->segmenter, segment,
-                                src->head - src->seg_pos,
+                                src->length - src->seg_pos,
                                 src->reader->eof, &seg_type);
       if (seg_len >= 0)
         break;
@@ -1738,8 +1760,10 @@ lex_source_try_get_pp (struct lex_source *src)
   src->seg_pos += seg_len;
   if (seg_type == SEG_NEWLINE)
     {
-      src->line_pos = src->seg_pos;
-      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. */
@@ -1762,15 +1786,15 @@ lex_source_try_get_pp (struct lex_source *src)
   for (int i = 0; i < n_lines; i++)
     {
       /* Beginning of line. */
-      const char *line = &src->buffer[src->journal_pos - src->tail];
+      const char *line = &src->buffer[src->journal_pos];
 
       /* Calculate line length, including \n or \r\n end-of-line if present.
 
-         We use src->head even though that may be beyond what we've actually
-         converted to tokens (which is only through line_pos).  That's because,
-         if we're emitting the line due to SEG_END_COMMAND, we want to take the
-         whole line through the newline, not just through the '.'. */
-      size_t max_len = src->head - src->journal_pos;
+         We use src->length even though that may be beyond what we've actually
+         converted to tokens.  That's because, if we're emitting the line due
+         to SEG_END_COMMAND, we want to take the whole line through the
+         newline, not just through the '.'. */
+      size_t max_len = src->length - src->journal_pos;
       const char *newline = memchr (line, '\n', max_len);
       size_t line_len = newline ? newline - line + 1 : max_len;
 
@@ -1858,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 - src->tail], 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);
@@ -1911,8 +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,
-            .line_pos = c0->line_pos,
-            .first_line = c0->first_line,
             .macro_rep = macro_rep,
             .ofs = ofs[i],
             .len = len[i],
@@ -1954,7 +1974,7 @@ lex_source_get_merge (struct lex_source *src)
    Returns true if successful, false on failure.  In the latter case, SRC is
    exhausted and 'src->eof' is now true. */
 static bool
-lex_source_get_lookahead (struct lex_source *src)
+lex_source_get_parse (struct lex_source *src)
 {
   struct merger m = MERGER_INIT;
   struct token out;
@@ -1973,7 +1993,7 @@ lex_source_get_lookahead (struct lex_source *src)
                                &out);
       if (!retval)
         {
-          lex_stage_shift (&src->lookahead, &src->merge, 1);
+          lex_source_push_parse (src, lex_stage_take_first (&src->merge));
           return true;
         }
       else if (retval > 0)
@@ -1988,8 +2008,6 @@ lex_source_get_lookahead (struct lex_source *src)
             .token = out,
             .token_pos = first->token_pos,
             .token_len = (last->token_pos - first->token_pos) + last->token_len,
-            .line_pos = first->line_pos,
-            .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
@@ -2001,7 +2019,7 @@ lex_source_get_lookahead (struct lex_source *src)
           };
           if (t->ref_cnt)
             ++*t->ref_cnt;
-          lex_stage_push_last (&src->lookahead, t);
+          lex_source_push_parse (src, t);
 
           for (int i = 0; i < retval; i++)
             lex_stage_pop_first (&src->merge);
@@ -2013,20 +2031,46 @@ lex_source_get_lookahead (struct lex_source *src)
 static void
 lex_source_push_endcmd__ (struct lex_source *src)
 {
-  assert (lex_stage_is_empty (&src->lookahead));
+  assert (src->n_parse == 0);
+
   struct lex_token *token = xmalloc (sizeof *token);
   *token = (struct lex_token) { .token = { .type = T_ENDCMD } };
-  lex_stage_push_last (&src->lookahead, token);
+  lex_source_push_parse (src, token);
+}
+
+static void
+lex_source_push_parse (struct lex_source *src, struct lex_token *token)
+{
+  if (src->n_parse >= src->allocated_parse)
+    src->parse = x2nrealloc (src->parse, &src->allocated_parse,
+                             sizeof *src->parse);
+  src->parse[src->n_parse++] = token;
+}
+
+static void
+lex_source_clear_parse (struct lex_source *src)
+{
+  for (size_t i = 0; i < src->n_parse; i++)
+    lex_token_destroy (src->parse[i]);
+  src->n_parse = src->parse_ofs = 0;
 }
 
 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);
@@ -2034,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)
@@ -2044,10 +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_stage_uninit (&src->lookahead);
-  ll_remove (&src->ll);
+  lex_source_clear_parse (src);
+  free (src->parse);
   free (src);
 }
 \f
@@ -2223,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);
+}