]> git.saurik.com Git - bison.git/blobdiff - src/scan-gram.l
Factor %FLAG at scan level.
[bison.git] / src / scan-gram.l
index 2ed043f07aab242127032c884a77c6e73bfd6a93..2cfbb4e4c1b96bb3fd6fd71d3082e436a7b70f2a 100644 (file)
@@ -1,11 +1,13 @@
 /* Bison Grammar Scanner                             -*- C -*-
-   Copyright (C) 2002 Free Software Foundation, Inc.
+
+   Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009
+   Free Software Foundation, Inc.
 
    This file is part of Bison, the GNU Compiler Compiler.
 
-   This program is free software; you can redistribute it and/or modify
+   This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
-   02111-1307  USA
-*/
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
-%option debug nodefault noyywrap never-interactive
+%option debug nodefault nounput noyywrap never-interactive
 %option prefix="gram_" outfile="lex.yy.c"
 
 %{
-#include "system.h"
-#include "mbswidth.h"
-#include "complain.h"
-#include "quote.h"
-#include "struniq.h"
-#include "getargs.h"
-#include "gram.h"
-#include "reader.h"
-
-#define YY_USER_INIT                           \
-do {                                           \
-  LOCATION_RESET (*loc);                       \
-  loc->file = current_file;                    \
-} while (0)
-
-/* Each time we match a string, move the end cursor to its end. */
-#define STEP  LOCATION_STEP (*loc)
-
-#define YY_USER_ACTION  extend_location (loc, yytext, yyleng);
-
-#define YY_INPUT(buf, result, size) ((result) = no_cr_read (yyin, buf, size))
-
-
-/* Read bytes from FP into buffer BUF of size SIZE.  Return the
-   number of bytes read.  Remove '\r' from input, treating \r\n
-   and isolated \r as \n.  */
-
-static size_t
-no_cr_read (FILE *fp, char *buf, size_t size)
-{
-  size_t s = fread (buf, 1, size, fp);
-  if (s)
-    {
-      char *w = memchr (buf, '\r', s);
-      if (w)
-       {
-         char const *r = ++w;
-         char const *lim = buf + s;
-
-         for (;;)
-           {
-             /* Found an '\r'.  Treat it like '\n', but ignore any
-                '\n' that immediately follows.  */
-             w[-1] = '\n';
-             if (r == lim)
-               {
-                 int ch = getc (fp);
-                 if (ch != '\n' && ungetc (ch, fp) != ch)
-                   break;
-               }
-             else if (*r == '\n')
-               r++;
-
-             /* Copy until the next '\r'.  */
-             do
-               {
-                 if (r == lim)
-                   return w - buf;
-               }
-             while ((*w++ = *r++) != '\r');
-           }
+/* Work around a bug in flex 2.5.31.  See Debian bug 333231
+   <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>.  */
+#undef gram_wrap
+#define gram_wrap() 1
 
-         return w - buf;
-       }
-    }
+#define FLEX_PREFIX(Id) gram_ ## Id
+#include <src/flex-scanner.h>
 
-  return s;
-}
+#include <src/complain.h>
+#include <src/files.h>
+#include <src/gram.h>
+#include <quotearg.h>
+#include <src/reader.h>
+#include <src/uniqstr.h>
 
+#include <mbswidth.h>
+#include <quote.h>
 
-/* Extend *LOC to account for token TOKEN of size SIZE.  */
+#include <src/scan-gram.h>
 
-static void
-extend_location (location_t *loc, char const *token, int size)
-{
-  int line = loc->last_line;
-  int column = loc->last_column;
-  char const *p0 = token;
-  char const *p = token;
-  char const *lim = token + size;
-
-  for (p = token; p < lim; p++)
-    switch (*p)
-      {
-      case '\r':
-       /* \r shouldn't survive no_cr_read.  */
-       abort ();
-
-      case '\n':
-       line++;
-       column = 1;
-       p0 = p + 1;
-       break;
-
-      case '\t':
-       column += mbsnwidth (p0, p - p0, 0);
-       column += 8 - ((column - 1) & 7);
-       p0 = p + 1;
-       break;
-      }
+#define YY_DECL GRAM_LEX_DECL
 
-  loc->last_line = line;
-  loc->last_column = column + mbsnwidth (p0, p - p0, 0);
-}
+#define YY_USER_INIT                                   \
+   code_start = scanner_cursor = loc->start;           \
 
+/* Location of scanner cursor.  */
+static boundary scanner_cursor;
 
+#define YY_USER_ACTION  location_compute (loc, &scanner_cursor, yytext, yyleng);
 
-/* STRING_OBSTACK -- Used to store all the characters that we need to
-   keep (to construct ID, STRINGS etc.).  Use the following macros to
-   use it.
+static size_t no_cr_read (FILE *, char *, size_t);
+#define YY_INPUT(buf, result, size) ((result) = no_cr_read (yyin, buf, size))
 
-   Use STRING_GROW to append what has just been matched, and
-   STRING_FINISH to end the string (it puts the ending 0).
-   STRING_FINISH also stores this string in LAST_STRING, which can be
-   used, and which is used by STRING_FREE to free the last string.  */
+#define RETURN_PERCENT_FLAG(Value)              \
+  do {                                          \
+    val->uniqstr = uniqstr_new (Value);         \
+    return PERCENT_FLAG;                        \
+  } while (0)
 
-static struct obstack string_obstack;
 
 /* A string representing the most recently saved token.  */
 static char *last_string;
 
-
-#define STRING_GROW   \
-  obstack_grow (&string_obstack, yytext, yyleng)
-
-#define STRING_FINISH                                  \
-  do {                                                 \
-    obstack_1grow (&string_obstack, '\0');             \
-    last_string = obstack_finish (&string_obstack);    \
-  } while (0)
-
-#define STRING_FREE \
-  obstack_free (&string_obstack, last_string)
-
 void
-scanner_last_string_free (void)
+gram_scanner_last_string_free (void)
 {
   STRING_FREE;
 }
 
-/* Within well-formed rules, RULE_LENGTH is the number of values in
-   the current rule so far, which says where to find `$0' with respect
-   to the top of the stack.  It is not the same as the rule->length in
-   the case of mid rule actions.
-
-   Outside of well-formed rules, RULE_LENGTH has an undefined value.  */
-static int rule_length;
-
-static void handle_dollar (braced_code_t code_kind,
-                          char *cp, location_t location);
-static void handle_at (braced_code_t code_kind,
-                      char *cp, location_t location);
-static void handle_syncline (char *args, location_t *location);
+static void handle_syncline (char *, location);
+static unsigned long int scan_integer (char const *p, int base, location loc);
 static int convert_ucn_to_byte (char const *hex_text);
-static void unexpected_end_of_file (location_t *, char const *);
+static void unexpected_eof (boundary, char const *);
+static void unexpected_newline (boundary, char const *);
 
 %}
-%x SC_COMMENT SC_LINE_COMMENT SC_YACC_COMMENT
-%x SC_STRING SC_CHARACTER
+ /* A C-like comment in directives/rules. */
+%x SC_YACC_COMMENT
+ /* Strings and characters in directives/rules. */
 %x SC_ESCAPED_STRING SC_ESCAPED_CHARACTER
-%x SC_BRACED_CODE SC_PROLOGUE SC_EPILOGUE
+ /* A identifier was just read in directives/rules.  Special state
+    to capture the sequence `identifier :'. */
+%x SC_AFTER_IDENTIFIER
+ /* A complex tag, with nested angles brackets. */
+%x SC_TAG
+
+ /* Three types of user code:
+    - prologue (code between `%{' `%}' in the first section, before %%);
+    - actions, printers, union, etc, (between braced in the middle section);
+    - epilogue (everything after the second %%). */
+%x SC_PROLOGUE SC_BRACED_CODE SC_EPILOGUE
+ /* C and C++ comments in code. */
+%x SC_COMMENT SC_LINE_COMMENT
+ /* Strings and characters in code. */
+%x SC_STRING SC_CHARACTER
 
 letter   [.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_]
 id       {letter}({letter}|[0-9])*
 directive %{letter}({letter}|[0-9]|-)*
-int      [0-9]+
+int      [0-9]+
 
 /* POSIX says that a tag must be both an id and a C union member, but
    historically almost any character is allowed in a tag.  We disallow
-   NUL and newline, as this simplifies our implementation.  */
-tag     [^\0\n>]+
+   NUL, as this simplifies our implementation.  We disallow angle
+   bracket to match them in nested pairs: several languages use them
+   for generics/template types.  */
+tag     [^\0<>]+
 
 /* Zero or more instances of backslash-newline.  Following GCC, allow
    white space between the backslash and the newline.  */
@@ -201,122 +116,161 @@ splice   (\\[ \f\t\v]*\n)*
 
 %%
 %{
-  /* Nesting level of the current code in braces.  */
-  int braces_level IF_LINT (= 0);
+  /* Nesting level.  Either for nested braces, or nested angle brackets
+     (but not mixed).  */
+  int nesting IF_LINT (= 0);
+
+  /* Parent context state, when applicable.  */
+  int context_state IF_LINT (= 0);
 
-  /* Scanner context when scanning C code.  */
-  int c_context IF_LINT (= 0);
+  /* Location of most recent identifier, when applicable.  */
+  location id_loc IF_LINT (= empty_location);
 
-  /* At each yylex invocation, mark the current position as the
-     start of the next token.  */
-  STEP;
+  /* Where containing code started, when applicable.  Its initial
+     value is relevant only when yylex is invoked in the SC_EPILOGUE
+     start condition.  */
+  boundary code_start = scanner_cursor;
+
+  /* Where containing comment or string or character literal started,
+     when applicable.  */
+  boundary token_start IF_LINT (= scanner_cursor);
 %}
 
 
+  /*-----------------------.
+  | Scanning white space.  |
+  `-----------------------*/
+
+<INITIAL,SC_AFTER_IDENTIFIER>
+{
+  /* Comments and white space.  */
+  ","         warn_at (*loc, _("stray `,' treated as white space"));
+  [ \f\n\t\v]  |
+  "//".*       ;
+  "/*" {
+    token_start = loc->start;
+    context_state = YY_START;
+    BEGIN SC_YACC_COMMENT;
+  }
+
+  /* #line directives are not documented, and may be withdrawn or
+     modified in future versions of Bison.  */
+  ^"#line "{int}" \"".*"\"\n" {
+    handle_syncline (yytext + sizeof "#line " - 1, *loc);
+  }
+}
+
+
   /*----------------------------.
   | Scanning Bison directives.  |
   `----------------------------*/
+
+  /* For directives that are also command line options, the regex must be
+       "%..."
+     after "[-_]"s are removed, and the directive must match the --long
+     option name, with a single string argument.  Otherwise, add exceptions
+     to ../build-aux/cross-options.pl.  */
+
 <INITIAL>
 {
-  "%binary"               return PERCENT_NONASSOC;
-  "%debug"                return PERCENT_DEBUG;
-  "%define"               return PERCENT_DEFINE;
-  "%defines"              return PERCENT_DEFINES;
-  "%destructor"           return PERCENT_DESTRUCTOR;
-  "%dprec"               return PERCENT_DPREC;
-  "%error"[-_]"verbose"   return PERCENT_ERROR_VERBOSE;
-  "%expect"               return PERCENT_EXPECT;
-  "%file-prefix"          return PERCENT_FILE_PREFIX;
+  "%binary"                         return PERCENT_NONASSOC;
+  "%code"                           return PERCENT_CODE;
+  "%debug"                          RETURN_PERCENT_FLAG("debug");
+  "%default"[-_]"prec"              return PERCENT_DEFAULT_PREC;
+  "%define"                         return PERCENT_DEFINE;
+  "%defines"                        return PERCENT_DEFINES;
+  "%destructor"                     return PERCENT_DESTRUCTOR;
+  "%dprec"                          return PERCENT_DPREC;
+  "%error"[-_]"verbose"             RETURN_PERCENT_FLAG("error_verbose");
+  "%expect"                         return PERCENT_EXPECT;
+  "%expect"[-_]"rr"                 return PERCENT_EXPECT_RR;
+  "%file-prefix"                    return PERCENT_FILE_PREFIX;
   "%fixed"[-_]"output"[-_]"files"   return PERCENT_YACC;
-  "%glr-parser"           return PERCENT_GLR_PARSER;
-  "%left"                 return PERCENT_LEFT;
-  "%locations"            return PERCENT_LOCATIONS;
-  "%merge"               return PERCENT_MERGE;
-  "%name"[-_]"prefix"     return PERCENT_NAME_PREFIX;
-  "%no"[-_]"lines"        return PERCENT_NO_LINES;
-  "%nonassoc"             return PERCENT_NONASSOC;
-  "%nterm"                return PERCENT_NTERM;
-  "%output"               return PERCENT_OUTPUT;
-  "%parse-param"          return PERCENT_PARSE_PARAM;
-  "%prec"                 rule_length--; return PERCENT_PREC;
-  "%printer"              return PERCENT_PRINTER;
-  "%pure"[-_]"parser"     return PERCENT_PURE_PARSER;
-  "%right"                return PERCENT_RIGHT;
-  "%lex-param"            return PERCENT_LEX_PARAM;
-  "%skeleton"             return PERCENT_SKELETON;
-  "%start"                return PERCENT_START;
-  "%term"                 return PERCENT_TOKEN;
-  "%token"                return PERCENT_TOKEN;
-  "%token"[-_]"table"     return PERCENT_TOKEN_TABLE;
-  "%type"                 return PERCENT_TYPE;
-  "%union"                return PERCENT_UNION;
-  "%verbose"              return PERCENT_VERBOSE;
-  "%yacc"                 return PERCENT_YACC;
-
-  {directive}             {
+  "%initial-action"                 return PERCENT_INITIAL_ACTION;
+  "%glr-parser"                     return PERCENT_GLR_PARSER;
+  "%language"                       return PERCENT_LANGUAGE;
+  "%left"                           return PERCENT_LEFT;
+  "%lex-param"                      return PERCENT_LEX_PARAM;
+  "%locations"                      return PERCENT_LOCATIONS;
+  "%merge"                          return PERCENT_MERGE;
+  "%name"[-_]"prefix"               return PERCENT_NAME_PREFIX;
+  "%no"[-_]"default"[-_]"prec"      return PERCENT_NO_DEFAULT_PREC;
+  "%no"[-_]"lines"                  return PERCENT_NO_LINES;
+  "%nonassoc"                       return PERCENT_NONASSOC;
+  "%nondeterministic-parser"        return PERCENT_NONDETERMINISTIC_PARSER;
+  "%nterm"                          return PERCENT_NTERM;
+  "%output"                         return PERCENT_OUTPUT;
+  "%parse-param"                    return PERCENT_PARSE_PARAM;
+  "%prec"                           return PERCENT_PREC;
+  "%precedence"                     return PERCENT_PRECEDENCE;
+  "%printer"                        return PERCENT_PRINTER;
+  "%pure"[-_]"parser"               return PERCENT_PURE_PARSER;
+  "%require"                        return PERCENT_REQUIRE;
+  "%right"                          return PERCENT_RIGHT;
+  "%skeleton"                       return PERCENT_SKELETON;
+  "%start"                          return PERCENT_START;
+  "%term"                           return PERCENT_TOKEN;
+  "%token"                          return PERCENT_TOKEN;
+  "%token"[-_]"table"               return PERCENT_TOKEN_TABLE;
+  "%type"                           return PERCENT_TYPE;
+  "%union"                          return PERCENT_UNION;
+  "%verbose"                        return PERCENT_VERBOSE;
+  "%yacc"                           return PERCENT_YACC;
+
+  {directive} {
     complain_at (*loc, _("invalid directive: %s"), quote (yytext));
-    STEP;
-  }
-
-  ^"#line "{int}" \"".*"\"\n" {
-    handle_syncline (yytext + sizeof "#line " - 1, loc);
-    STEP;
   }
 
   "="                     return EQUAL;
-  ":"                     rule_length = 0; return COLON;
-  "|"                     rule_length = 0; return PIPE;
+  "|"                     return PIPE;
   ";"                     return SEMICOLON;
 
-  [ \f\n\t\v]  STEP;
-
-  "," {
-    warn_at (*loc, _("stray `,' treated as white space"));
-    STEP;
-  }
-
-  {id}        {
-    val->symbol = symbol_get (yytext, *loc);
-    rule_length++;
-    return ID;
+  {id} {
+    val->uniqstr = uniqstr_new (yytext);
+    id_loc = *loc;
+    BEGIN SC_AFTER_IDENTIFIER;
   }
 
   {int} {
-    unsigned long num;
-    errno = 0;
-    num = strtoul (yytext, 0, 10);
-    if (INT_MAX < num || errno)
-      {
-       complain_at (*loc, _("integer out of range: %s"), quote (yytext));
-       num = INT_MAX;
-      }
-    val->integer = num;
+    val->integer = scan_integer (yytext, 10, *loc);
+    return INT;
+  }
+  0[xX][0-9abcdefABCDEF]+ {
+    val->integer = scan_integer (yytext, 16, *loc);
     return INT;
   }
 
   /* Characters.  We don't check there is only one.  */
-  "'"         STRING_GROW; BEGIN SC_ESCAPED_CHARACTER;
+  "'"        STRING_GROW; token_start = loc->start; BEGIN SC_ESCAPED_CHARACTER;
 
   /* Strings. */
-  "\""        STRING_GROW; BEGIN SC_ESCAPED_STRING;
-
-  /* Comments. */
-  "/*"        BEGIN SC_YACC_COMMENT;
-  "//".*      STEP;
+  "\""       token_start = loc->start; BEGIN SC_ESCAPED_STRING;
 
   /* Prologue. */
-  "%{"        BEGIN SC_PROLOGUE;
+  "%{"        code_start = loc->start; BEGIN SC_PROLOGUE;
 
   /* Code in between braces.  */
-  "{"         STRING_GROW; braces_level = 0; BEGIN SC_BRACED_CODE;
+  "{" {
+    STRING_GROW;
+    nesting = 0;
+    code_start = loc->start;
+    BEGIN SC_BRACED_CODE;
+  }
 
   /* A type. */
+  "<*>"       return TAG_ANY;
+  "<>"        return TAG_NONE;
   "<"{tag}">" {
-    obstack_grow (&string_obstack, yytext + 1, yyleng - 2);
+    obstack_grow (&obstack_for_string, yytext + 1, yyleng - 2);
     STRING_FINISH;
-    val->struniq = struniq_new (last_string);
+    val->uniqstr = uniqstr_new (last_string);
     STRING_FREE;
-    return TYPE;
+    return TAG;
+  }
+  "<"         {
+    nesting = 0;
+    token_start = loc->start;
+    BEGIN SC_TAG;
   }
 
   "%%" {
@@ -328,7 +282,48 @@ splice      (\\[ \f\t\v]*\n)*
 
   . {
     complain_at (*loc, _("invalid character: %s"), quote (yytext));
-    STEP;
+  }
+
+  <<EOF>> {
+    loc->start = loc->end = scanner_cursor;
+    yyterminate ();
+  }
+}
+
+
+  /*--------------------------------------------------------------.
+  | Supporting \0 complexifies our implementation for no expected |
+  | added value.                                                  |
+  `--------------------------------------------------------------*/
+
+<SC_ESCAPED_CHARACTER,SC_ESCAPED_STRING,SC_TAG>
+{
+  \0       complain_at (*loc, _("invalid null character"));
+}
+
+
+  /*-----------------------------------------------------------------.
+  | Scanning after an identifier, checking whether a colon is next.  |
+  `-----------------------------------------------------------------*/
+
+<SC_AFTER_IDENTIFIER>
+{
+  ":" {
+    *loc = id_loc;
+    BEGIN INITIAL;
+    return ID_COLON;
+  }
+  . {
+    scanner_cursor.column -= mbsnwidth (yytext, yyleng, 0);
+    yyless (0);
+    *loc = id_loc;
+    BEGIN INITIAL;
+    return ID;
+  }
+  <<EOF>> {
+    *loc = id_loc;
+    BEGIN INITIAL;
+    return ID;
   }
 }
 
@@ -339,13 +334,9 @@ splice      (\\[ \f\t\v]*\n)*
 
 <SC_YACC_COMMENT>
 {
-  "*/" {
-    STEP;
-    BEGIN INITIAL;
-  }
-
+  "*/"     BEGIN context_state;
   .|\n    ;
-  <<EOF>>  unexpected_end_of_file (loc, "*/");
+  <<EOF>>  unexpected_eof (token_start, "*/"); BEGIN context_state;
 }
 
 
@@ -355,8 +346,8 @@ splice       (\\[ \f\t\v]*\n)*
 
 <SC_COMMENT>
 {
-  "*"{splice}"/"  STRING_GROW; BEGIN c_context;
-  <<EOF>>        unexpected_end_of_file (loc, "*/");
+  "*"{splice}"/"  STRING_GROW; BEGIN context_state;
+  <<EOF>>        unexpected_eof (token_start, "*/"); BEGIN context_state;
 }
 
 
@@ -366,56 +357,104 @@ splice    (\\[ \f\t\v]*\n)*
 
 <SC_LINE_COMMENT>
 {
-  "\n"          STRING_GROW; BEGIN c_context;
+  "\n"          STRING_GROW; BEGIN context_state;
   {splice}      STRING_GROW;
-  <<EOF>>       BEGIN c_context;
+  <<EOF>>       BEGIN context_state;
 }
 
 
-  /*----------------------------------------------------------------.
-  | Scanning a C string, including its escapes.  The initial `"' is |
-  | already eaten.                                                  |
-  `----------------------------------------------------------------*/
+  /*------------------------------------------------.
+  | Scanning a Bison string, including its escapes. |
+  | The initial quote is already eaten.             |
+  `------------------------------------------------*/
 
 <SC_ESCAPED_STRING>
 {
-  "\"" {
-    STRING_GROW;
+  "\""|"\n" {
+    if (yytext[0] == '\n')
+      unexpected_newline (token_start, "\"");
     STRING_FINISH;
-    val->string = last_string;
-    rule_length++;
+    loc->start = token_start;
+    val->chars = last_string;
+    BEGIN INITIAL;
+    return STRING;
+  }
+  <<EOF>> {
+    unexpected_eof (token_start, "\"");
+    STRING_FINISH;
+    loc->start = token_start;
+    val->chars = last_string;
     BEGIN INITIAL;
     return STRING;
   }
-
-  .|\n     STRING_GROW;
-  <<EOF>>   unexpected_end_of_file (loc, "\"");
 }
 
-  /*---------------------------------------------------------------.
-  | Scanning a C character, decoding its escapes.  The initial "'" |
-  | is already eaten.                                              |
-  `---------------------------------------------------------------*/
+  /*----------------------------------------------------------.
+  | Scanning a Bison character literal, decoding its escapes. |
+  | The initial quote is already eaten.                              |
+  `----------------------------------------------------------*/
 
 <SC_ESCAPED_CHARACTER>
 {
-  "'" {
+  "'"|"\n" {
+    if (yytext[0] == '\n')
+      unexpected_newline (token_start, "'");
     STRING_GROW;
     STRING_FINISH;
-    val->symbol = symbol_get (last_string, *loc);
-    symbol_class_set (val->symbol, token_sym, *loc);
-    symbol_user_token_number_set (val->symbol,
-                                 (unsigned char) last_string[1], *loc);
+    loc->start = token_start;
+    val->character = last_string[1];
     STRING_FREE;
-    rule_length++;
     BEGIN INITIAL;
-    return ID;
+    return CHAR;
+  }
+  <<EOF>> {
+    unexpected_eof (token_start, "'");
+    STRING_FINISH;
+    loc->start = token_start;
+    if (strlen(last_string) > 1)
+      val->character = last_string[1];
+    else
+      val->character = last_string[0];
+    STRING_FREE;
+    BEGIN INITIAL;
+    return CHAR;
   }
-
-  .|\n     STRING_GROW;
-  <<EOF>>   unexpected_end_of_file (loc, "'");
 }
 
+  /*-----------------------------------------------------------.
+  | Scanning a Bison nested tag.  The initial angle bracket is |
+  | already eaten.                                             |
+  `-----------------------------------------------------------*/
+
+<SC_TAG>
+{
+  ">" {
+    --nesting;
+    if (nesting < 0)
+      {
+        STRING_FINISH;
+        loc->start = token_start;
+        val->uniqstr = uniqstr_new (last_string);
+        STRING_FREE;
+        BEGIN INITIAL;
+        return TAG;
+      }
+    STRING_GROW;
+  }
+
+  [^<>]+ STRING_GROW;
+  "<"+   STRING_GROW; nesting += yyleng;
+
+  <<EOF>> {
+    unexpected_eof (token_start, ">");
+    STRING_FINISH;
+    loc->start = token_start;
+    val->uniqstr = uniqstr_new (last_string);
+    STRING_FREE;
+    BEGIN INITIAL;
+    return TAG;
+  }
+}
 
   /*----------------------------.
   | Decode escaped characters.  |
@@ -424,84 +463,73 @@ splice     (\\[ \f\t\v]*\n)*
 <SC_ESCAPED_STRING,SC_ESCAPED_CHARACTER>
 {
   \\[0-7]{1,3} {
-    unsigned long c = strtoul (yytext + 1, 0, 8);
+    unsigned long int c = strtoul (yytext + 1, NULL, 8);
     if (UCHAR_MAX < c)
-      {
-       complain_at (*loc, _("invalid escape sequence: %s"),
-                    quote (yytext));
-       STEP;
-      }
+      complain_at (*loc, _("invalid escape sequence: %s"), quote (yytext));
+    else if (! c)
+      complain_at (*loc, _("invalid null character: %s"), quote (yytext));
     else
-      obstack_1grow (&string_obstack, c);
+      obstack_1grow (&obstack_for_string, c);
   }
 
-  \\x[0-9a-fA-F]+ {
-    unsigned long c;
-    errno = 0;
-    c = strtoul (yytext + 2, 0, 16);
-    if (UCHAR_MAX < c || errno)
-      {
-       complain_at (*loc, _("invalid escape sequence: %s"),
-                    quote (yytext));
-       STEP;
-      }
+  \\x[0-9abcdefABCDEF]+ {
+    verify (UCHAR_MAX < ULONG_MAX);
+    unsigned long int c = strtoul (yytext + 2, NULL, 16);
+    if (UCHAR_MAX < c)
+      complain_at (*loc, _("invalid escape sequence: %s"), quote (yytext));
+    else if (! c)
+      complain_at (*loc, _("invalid null character: %s"), quote (yytext));
     else
-      obstack_1grow (&string_obstack, c);
+      obstack_1grow (&obstack_for_string, c);
   }
 
-  \\a  obstack_1grow (&string_obstack, '\a');
-  \\b  obstack_1grow (&string_obstack, '\b');
-  \\f  obstack_1grow (&string_obstack, '\f');
-  \\n  obstack_1grow (&string_obstack, '\n');
-  \\r  obstack_1grow (&string_obstack, '\r');
-  \\t  obstack_1grow (&string_obstack, '\t');
-  \\v  obstack_1grow (&string_obstack, '\v');
+  \\a  obstack_1grow (&obstack_for_string, '\a');
+  \\b  obstack_1grow (&obstack_for_string, '\b');
+  \\f  obstack_1grow (&obstack_for_string, '\f');
+  \\n  obstack_1grow (&obstack_for_string, '\n');
+  \\r  obstack_1grow (&obstack_for_string, '\r');
+  \\t  obstack_1grow (&obstack_for_string, '\t');
+  \\v  obstack_1grow (&obstack_for_string, '\v');
 
   /* \\[\"\'?\\] would be shorter, but it confuses xgettext.  */
-  \\("\""|"'"|"?"|"\\")  obstack_1grow (&string_obstack, yytext[1]);
+  \\("\""|"'"|"?"|"\\")  obstack_1grow (&obstack_for_string, yytext[1]);
 
-  \\(u|U[0-9a-fA-F]{4})[0-9a-fA-F]{4} {
+  \\(u|U[0-9abcdefABCDEF]{4})[0-9abcdefABCDEF]{4} {
     int c = convert_ucn_to_byte (yytext);
     if (c < 0)
-      {
-       complain_at (*loc, _("invalid escape sequence: %s"),
-                    quote (yytext));
-       STEP;
-      }
+      complain_at (*loc, _("invalid escape sequence: %s"), quote (yytext));
+    else if (! c)
+      complain_at (*loc, _("invalid null character: %s"), quote (yytext));
     else
-      obstack_1grow (&string_obstack, c);
+      obstack_1grow (&obstack_for_string, c);
   }
   \\(.|\n)     {
-    complain_at (*loc, _("unrecognized escape sequence: %s"),
-                quote (yytext));
+    complain_at (*loc, _("unrecognized escape sequence: %s"), quote (yytext));
     STRING_GROW;
   }
 }
 
+  /*--------------------------------------------.
+  | Scanning user-code characters and strings.  |
+  `--------------------------------------------*/
 
-  /*----------------------------------------------------------.
-  | Scanning a C character without decoding its escapes.  The |
-  | initial "'" is already eaten.                             |
-  `----------------------------------------------------------*/
+<SC_CHARACTER,SC_STRING>
+{
+  {splice}|\\{splice}[^\n\[\]] STRING_GROW;
+}
 
 <SC_CHARACTER>
 {
-  "'"                  STRING_GROW; BEGIN c_context;
-  \\{splice}[^$@\[\]]  STRING_GROW;
-  <<EOF>>              unexpected_end_of_file (loc, "'");
+  "'"          STRING_GROW; BEGIN context_state;
+  \n           unexpected_newline (token_start, "'"); BEGIN context_state;
+  <<EOF>>      unexpected_eof (token_start, "'"); BEGIN context_state;
 }
 
-
-  /*----------------------------------------------------------------.
-  | Scanning a C string, without decoding its escapes.  The initial |
-  | `"' is already eaten.                                           |
-  `----------------------------------------------------------------*/
-
 <SC_STRING>
 {
-  "\""                 STRING_GROW; BEGIN c_context;
-  \\{splice}[^$@\[\]]  STRING_GROW;
-  <<EOF>>              unexpected_end_of_file (loc, "\"");
+  "\""         STRING_GROW; BEGIN context_state;
+  \n           unexpected_newline (token_start, "\""); BEGIN context_state;
+  <<EOF>>      unexpected_eof (token_start, "\""); BEGIN context_state;
 }
 
 
@@ -511,30 +539,51 @@ splice     (\\[ \f\t\v]*\n)*
 
 <SC_BRACED_CODE,SC_PROLOGUE,SC_EPILOGUE>
 {
-  "'"            STRING_GROW; c_context = YY_START; BEGIN SC_CHARACTER;
-  "\""           STRING_GROW; c_context = YY_START; BEGIN SC_STRING;
-  "/"{splice}"*"  STRING_GROW; c_context = YY_START; BEGIN SC_COMMENT;
-  "/"{splice}"/"  STRING_GROW; c_context = YY_START; BEGIN SC_LINE_COMMENT;
+  "'" {
+    STRING_GROW;
+    context_state = YY_START;
+    token_start = loc->start;
+    BEGIN SC_CHARACTER;
+  }
+  "\"" {
+    STRING_GROW;
+    context_state = YY_START;
+    token_start = loc->start;
+    BEGIN SC_STRING;
+  }
+  "/"{splice}"*" {
+    STRING_GROW;
+    context_state = YY_START;
+    token_start = loc->start;
+    BEGIN SC_COMMENT;
+  }
+  "/"{splice}"/" {
+    STRING_GROW;
+    context_state = YY_START;
+    BEGIN SC_LINE_COMMENT;
+  }
 }
 
 
-  /*---------------------------------------------------------------.
-  | Scanning some code in braces (%union and actions). The initial |
-  | "{" is already eaten.                                          |
-  `---------------------------------------------------------------*/
+
+  /*-----------------------------------------------------------.
+  | Scanning some code in braces (actions). The initial "{" is |
+  | already eaten.                                             |
+  `-----------------------------------------------------------*/
 
 <SC_BRACED_CODE>
 {
-  "{"|"<"{splice}"%"  STRING_GROW; braces_level++;
-  "%"{splice}">"      STRING_GROW; braces_level--;
+  "{"|"<"{splice}"%"  STRING_GROW; nesting++;
+  "%"{splice}">"      STRING_GROW; nesting--;
   "}" {
-    STRING_GROW;
-    braces_level--;
-    if (braces_level < 0)
+    obstack_1grow (&obstack_for_string, '}');
+
+    --nesting;
+    if (nesting < 0)
       {
        STRING_FINISH;
-       val->string = last_string;
-       rule_length++;
+       loc->start = code_start;
+       val->code = last_string;
        BEGIN INITIAL;
        return BRACED_CODE;
       }
@@ -544,12 +593,14 @@ splice     (\\[ \f\t\v]*\n)*
      (as `<' `<%').  */
   "<"{splice}"<"  STRING_GROW;
 
-  "$"("<"{tag}">")?(-?[0-9]+|"$") { handle_dollar (current_braced_code,
-                                                  yytext, *loc); }
-  "@"(-?[0-9]+|"$")               { handle_at (current_braced_code,
-                                              yytext, *loc); }
-
-  <<EOF>>  unexpected_end_of_file (loc, "}");
+  <<EOF>> {
+    unexpected_eof (code_start, "}");
+    STRING_FINISH;
+    loc->start = code_start;
+    val->code = last_string;
+    BEGIN INITIAL;
+    return BRACED_CODE;
+  }
 }
 
 
@@ -561,12 +612,20 @@ splice     (\\[ \f\t\v]*\n)*
 {
   "%}" {
     STRING_FINISH;
-    val->string = last_string;
+    loc->start = code_start;
+    val->chars = last_string;
     BEGIN INITIAL;
     return PROLOGUE;
   }
 
-  <<EOF>>  unexpected_end_of_file (loc, "%}");
+  <<EOF>> {
+    unexpected_eof (code_start, "%}");
+    STRING_FINISH;
+    loc->start = code_start;
+    val->chars = last_string;
+    BEGIN INITIAL;
+    return PROLOGUE;
+  }
 }
 
 
@@ -579,201 +638,88 @@ splice    (\\[ \f\t\v]*\n)*
 {
   <<EOF>> {
     STRING_FINISH;
-    val->string = last_string;
+    loc->start = code_start;
+    val->chars = last_string;
     BEGIN INITIAL;
     return EPILOGUE;
   }
 }
 
 
-  /*----------------------------------------------------------------.
-  | By default, grow the string obstack with the input, escaping M4 |
-  | quoting characters.                                                    |
-  `----------------------------------------------------------------*/
-
-<SC_COMMENT,SC_LINE_COMMENT,SC_STRING,SC_CHARACTER,SC_BRACED_CODE,SC_PROLOGUE,SC_EPILOGUE>
-{
-  \$   obstack_sgrow (&string_obstack, "$][");
-  \@   obstack_sgrow (&string_obstack, "@@");
-  \[   obstack_sgrow (&string_obstack, "@{");
-  \]   obstack_sgrow (&string_obstack, "@}");
-  .|\n  STRING_GROW;
-}
+  /*-----------------------------------------------------.
+  | By default, grow the string obstack with the input.  |
+  `-----------------------------------------------------*/
 
+<SC_COMMENT,SC_LINE_COMMENT,SC_BRACED_CODE,SC_PROLOGUE,SC_EPILOGUE,SC_STRING,SC_CHARACTER,SC_ESCAPED_STRING,SC_ESCAPED_CHARACTER>.     |
+<SC_COMMENT,SC_LINE_COMMENT,SC_BRACED_CODE,SC_PROLOGUE,SC_EPILOGUE>\n  STRING_GROW;
 
 %%
 
-/*------------------------------------------------------------------.
-| TEXT is pointing to a wannabee semantic value (i.e., a `$').      |
-|                                                                   |
-| Possible inputs: $[<TYPENAME>]($|integer)                         |
-|                                                                   |
-| Output to the STRING_OBSTACK a reference to this semantic value.  |
-`------------------------------------------------------------------*/
+/* Read bytes from FP into buffer BUF of size SIZE.  Return the
+   number of bytes read.  Remove '\r' from input, treating \r\n
+   and isolated \r as \n.  */
 
-static inline void
-handle_action_dollar (char *text, location_t location)
+static size_t
+no_cr_read (FILE *fp, char *buf, size_t size)
 {
-  const char *type_name = NULL;
-  char *cp = text + 1;
-
-  /* Get the type name if explicit. */
-  if (*cp == '<')
+  size_t bytes_read = fread (buf, 1, size, fp);
+  if (bytes_read)
     {
-      type_name = ++cp;
-      while (*cp != '>')
-       ++cp;
-      *cp = '\0';
-      ++cp;
-    }
+      char *w = memchr (buf, '\r', bytes_read);
+      if (w)
+       {
+         char const *r = ++w;
+         char const *lim = buf + bytes_read;
 
-  if (*cp == '$')
-    {
-      if (!type_name)
-       type_name = symbol_list_n_type_name_get (current_rule, location, 0);
-      if (!type_name && typed)
-       complain_at (location, _("$$ of `%s' has no declared type"),
-                    current_rule->sym->tag);
-      if (!type_name)
-       type_name = "";
-      obstack_fgrow1 (&string_obstack,
-                     "]b4_lhs_value([%s])[", type_name);
-    }
-  else
-    {
-      long num;
-      errno = 0;
-      num = strtol (cp, 0, 10);
+         for (;;)
+           {
+             /* Found an '\r'.  Treat it like '\n', but ignore any
+                '\n' that immediately follows.  */
+             w[-1] = '\n';
+             if (r == lim)
+               {
+                 int ch = getc (fp);
+                 if (ch != '\n' && ungetc (ch, fp) != ch)
+                   break;
+               }
+             else if (*r == '\n')
+               r++;
 
-      if (INT_MIN <= num && num <= rule_length && ! errno)
-       {
-         int n = num;
-         if (!type_name && n > 0)
-           type_name = symbol_list_n_type_name_get (current_rule, location,
-                                                    n);
-         if (!type_name && typed)
-           complain_at (location, _("$%d of `%s' has no declared type"),
-                     n, current_rule->sym->tag);
-         if (!type_name)
-           type_name = "";
-         obstack_fgrow3 (&string_obstack,
-                         "]b4_rhs_value([%d], [%d], [%s])[",
-                         rule_length, n, type_name);
+             /* Copy until the next '\r'.  */
+             do
+               {
+                 if (r == lim)
+                   return w - buf;
+               }
+             while ((*w++ = *r++) != '\r');
+           }
+
+         return w - buf;
        }
-      else
-       complain_at (location, _("integer out of range: %s"), quote (text));
     }
-}
-
 
-/*---------------------------------------------------------------.
-| TEXT is expected to be $$ in some code associated to a symbol: |
-| destructor or printer.                                         |
-`---------------------------------------------------------------*/
-
-static inline void
-handle_symbol_code_dollar (char *text, location_t location)
-{
-  char *cp = text + 1;
-  if (*cp == '$')
-    obstack_sgrow (&string_obstack, "]b4_dollar_dollar[");
-  else
-    complain_at (location, _("invalid value: %s"), quote (text));
+  return bytes_read;
 }
 
 
-/*-----------------------------------------------------------------.
-| Dispatch onto handle_action_dollar, or handle_destructor_dollar, |
-| depending upon CODE_KIND.                                        |
-`-----------------------------------------------------------------*/
-
-static void
-handle_dollar (braced_code_t braced_code_kind,
-              char *text, location_t location)
-{
-  switch (braced_code_kind)
-    {
-    case action_braced_code:
-      handle_action_dollar (text, location);
-      break;
-
-    case destructor_braced_code:
-    case printer_braced_code:
-      handle_symbol_code_dollar (text, location);
-      break;
-    }
-}
-
 
 /*------------------------------------------------------.
-| TEXT is a location token (i.e., a `@...').  Output to |
-| STRING_OBSTACK a reference to this location.          |
+| Scan NUMBER for a base-BASE integer at location LOC.  |
 `------------------------------------------------------*/
 
-static inline void
-handle_action_at (char *text, location_t location)
+static unsigned long int
+scan_integer (char const *number, int base, location loc)
 {
-  char *cp = text + 1;
-  locations_flag = 1;
+  verify (INT_MAX < ULONG_MAX);
+  unsigned long int num = strtoul (number, NULL, base);
 
-  if (*cp == '$')
+  if (INT_MAX < num)
     {
-      obstack_sgrow (&string_obstack, "]b4_lhs_location[");
+      complain_at (loc, _("integer out of range: %s"), quote (number));
+      num = INT_MAX;
     }
-  else
-    {
-      long num;
-      errno = 0;
-      num = strtol (cp, 0, 10);
 
-      if (INT_MIN <= num && num <= rule_length && ! errno)
-       {
-         int n = num;
-         obstack_fgrow2 (&string_obstack, "]b4_rhs_location([%d], [%d])[",
-                         rule_length, n);
-       }
-      else
-       complain_at (location, _("integer out of range: %s"), quote (text));
-    }
-}
-
-
-/*---------------------------------------------------------------.
-| TEXT is expected to be @$ in some code associated to a symbol: |
-| destructor or printer.                                         |
-`---------------------------------------------------------------*/
-
-static inline void
-handle_symbol_code_at (char *text, location_t location)
-{
-  char *cp = text + 1;
-  if (*cp == '$')
-    obstack_sgrow (&string_obstack, "]b4_at_dollar[");
-  else
-    complain_at (location, _("invalid value: %s"), quote (text));
-}
-
-
-/*-------------------------------------------------------------------.
-| Dispatch onto handle_action_at, or handle_destructor_at, depending |
-| upon CODE_KIND.                                                    |
-`-------------------------------------------------------------------*/
-
-static void
-handle_at (braced_code_t braced_code_kind,
-          char *text, location_t location)
-{
-  switch (braced_code_kind)
-    {
-    case action_braced_code:
-      handle_action_at (text, location);
-      break;
-
-    case destructor_braced_code:
-    case printer_braced_code:
-      handle_symbol_code_at (text, location);
-      break;
-    }
+  return num;
 }
 
 
@@ -786,7 +732,8 @@ handle_at (braced_code_t braced_code_kind,
 static int
 convert_ucn_to_byte (char const *ucn)
 {
-  unsigned long code = strtoul (ucn + 2, 0, 16);
+  verify (UCHAR_MAX <= INT_MAX);
+  unsigned long int code = strtoul (ucn + 2, NULL, 16);
 
   /* FIXME: Currently we assume Unicode-compatible unibyte characters
      on ASCII hosts (i.e., Latin-1 on hosts with 8-bit bytes).  On
@@ -838,37 +785,58 @@ convert_ucn_to_byte (char const *ucn)
 `----------------------------------------------------------------*/
 
 static void
-handle_syncline (char *args, location_t *location)
+handle_syncline (char *args, location loc)
+{
+  char *after_num;
+  unsigned long int lineno = strtoul (args, &after_num, 10);
+  char *file = strchr (after_num, '"') + 1;
+  *strchr (file, '"') = '\0';
+  if (INT_MAX <= lineno)
+    {
+      warn_at (loc, _("line number overflow"));
+      lineno = INT_MAX;
+    }
+  current_file = uniqstr_new (file);
+  boundary_set (&scanner_cursor, current_file, lineno, 1);
+}
+
+
+/*----------------------------------------------------------------.
+| For a token or comment starting at START, report message MSGID, |
+| which should say that an end marker was found before           |
+| the expected TOKEN_END.                                        |
+`----------------------------------------------------------------*/
+
+static void
+unexpected_end (boundary start, char const *msgid, char const *token_end)
 {
-  int lineno = strtol (args, &args, 10);
-  const char *file = NULL;
-  file = strchr (args, '"') + 1;
-  *strchr (file, '"') = 0;
-  current_file = xstrdup (file);
-  location->file = current_file;
-  location->last_line = lineno;
+  location loc;
+  loc.start = start;
+  loc.end = scanner_cursor;
+  complain_at (loc, _(msgid), token_end);
 }
 
 
-/*-------------------------------------------------------------.
-| Report an unexpected end of file at LOC.  An end of file was |
-| encountered and the expected TOKEN_END was missing.  After   |
-| reporting the problem, pretend that TOKEN_END was found.     |
-`-------------------------------------------------------------*/
+/*------------------------------------------------------------------------.
+| Report an unexpected EOF in a token or comment starting at START.       |
+| An end of file was encountered and the expected TOKEN_END was missing.  |
+`------------------------------------------------------------------------*/
 
 static void
-unexpected_end_of_file (location_t *loc, char const *token_end)
+unexpected_eof (boundary start, char const *token_end)
 {
-  size_t i = strlen (token_end);
+  unexpected_end (start, N_("missing `%s' at end of file"), token_end);
+}
 
-  complain_at (*loc, _("missing `%s' at end of file"), token_end);
 
-  /* Adjust location's last column so that any later message does not
-     mention the characters just inserted.  */
-  loc->last_column -= i;
+/*----------------------------------------.
+| Likewise, but for unexpected newlines.  |
+`----------------------------------------*/
 
-  while (i != 0)
-    unput (token_end[--i]);
+static void
+unexpected_newline (boundary start, char const *token_end)
+{
+  unexpected_end (start, N_("missing `%s' at end of line"), token_end);
 }
 
 
@@ -877,9 +845,9 @@ unexpected_end_of_file (location_t *loc, char const *token_end)
 `-------------------------*/
 
 void
-scanner_initialize (void)
+gram_scanner_initialize (void)
 {
-  obstack_init (&string_obstack);
+  obstack_init (&obstack_for_string);
 }
 
 
@@ -888,9 +856,9 @@ scanner_initialize (void)
 `-----------------------------------------------*/
 
 void
-scanner_free (void)
+gram_scanner_free (void)
 {
-  obstack_free (&string_obstack, 0);
+  obstack_free (&obstack_for_string, 0);
   /* Reclaim Flex's buffers.  */
-  yy_delete_buffer (YY_CURRENT_BUFFER);
+  yylex_destroy ();
 }