]> git.saurik.com Git - bison.git/blobdiff - src/scan-gram.l
Merge remote-tracking branch 'origin/maint'
[bison.git] / src / scan-gram.l
index 3c38906f3279897f37755fb23c8695aeca849962..cf8b220d3a329479a700a8f912cf96b684882685 100644 (file)
@@ -1,6 +1,6 @@
 /* Bison Grammar Scanner                             -*- C -*-
 
-   Copyright (C) 2002-2011 Free Software Foundation, Inc.
+   Copyright (C) 2002-2013 Free Software Foundation, Inc.
 
    This file is part of Bison, the GNU Compiler Compiler.
 
@@ -17,7 +17,7 @@
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
-%option debug nodefault noinput nounput noyywrap never-interactive
+%option debug nodefault noinput noyywrap never-interactive
 %option prefix="gram_" outfile="lex.yy.c"
 
 %{
 
 #include <src/complain.h>
 #include <src/files.h>
+#include <src/getargs.h>
 #include <src/gram.h>
 #include <quotearg.h>
 #include <src/reader.h>
 #include <src/uniqstr.h>
 
-#include <ctype.h>
+#include <c-ctype.h>
 #include <mbswidth.h>
 #include <quote.h>
 
@@ -73,6 +74,15 @@ static size_t no_cr_read (FILE *, char *, size_t);
     yyless (0);                                                 \
   } while (0)
 
+#define DEPRECATED(Msg)                                         \
+  do {                                                          \
+    size_t i;                                                   \
+    deprecated_directive (loc, yytext, Msg);                    \
+    scanner_cursor.column -= mbsnwidth (Msg, strlen (Msg), 0);  \
+    for (i = strlen (Msg); i != 0; --i)                         \
+      unput (Msg[i - 1]);                                       \
+  } while (0)
+
 /* A string representing the most recently saved token.  */
 static char *last_string;
 
@@ -100,16 +110,21 @@ static void unexpected_newline (boundary, char const *);
  /* Strings and characters in directives/rules. */
 %x SC_ESCAPED_STRING SC_ESCAPED_CHARACTER
  /* A identifier was just read in directives/rules.  Special state
-    to capture the sequence `identifier :'. */
+    to capture the sequence 'identifier :'. */
 %x SC_AFTER_IDENTIFIER
- /* A complex tag, with nested angles brackets. */
+
+ /* 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, as this simplifies our implementation.  We match
+    angle brackets in nested pairs: several languages use them for
+    generics/template types.  */
 %x SC_TAG
 
  /* Four types of user code:
-    - prologue (code between `%{' `%}' in the first section, before %%);
+    - prologue (code between '%{' '%}' in the first section, before %%);
     - actions, printers, union, etc, (between braced in the middle section);
-    - epilogue (everything after the second %%). 
-    - predicate (code between `%?{' and `{' in middle section); */
+    - epilogue (everything after the second %%).
+    - predicate (code between '%?{' and '{' in middle section); */
 %x SC_PROLOGUE SC_BRACED_CODE SC_EPILOGUE SC_PREDICATE
  /* C and C++ comments in code. */
 %x SC_COMMENT SC_LINE_COMMENT
@@ -119,21 +134,18 @@ static void unexpected_newline (boundary, char const *);
 %x SC_BRACKETED_ID SC_RETURN_BRACKETED_ID
 
 letter    [.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_]
+notletter [^.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_]{-}[%\{]
 id        {letter}({letter}|[-0-9])*
-directive %{id}
 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, 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.  */
 splice   (\\[ \f\t\v]*\n)*
 
+/* An equal sign, with optional leading whitespaces. This is used in some
+   deprecated constructs. */
+eqopt    ([[:space:]]*=)?
+
 %%
 %{
   /* Nesting level.  Either for nested braces, or nested angle brackets
@@ -164,9 +176,11 @@ splice   (\\[ \f\t\v]*\n)*
 <INITIAL,SC_AFTER_IDENTIFIER,SC_BRACKETED_ID,SC_RETURN_BRACKETED_ID>
 {
   /* Comments and white space.  */
-  ","          warn_at (*loc, _("stray `,' treated as white space"));
+  "," {
+     complain (loc, Wother, _("stray ',' treated as white space"));
+  }
   [ \f\n\t\v]  |
-  "//".*       ;
+  "//".*       continue;
   "/*" {
     token_start = loc->start;
     context_state = YY_START;
@@ -175,7 +189,7 @@ splice   (\\[ \f\t\v]*\n)*
 
   /* #line directives are not documented, and may be withdrawn or
      modified in future versions of Bison.  */
-  ^"#line "{int}" \"".*"\"\n" {
+  ^"#line "{int}(" \"".*"\"")?"\n" {
     handle_syncline (yytext + sizeof "#line " - 1, *loc);
   }
 }
@@ -196,16 +210,17 @@ splice   (\\[ \f\t\v]*\n)*
   "%binary"                         return PERCENT_NONASSOC;
   "%code"                           return PERCENT_CODE;
   "%debug"                          RETURN_PERCENT_FLAG("parse.trace");
-  "%default"[-_]"prec"              return PERCENT_DEFAULT_PREC;
+  "%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_ERROR_VERBOSE;
+  "%empty"                          return PERCENT_EMPTY;
+  "%error-verbose"                  return PERCENT_ERROR_VERBOSE;
   "%expect"                         return PERCENT_EXPECT;
-  "%expect"[-_]"rr"                 return PERCENT_EXPECT_RR;
+  "%expect-rr"                      return PERCENT_EXPECT_RR;
   "%file-prefix"                    return PERCENT_FILE_PREFIX;
-  "%fixed"[-_]"output"[-_]"files"   return PERCENT_YACC;
+  "%fixed-output-files"             return PERCENT_YACC;
   "%initial-action"                 return PERCENT_INITIAL_ACTION;
   "%glr-parser"                     return PERCENT_GLR_PARSER;
   "%language"                       return PERCENT_LANGUAGE;
@@ -213,9 +228,9 @@ splice   (\\[ \f\t\v]*\n)*
   "%lex-param"                      RETURN_PERCENT_PARAM(lex);
   "%locations"                      RETURN_PERCENT_FLAG("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;
+  "%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;
@@ -225,21 +240,34 @@ splice   (\\[ \f\t\v]*\n)*
   "%prec"                           return PERCENT_PREC;
   "%precedence"                     return PERCENT_PRECEDENCE;
   "%printer"                        return PERCENT_PRINTER;
-  "%pure"[-_]"parser"               RETURN_PERCENT_FLAG("api.pure");
+  "%pure-parser"                    RETURN_PERCENT_FLAG("api.pure");
   "%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;
+  "%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));
+  /* deprecated */
+  "%default"[-_]"prec"              DEPRECATED("%default-prec");
+  "%error"[-_]"verbose"             DEPRECATED("%define parse.error verbose");
+  "%expect"[-_]"rr"                 DEPRECATED("%expect-rr");
+  "%file-prefix"{eqopt}             DEPRECATED("%file-prefix");
+  "%fixed"[-_]"output"[-_]"files"   DEPRECATED("%fixed-output-files");
+  "%name"[-_]"prefix"{eqopt}        DEPRECATED("%name-prefix");
+  "%no"[-_]"default"[-_]"prec"      DEPRECATED("%no-default-prec");
+  "%no"[-_]"lines"                  DEPRECATED("%no-lines");
+  "%output"{eqopt}                  DEPRECATED("%output");
+  "%pure"[-_]"parser"               DEPRECATED("%pure-parser");
+  "%token"[-_]"table"               DEPRECATED("%token-table");
+
+  "%"{id}|"%"{notletter}([[:graph:]])+ {
+    complain (loc, complaint, _("invalid directive: %s"), quote (yytext));
   }
 
   "="                     return EQUAL;
@@ -265,7 +293,7 @@ splice   (\\[ \f\t\v]*\n)*
   /* Identifiers may not start with a digit.  Yet, don't silently
      accept "1FOO" as "1 FOO".  */
   {int}{id} {
-    complain_at (*loc, _("invalid identifier: %s"), quote (yytext));
+    complain (loc, complaint, _("invalid identifier: %s"), quote (yytext));
   }
 
   /* Characters.  */
@@ -295,13 +323,6 @@ splice   (\\[ \f\t\v]*\n)*
   /* A type. */
   "<*>"       return TAG_ANY;
   "<>"        return TAG_NONE;
-  "<"{tag}">" {
-    obstack_grow (&obstack_for_string, yytext + 1, yyleng - 2);
-    STRING_FINISH;
-    val->uniqstr = uniqstr_new (last_string);
-    STRING_FREE;
-    return TAG;
-  }
   "<"         {
     nesting = 0;
     token_start = loc->start;
@@ -322,8 +343,10 @@ splice   (\\[ \f\t\v]*\n)*
     BEGIN SC_BRACKETED_ID;
   }
 
-  . {
-    complain_at (*loc, _("invalid character: %s"), quote (yytext));
+  [^\[%A-Za-z0-9_<>{}\"\'*;|=/, \f\n\t\v]+|. {
+    complain (loc, complaint, "%s: %s",
+              ngettext ("invalid character", "invalid characters", yyleng),
+              quote_mem (yytext, yyleng));
   }
 
   <<EOF>> {
@@ -340,7 +363,7 @@ splice   (\\[ \f\t\v]*\n)*
 
 <SC_ESCAPED_CHARACTER,SC_ESCAPED_STRING,SC_TAG>
 {
-  \0        complain_at (*loc, _("invalid null character"));
+  \0        complain (loc, complaint, _("invalid null character"));
 }
 
 
@@ -392,8 +415,9 @@ splice   (\\[ \f\t\v]*\n)*
   {id} {
     if (bracketed_id_str)
       {
-        complain_at (*loc, _("unexpected identifier in bracketed name: %s"),
-                     quote (yytext));
+        complain (loc, complaint,
+                  _("unexpected identifier in bracketed name: %s"),
+                  quote (yytext));
       }
     else
       {
@@ -414,12 +438,16 @@ splice   (\\[ \f\t\v]*\n)*
           }
       }
     else
-      complain_at (*loc, _("an identifier expected"));
+      complain (loc, complaint, _("an identifier expected"));
   }
-  . {
-    complain_at (*loc, _("invalid character in bracketed name: %s"),
-                 quote (yytext));
+
+  [^\].A-Za-z0-9_/ \f\n\t\v]+|. {
+    complain (loc, complaint, "%s: %s",
+              ngettext ("invalid character in bracketed name",
+                        "invalid characters in bracketed name", yyleng),
+              quote_mem (yytext, yyleng));
   }
+
   <<EOF>> {
     BEGIN bracketed_id_context_state;
     unexpected_eof (bracketed_id_start, "]");
@@ -440,19 +468,19 @@ splice   (\\[ \f\t\v]*\n)*
 
 
   /*---------------------------------------------------------------.
-  | Scanning a Yacc comment.  The initial `/ *' is already eaten.  |
+  | Scanning a Yacc comment.  The initial '/ *' is already eaten.  |
   `---------------------------------------------------------------*/
 
 <SC_YACC_COMMENT>
 {
   "*/"     BEGIN context_state;
-  .|\n     ;
+  .|\n     continue;
   <<EOF>>  unexpected_eof (token_start, "*/"); BEGIN context_state;
 }
 
 
   /*------------------------------------------------------------.
-  | Scanning a C comment.  The initial `/ *' is already eaten.  |
+  | Scanning a C comment.  The initial '/ *' is already eaten.  |
   `------------------------------------------------------------*/
 
 <SC_COMMENT>
@@ -463,7 +491,7 @@ splice   (\\[ \f\t\v]*\n)*
 
 
   /*--------------------------------------------------------------.
-  | Scanning a line comment.  The initial `//' is already eaten.  |
+  | Scanning a line comment.  The initial '//' is already eaten.  |
   `--------------------------------------------------------------*/
 
 <SC_LINE_COMMENT>
@@ -481,23 +509,15 @@ splice   (\\[ \f\t\v]*\n)*
 
 <SC_ESCAPED_STRING>
 {
-  "\""|"\n" {
-    if (yytext[0] == '\n')
-      unexpected_newline (token_start, "\"");
-    STRING_FINISH;
-    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;
   }
+  <<EOF>>   unexpected_eof (token_start, "\"");
+  "\n"      unexpected_newline (token_start, "\"");
 }
 
   /*----------------------------------------------------------.
@@ -507,53 +527,34 @@ splice   (\\[ \f\t\v]*\n)*
 
 <SC_ESCAPED_CHARACTER>
 {
-  "'"|"\n" {
-    STRING_FINISH;
-    loc->start = token_start;
-    val->character = last_string[0];
-    {
-      /* FIXME: Eventually, make these errors.  */
-      if (last_string[0] == '\0')
-        {
-          warn_at (*loc, _("empty character literal"));
-          /* '\0' seems dangerous even if we are about to complain.  */
-          val->character = '\'';
-        }
-      else if (last_string[1] != '\0')
-        warn_at (*loc, _("extra characters in character literal"));
-    }
-    if (yytext[0] == '\n')
-      unexpected_newline (token_start, "'");
-    STRING_FREE;
-    BEGIN INITIAL;
-    return CHAR;
-  }
-  <<EOF>> {
+  "'" {
     STRING_FINISH;
     loc->start = token_start;
     val->character = last_string[0];
+
+    /* FIXME: Eventually, make these errors.  */
+    if (last_string[0] == '\0')
     {
-      /* FIXME: Eventually, make these errors.  */
-      if (last_string[0] == '\0')
-        {
-          warn_at (*loc, _("empty character literal"));
-          /* '\0' seems dangerous even if we are about to complain.  */
-          val->character = '\'';
-        }
-      else if (last_string[1] != '\0')
-        warn_at (*loc, _("extra characters in character literal"));
+      complain (loc, Wother, _("empty character literal"));
+      /* '\0' seems dangerous even if we are about to complain.  */
+      val->character = '\'';
     }
-    unexpected_eof (token_start, "'");
+    else if (last_string[1] != '\0')
+      complain (loc, Wother,
+                _("extra characters in character literal"));
     STRING_FREE;
     BEGIN INITIAL;
     return CHAR;
   }
+  "\n"      unexpected_newline (token_start, "'");
+  <<EOF>>   unexpected_eof (token_start, "'");
 }
 
-  /*-----------------------------------------------------------.
-  | Scanning a Bison nested tag.  The initial angle bracket is |
-  | already eaten.                                             |
-  `-----------------------------------------------------------*/
+
+
+  /*--------------------------------------------------------------.
+  | Scanning a tag.  The initial angle bracket is already eaten.  |
+  `--------------------------------------------------------------*/
 
 <SC_TAG>
 {
@@ -571,18 +572,10 @@ splice   (\\[ \f\t\v]*\n)*
     STRING_GROW;
   }
 
-  [^<>]+ 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;
-  }
+  <<EOF>>   unexpected_eof (token_start, ">");
 }
 
   /*----------------------------.
@@ -594,7 +587,7 @@ splice   (\\[ \f\t\v]*\n)*
   \\[0-7]{1,3} {
     unsigned long int c = strtoul (yytext + 1, NULL, 8);
     if (!c || UCHAR_MAX < c)
-      complain_at (*loc, _("invalid number after \\-escape: %s"),
+      complain (loc, complaint, _("invalid number after \\-escape: %s"),
                    yytext+1);
     else
       obstack_1grow (&obstack_for_string, c);
@@ -604,7 +597,7 @@ splice   (\\[ \f\t\v]*\n)*
     verify (UCHAR_MAX < ULONG_MAX);
     unsigned long int c = strtoul (yytext + 2, NULL, 16);
     if (!c || UCHAR_MAX < c)
-      complain_at (*loc, _("invalid number after \\-escape: %s"),
+      complain (loc, complaint, _("invalid number after \\-escape: %s"),
                    yytext+1);
     else
       obstack_1grow (&obstack_for_string, c);
@@ -624,7 +617,7 @@ splice   (\\[ \f\t\v]*\n)*
   \\(u|U[0-9abcdefABCDEF]{4})[0-9abcdefABCDEF]{4} {
     int c = convert_ucn_to_byte (yytext);
     if (c <= 0)
-      complain_at (*loc, _("invalid number after \\-escape: %s"),
+      complain (loc, complaint, _("invalid number after \\-escape: %s"),
                    yytext+1);
     else
       obstack_1grow (&obstack_for_string, c);
@@ -632,11 +625,12 @@ splice   (\\[ \f\t\v]*\n)*
   \\(.|\n)      {
     char const *p = yytext + 1;
     /* Quote only if escaping won't make the character visible.  */
-    if (isspace ((unsigned char) *p) && isprint ((unsigned char) *p))
+    if (c_isspace ((unsigned char) *p) && c_isprint ((unsigned char) *p))
       p = quote (p);
     else
       p = quotearg_style_mem (escape_quoting_style, p, 1);
-    complain_at (*loc, _("invalid character after \\-escape: %s"), p);
+    complain (loc, complaint, _("invalid character after \\-escape: %s"),
+                 p);
   }
 }
 
@@ -652,15 +646,15 @@ splice   (\\[ \f\t\v]*\n)*
 <SC_CHARACTER>
 {
   "'"           STRING_GROW; BEGIN context_state;
-  \n            unexpected_newline (token_start, "'"); BEGIN context_state;
-  <<EOF>>       unexpected_eof (token_start, "'"); BEGIN context_state;
+  \n            unexpected_newline (token_start, "'");
+  <<EOF>>       unexpected_eof (token_start, "'");
 }
 
 <SC_STRING>
 {
   "\""          STRING_GROW; BEGIN context_state;
-  \n            unexpected_newline (token_start, "\""); BEGIN context_state;
-  <<EOF>>       unexpected_eof (token_start, "\""); BEGIN context_state;
+  \n            unexpected_newline (token_start, "\"");
+  <<EOF>>       unexpected_eof (token_start, "\"");
 }
 
 
@@ -707,19 +701,11 @@ splice   (\\[ \f\t\v]*\n)*
   "{"|"<"{splice}"%"  STRING_GROW; nesting++;
   "%"{splice}">"      STRING_GROW; nesting--;
 
-  /* Tokenize `<<%' correctly (as `<<' `%') rather than incorrrectly
-     (as `<' `<%').  */
+  /* Tokenize '<<%' correctly (as '<<' '%') rather than incorrrectly
+     (as '<' '<%').  */
   "<"{splice}"<"  STRING_GROW;
 
-  <<EOF>> {
-    int token = (YY_START == SC_BRACED_CODE) ? BRACED_CODE : BRACED_PREDICATE;
-    unexpected_eof (code_start, "}");
-    STRING_FINISH;
-    loc->start = code_start;
-    val->code = last_string;
-    BEGIN INITIAL;
-    return token;
-  }
+  <<EOF>>   unexpected_eof (code_start, "}");
 }
 
 <SC_BRACED_CODE>
@@ -770,14 +756,7 @@ splice   (\\[ \f\t\v]*\n)*
     return PROLOGUE;
   }
 
-  <<EOF>> {
-    unexpected_eof (code_start, "%}");
-    STRING_FINISH;
-    loc->start = code_start;
-    val->chars = last_string;
-    BEGIN INITIAL;
-    return PROLOGUE;
-  }
+  <<EOF>>   unexpected_eof (code_start, "%}");
 }
 
 
@@ -867,7 +846,8 @@ scan_integer (char const *number, int base, location loc)
 
   if (INT_MAX < num)
     {
-      complain_at (loc, _("integer out of range: %s"), quote (number));
+      complain (&loc, complaint, _("integer out of range: %s"),
+                   quote (number));
       num = INT_MAX;
     }
 
@@ -932,31 +912,35 @@ convert_ucn_to_byte (char const *ucn)
 }
 
 
-/*----------------------------------------------------------------.
-| Handle `#line INT "FILE"'.  ARGS has already skipped `#line '.  |
-`----------------------------------------------------------------*/
+/*---------------------------------------------------------------------.
+| Handle '#line INT( "FILE")?\n'.  ARGS has already skipped '#line '.  |
+`---------------------------------------------------------------------*/
 
 static void
 handle_syncline (char *args, location loc)
 {
-  char *after_num;
-  unsigned long int lineno = strtoul (args, &after_num, 10);
-  char *file = mbschr (after_num, '"') + 1;
-  *mbschr (file, '"') = '\0';
+  char *file;
+  unsigned long int lineno = strtoul (args, &file, 10);
   if (INT_MAX <= lineno)
     {
-      warn_at (loc, _("line number overflow"));
+      complain (&loc, Wother, _("line number overflow"));
       lineno = INT_MAX;
     }
-  current_file = uniqstr_new (file);
+
+  file = strchr (file, '"');
+  if (file)
+    {
+      *strchr (file + 1, '"') = '\0';
+      current_file = uniqstr_new (file + 1);
+    }
   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.                                         |
+| which should say that an end marker was found before the        |
+| expected TOKEN_END. Then, pretend that TOKEN_END was found.     |
 `----------------------------------------------------------------*/
 
 static void
@@ -965,19 +949,33 @@ unexpected_end (boundary start, char const *msgid, char const *token_end)
   location loc;
   loc.start = start;
   loc.end = scanner_cursor;
-  complain_at (loc, _(msgid), token_end);
+  size_t i = strlen (token_end);
+
+/* Adjust scanner cursor so that any later message does not count
+     the characters about to be inserted.  */
+  scanner_cursor.column -= i;
+
+  while (i != 0)
+    unput (token_end[--i]);
+
+  token_end = quote (token_end);
+  /* Instead of '\'', display "'".  */
+  if (STREQ (token_end, "'\\''"))
+    token_end = "\"'\"";
+  complain (&loc, complaint, _(msgid), token_end);
 }
 
 
 /*------------------------------------------------------------------------.
 | 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.  |
+| After reporting the problem, pretend that TOKEN_END was found.          |
 `------------------------------------------------------------------------*/
 
 static void
 unexpected_eof (boundary start, char const *token_end)
 {
-  unexpected_end (start, N_("missing `%s' at end of file"), token_end);
+  unexpected_end (start, N_("missing %s at end of file"), token_end);
 }
 
 
@@ -988,7 +986,7 @@ unexpected_eof (boundary start, char const *token_end)
 static void
 unexpected_newline (boundary start, char const *token_end)
 {
-  unexpected_end (start, N_("missing `%s' at end of line"), token_end);
+  unexpected_end (start, N_("missing %s at end of line"), token_end);
 }