m4-patches
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

head - Re: branch-1_4 read stdin twice


From: Eric Blake
Subject: head - Re: branch-1_4 read stdin twice
Date: Thu, 7 Sep 2006 23:52:17 +0000 (UTC)
User-agent: Loom/3.14 (http://gmane.org/)

Eric Blake <ebb9 <at> byu.net> writes:

> I also noticed that my patch for debian bug 385720 introduced a regression - 
> when stdin was a terminal, m4 was calling getc() a second time after 
detecting 
> EOF, making the user have to type two ^D sequences instead of one.  But I 
don't 
> know how to add a test for this in the 1.4.x testsuite; maybe I'll come up 
with 
> something when porting this patch to head.

Testing the double ^D would require a pty, which is more work than I'm willing 
spend figuring it out.  This also merges several other patches from the branch, 
such as multiple m4wraps (although I would still like to get it in FIFO order), 
reporting read errors, allowing multi-character quotes starting with '(', 
avoiding free'd memory when reporting EOF mid-string, reporting an error on EOF 
mid-comment, and properly matching 8-bit input.  I think input.c is now fully 
ported from the various branch improvements.

2006-09-07  Eric Blake  <address@hidden>

        * m4/m4module.h (m4_peek_input): No longer export.
        (m4_error_at_line, m4_warn_at_line): New prototypes.
        (m4_is_symbol_void): New macro.
        (m4_push_file): Update prototype.
        * m4/m4private.h (m4__peek_token): New prototype.
        (M4_TOKEN_OPEN, M4_TOKEN_COMMA, M4_TOKEN_CLOSE): New enumerators.
        * m4/utility.c (m4_error_at_line, m4_warn_at_line): New functions.
        * src/main.c (main): Allow reading from stdin twice.
        * modules/m4.c (include): Adjust to new prototype.
        * m4/input.c: General comment cleanup.
        (file_peek, file_read, file_unget, push_file): Set end flag on
        EOF, so that we don't call getc twice.
        (push_file, file_clean): Port fix from branch to avoid closing
        stdin prematurely.
        (pop_input): Port fix from branch to avoid reading free'd memory
        when input ends mid-string.
        (m4_pop_wrapup): Port fix from branch to allow multiple m4wraps.
        (string_peek, string_read): Always use unsigned char.
        (m4_skip_line): Warn when dnl cut short by EOF.
        (peek_input): Rename from m4_peek_input.
        (match_input): Update signature, to distinguish between `(' token
        and multi-char quote or comment beginning with `('.
        (m4_input_exit): Cleanup now done in m4_pop_wrapup.
        (m4__peek_token): New function, ported from branch.
        (m4__next_token): Update to new token types.
        * m4/macro.c (expand_token, expand_argument): Use peek_token.
        * doc/m4.texinfo (Pseudo Arguments, Defn, Answers): Fix typos.
        (Invoking m4): Remerge from branch.

Index: doc/m4.texinfo
===================================================================
RCS file: /sources/m4/m4/doc/m4.texinfo,v
retrieving revision 1.42
diff -u -r1.42 m4.texinfo
--- doc/m4.texinfo      5 Sep 2006 23:16:39 -0000       1.42
+++ doc/m4.texinfo      7 Sep 2006 23:45:38 -0000
@@ -668,10 +668,12 @@
 name of @file{-} is taken to mean the standard input.  It is
 conventional, but not required, for input files to end in @samp{.m4}.
 
-The input files are read in the sequence given.  The standard input can
-only be read once, so the file name @file{-} should only appear once on
-the command line.  It is an error if an input file ends in the middle of
-argument collection, a comment, or a quoted string.
+The input files are read in the sequence given.  Standard input can be
+read more than once, so the file name @file{-} may appear multiple times
+on the command line; this makes a difference when input is from a
+terminal or other special file type.  It is an error if an input file
+ends in the middle of argument collection, a comment, or a quoted
+string.
 @comment FIXME - it would be nicer if we let these three things
 @comment continue across file boundaries, provided that we warn in
 @comment interactive use when switching to stdin in a non-default parse
@@ -1583,7 +1585,7 @@
 @example
 define(`echo1', `$*')
 @result{}
-define(`echo2', `$@')
+define(`echo2', `$@@')
 @result{}
 define(`foo', `bar')
 @result{}
@@ -1770,7 +1772,7 @@
 @result{}
 define(`a', `A')
 @result{}
-define(`echo', `$@')
+define(`echo', `$@@')
 @result{}
 foo
 @result{}A'A
@@ -5077,7 +5079,7 @@
                `define(`$1',
                        `_arg1$2')$3`'_foreach(`$1', `(shift$2)',
                                               `$3')')')dnl
-define(`dquote', ``$@'')
+define(`dquote', ``$@@'')
 @result{}
 foreach(`macro', (dquote(symbols)), `regexp(macro, `shift', `\&')')
 @result{}shift
Index: m4/input.c
===================================================================
RCS file: /sources/m4/m4/m4/input.c,v
retrieving revision 1.46
diff -u -r1.46 input.c
--- m4/input.c  5 Sep 2006 23:16:39 -0000       1.46
+++ m4/input.c  7 Sep 2006 23:45:38 -0000
@@ -28,42 +28,44 @@
 /*#define DEBUG_INPUT */
 
 /*
-   Unread input can be either files, that should be read (eg. included
-   files), strings, which should be rescanned (eg. macro expansion
-   text), single characters or quoted builtin definitions (as returned by
-   the builtin "defn").  Unread input are organised in a stack,
+   Unread input can be either files that should be read (eg. included
+   files), strings which should be rescanned (eg. macro expansion
+   text), single characters, or quoted builtin definitions (as returned by
+   the builtin "defn").  Unread input is organized in a stack,
    implemented with an obstack.  Each input source is described by a
    "struct input_block".  The obstack is "input_stack".  The top of the
    input stack is "isp".
 
    Each input_block has an associated struct input_funcs, that defines
-   functions for peeking, reading, unget and cleanup.  All input is done
-   through the functions pointers of the input_funcs of the top most
-   input_block.  When a input_block is exausted, its reader returns
-   CHAR_RETRY which causes the input_block to be popped from the
-   input_stack.
-
-   The macro "m4wrap" places the text to be saved on another input stack,
-   on the obstack "wrapup_stack", whose top is "wsp".  When EOF is seen
-   on normal input (eg, when "input_stack" is empty), input is switched
-   over to "wrapup_stack".  To make this easier, all references to the
-   current input stack, whether it be "input_stack" or "wrapup_stack",
-   are done through a pointer "current_input", which points to either
-   "input_stack" or "wrapup_stack".
+   functions for peeking, reading, unget and cleanup.  All input is
+   done through the function pointers of the input_funcs of the top
+   most input_block, and all characters are unsigned.  When a
+   input_block is exausted, its reader returns CHAR_RETRY which causes
+   the input_block to be popped from the input_stack.
+
+   The macro "m4wrap" places the text to be saved on another input
+   stack, on the obstack "wrapup_stack", whose top is "wsp".  When EOF
+   is seen on normal input (eg, when "current_input" is empty), input is
+   switched over to "wrapup_stack", and the original "current_input" is
+   freed.  A new stack is allocated for "wrapup_stack", which will
+   accept any text produced by calls to "m4wrap" from within the
+   wrapped text.  This process of shuffling "wrapup_stack" to
+   "current_input" can continue indefinitely, even generating infinite
+   loops (e.g. "define(`f',`m4wrap(`f')')f"), without memory leaks.
 
    Pushing new input on the input stack is done by m4_push_file (),
    m4_push_string (), m4_push_single () or m4_push_wrapup () (for wrapup
    text), and m4_push_builtin () (for builtin definitions).  Because
    macro expansion needs direct access to the current input obstack (for
-   optimisation), m4_push_string () are split in two functions,
+   optimization), m4_push_string () is split in two functions,
    push_string_init (), which returns a pointer to the current input
-   stack, and push_string_finish (), which return a pointer to the final
+   stack, and push_string_finish (), which returns a pointer to the final
    text.  The input_block *next is used to manage the coordination
    between the different push routines.
 
-   The current file and line number are stored in two global variables,
+   The current file and line number are stored in the context,
    for use by the error handling functions in m4.c.  Whenever a file
-   input_block is pushed, the current file name and line number is saved
+   input_block is pushed, the current file name and line number are saved
    in the input_block, and the two variables are reset to match the new
    input file.  */
 
@@ -74,8 +76,10 @@
 static void  init_builtin_token        (m4 *context, m4_symbol_value *token);
 static int   builtin_peek              (void);
 static int   builtin_read              (m4 *);
-static int   match_input               (m4 *context, const unsigned char *s);
+static bool  match_input               (m4 *context, const unsigned char *s,
+                                        bool);
 static int   next_char                 (m4 *context);
+static int   peek_char                 (m4 *context);
 static void  pop_input                 (m4 *context);
 static int   single_peek               (void);
 static int   single_read               (m4 *);
@@ -113,20 +117,22 @@
       struct
        {
          FILE *file;           /* input file handle */
+         bool end;             /* true iff peek returned EOF */
+         bool close;           /* true if file should be closed on EOF */
          const char *name;     /* name of PREVIOUS input file */
-         int lineno;           /* current line number for do */
-         /* Yet another attack of "The curse of global variables" (sigh) */
-         int out_lineno;       /* current output line number do */
+         int lineno;           /* current line of previous file */
+         int out_lineno;       /* current output line of previous file */
          bool advance_line;    /* start_of_input_line from next_char () */
        }
       u_f;
       struct
        {
-         m4_builtin_func *func;  /* pointer to builtins function. */
+         m4_builtin_func *func;  /* pointer to builtin's function. */
          lt_dlhandle handle;     /* originating module. */
          int flags;              /* flags associated with the builtin. */
          m4_hash *arg_signature; /* argument signature for builtin.  */
-         int min_args, max_args; /* argv maxima and minima for the builtin. */
+         unsigned int min_args;  /* argv minima for the builtin. */
+         unsigned int max_args;  /* argv maxima for the builtin. */
          bool traced;    /* true iff builtin is traced. */
          bool read;              /* true iff block has been read. */
        }
@@ -141,13 +147,14 @@
 /* Obstack for storing individual tokens.  */
 static m4_obstack token_stack;
 
-/* Normal input stack.  */
-static m4_obstack input_stack;
+/* Wrapup input stack.
 
-/* Wrapup input stack.  */
-static m4_obstack wrapup_stack;
+   FIXME - m4wrap should be FIFO, which implies a queue, not a stack.
+   While fixing this, m4wrap should also remember what the current
+   file and line are for each chunk of wrapped text.  */
+static m4_obstack *wrapup_stack;
 
-/* Input or wrapup.  */
+/* Current stack, from input or wrapup.  */
 static m4_obstack *current_input;
 
 /* Bottom of token_stack, for obstack_free.  */
@@ -168,15 +175,6 @@
 
 
 
-/* m4_push_file () pushes an input file on the input stack, saving the
-  current file name and line number.  If next is non-NULL, this push
-  invalidates a call to m4_push_string_init (), whose storage are
-  consequentely released.
-
-  file_read () manages line numbers for error messages, so they do not
-  get wrong, due to lookahead.  The token consisting of a newline
-  alone is taken as belonging to the line it ends, and the current
-  line number is not incremented until the next character is read.  */
 static int
 file_peek (void)
 {
@@ -184,7 +182,10 @@
 
   ch = getc (isp->u.u_f.file);
   if (ch == EOF)
-    return CHAR_RETRY;
+    {
+      isp->u.u_f.end = true;
+      return CHAR_RETRY;
+    }
 
   ungetc (ch, isp->u.u_f.file);
   return ch;
@@ -201,7 +202,9 @@
       m4_set_current_line (context, m4_get_current_line (context) + 1);
     }
 
-  ch = getc (isp->u.u_f.file);
+  /* If stdin is a terminal, calling getc after peek_input already
+     called it would make the user have to hit ^D twice to quit.  */
+  ch = isp->u.u_f.end ? EOF : getc (isp->u.u_f.file);
   if (ch == EOF)
     return CHAR_RETRY;
 
@@ -211,10 +214,10 @@
 }
 
 static void
-file_unget (ch)
-     int ch;
+file_unget (int ch)
 {
   ungetc (ch, isp->u.u_f.file);
+  isp->u.u_f.end = false;
   if (ch == '\n')
     start_of_input_line = false;
 }
@@ -229,7 +232,15 @@
   else
     m4_debug_message (context, M4_DEBUG_TRACE_INPUT, _("input exhausted"));
 
-  fclose (isp->u.u_f.file);
+  if (ferror (isp->u.u_f.file))
+    {
+      m4_error (context, 0, 0, _("error reading file `%s'"),
+               m4_get_current_file (context));
+      fclose (isp->u.u_f.file);
+    }
+  else if (isp->u.u_f.close && fclose (isp->u.u_f.file) == EOF)
+    m4_error (context, 0, errno, _("error reading file `%s'"),
+             m4_get_current_file (context));
   m4_set_current_file (context, isp->u.u_f.name);
   m4_set_current_line (context, isp->u.u_f.lineno);
   m4_output_current_line = isp->u.u_f.out_lineno;
@@ -242,8 +253,18 @@
   file_peek, file_read, file_unget, file_clean
 };
 
+/* m4_push_file () pushes an input file FP with name TITLE on the
+  input stack, saving the current file name and line number.  If next
+  is non-NULL, this push invalidates a call to m4_push_string_init (),
+  whose storage is consequently released.  If CLOSE, then close FP at
+  end of file.
+
+  file_read () manages line numbers for error messages, so they do not
+  get wrong due to lookahead.  The token consisting of a newline
+  alone is taken as belonging to the line it ends, and the current
+  line number is not incremented until the next character is read.  */
 void
-m4_push_file (m4 *context, FILE *fp, const char *title)
+m4_push_file (m4 *context, FILE *fp, const char *title, bool close)
 {
   input_block *i;
 
@@ -261,6 +282,7 @@
   i->funcs = &file_funcs;
 
   i->u.u_f.file = fp;
+  i->u.u_f.end = false;
   i->u.u_f.name = m4_get_current_file (context);
   i->u.u_f.lineno = m4_get_current_line (context);
   i->u.u_f.out_lineno = m4_output_current_line;
@@ -275,9 +297,6 @@
   isp = i;
 }
 
-/* m4_push_builtin () pushes a builtins definition on the input stack.  If
-   next is non-NULL, this push invalidates a call to m4_push_string_init (),
-   whose storage are consequentely released.  */
 static int
 builtin_peek (void)
 {
@@ -301,6 +320,10 @@
   builtin_peek, builtin_read, NULL, NULL
 };
 
+/* m4_push_builtin () pushes TOKEN, which contains a builtin's
+   definition, on the input stack.  If next is non-NULL, this push
+   invalidates a call to m4_push_string_init (), whose storage is
+   consequently released.  */
 void
 m4_push_builtin (m4_symbol_value *token)
 {
@@ -331,7 +354,6 @@
   isp = i;
 }
 
-/* Push a single character on to the input stack.  */
 static int
 single_peek (void)
 {
@@ -353,6 +375,7 @@
   single_peek, single_read, NULL, NULL
 };
 
+/* Push a single character CH on to the input stack.  */
 void
 m4_push_single (int ch)
 {
@@ -375,12 +398,10 @@
   isp = i;
 }
 
-/* First half of m4_push_string ().  The pointer next points to the new
-   input_block.  */
 static int
 string_peek (void)
 {
-  int ch = *isp->u.u_s.current;
+  int ch = (unsigned char) *isp->u.u_s.current;
 
   return (ch == '\0') ? CHAR_RETRY : ch;
 }
@@ -388,7 +409,7 @@
 static int
 string_read (m4 *context M4_GNUC_UNUSED)
 {
-  int ch = *isp->u.u_s.current++;
+  int ch = (unsigned char) *isp->u.u_s.current++;
 
   return (ch == '\0') ? CHAR_RETRY : ch;
 
@@ -400,13 +421,15 @@
   if (isp->u.u_s.current > isp->u.u_s.start)
     *--isp->u.u_s.current = ch;
   else
-    m4_push_single(ch);
+    m4_push_single (ch);
 }
 
 static struct input_funcs string_funcs = {
   string_peek, string_read, string_unget, NULL
 };
 
+/* First half of m4_push_string ().  The pointer next points to the new
+   input_block.  */
 m4_obstack *
 m4_push_string_init (m4 *context)
 {
@@ -459,17 +482,19 @@
    the input stack, and m4_push_string () and m4_push_file () will
    operate on wrapup_stack.  M4_push_wrapup should be done as
    m4_push_string (), but this will suffice, as long as arguments to
-   m4_m4wrap () are moderate in size.  */
+   m4_m4wrap () are moderate in size.
+
+   FIXME - we should allow pushing builtins as well as text.  */
 void
 m4_push_wrapup (const char *s)
 {
-  input_block *i = (input_block *) obstack_alloc (&wrapup_stack,
+  input_block *i = (input_block *) obstack_alloc (wrapup_stack,
                                                  sizeof (struct input_block));
   i->prev = wsp;
 
   i->funcs = &string_funcs;
 
-  i->u.u_s.start = obstack_copy0 (&wrapup_stack, s, strlen (s));
+  i->u.u_s.start = obstack_copy0 (wrapup_stack, s, strlen (s));
   i->u.u_s.current = i->u.u_s.start;
 
   wsp = i;
@@ -478,7 +503,7 @@
 
 /* The function pop_input () pops one level of input sources.  If the
    popped input_block is a file, current_file and current_line are
-   reset to the saved values before the memory for the input_block are
+   reset to the saved values before the memory for the input_block is
    released.  */
 static void
 pop_input (m4 *context)
@@ -488,22 +513,37 @@
   if (isp->funcs->clean_func != NULL)
     (*isp->funcs->clean_func) (context);
 
-  obstack_free (current_input, isp);
-  next = NULL;                 /* might be set in m4_push_string_init () */
+  if (tmp != NULL)
+    {
+      obstack_free (current_input, isp);
+      next = NULL;     /* might be set in m4_push_string_init () */
+    }
 
   isp = tmp;
 }
 
-/* To switch input over to the wrapup stack, main () calls pop_wrapup
+/* To switch input over to the wrapup stack, main () calls pop_wrapup.
    Since wrapup text can install new wrapup text, pop_wrapup () returns
    false when there is no wrapup text on the stack, and true otherwise.  */
 bool
 m4_pop_wrapup (void)
 {
+  next = NULL;
+  obstack_free (current_input, NULL);
+  free (current_input);
+
   if (wsp == NULL)
-    return false;
+    {
+      obstack_free (wrapup_stack, NULL);
+      current_input = NULL;
+      DELETE (wrapup_stack);
+      return false;
+    }
+
+  current_input = wrapup_stack;
+  wrapup_stack = (m4_obstack *) xmalloc (sizeof (m4_obstack));
+  obstack_init (wrapup_stack);
 
-  current_input = &wrapup_stack;
   isp = wsp;
   wsp = NULL;
 
@@ -524,7 +564,7 @@
   m4_set_symbol_value_func (token, isp->u.u_b.func);
   VALUE_HANDLE (token)         = isp->u.u_b.handle;
   VALUE_FLAGS (token)          = isp->u.u_b.flags;
-  VALUE_ARG_SIGNATURE(token)   = isp->u.u_b.arg_signature;
+  VALUE_ARG_SIGNATURE (token)  = isp->u.u_b.arg_signature;
   VALUE_MIN_ARGS (token)       = isp->u.u_b.min_args;
   VALUE_MAX_ARGS (token)       = isp->u.u_b.max_args;
 }
@@ -549,7 +589,7 @@
        {
          while ((ch = f (context)) != CHAR_RETRY)
            {
-             /* if (!IS_IGNORE(ch)) */
+             /* if (!IS_IGNORE (ch)) */
                return ch;
            }
        }
@@ -564,11 +604,11 @@
     }
 }
 
-/* The function m4_peek_input () is used to look at the next character in
+/* The function peek_char () is used to look at the next character in
    the input stream.  At any given time, it reads from the input_block
    on the top of the current input stack.  */
-int
-m4_peek_input (m4 *context)
+static int
+peek_char (m4 *context)
 {
   int ch;
   int (*f) (void);
@@ -581,97 +621,121 @@
       f = isp->funcs->peek_func;
       if (f != NULL)
        {
-         if ((ch = (*f)()) != CHAR_RETRY)
+         if ((ch = f ()) != CHAR_RETRY)
            {
-             return /* (IS_IGNORE(ch)) ? next_char () : */ ch;
+             return /* (IS_IGNORE (ch)) ? next_char () : */ ch;
            }
        }
       else
        {
-         assert (!"INTERNAL ERROR: input stack botch in m4_peek_input ()");
+         assert (!"INTERNAL ERROR: input stack botch in peek_char ()");
          abort ();
        }
 
-      /* End of input source --- pop one level.  */
-      pop_input (context);
+      /* End of current input source --- pop one level if another
+        level of input still exists.  */
+      if (isp->prev != NULL)
+       pop_input (context);
+      else
+       return CHAR_EOF;
     }
 }
 
 /* The function unget_input () puts back a character on the input
-   stack, using an existing input_block if possible.  */
+   stack, using an existing input_block if possible.  This is not safe
+   to call more than once without an intervening next_char.  */
 static void
 unget_input (int ch)
 {
   if (isp != NULL && isp->funcs->unget_func != NULL)
-    (*isp->funcs->unget_func)(ch);
+    isp->funcs->unget_func (ch);
   else
-    m4_push_single(ch);
+    m4_push_single (ch);
 }
 
-/* skip_line () simply discards all immediately following characters, upto
+/* skip_line () simply discards all immediately following characters, up to
    the first newline.  It is only used from m4_dnl ().  */
 void
 m4_skip_line (m4 *context)
 {
   int ch;
+  const char *file = m4_get_current_file (context);
+  int line = m4_get_current_line (context);
 
   while ((ch = next_char (context)) != CHAR_EOF && ch != '\n')
     ;
+  if (ch == CHAR_EOF)
+    /* current_file changed; use the previous value we cached.  */
+    m4_warn_at_line (context, 0, file, line,
+                    _("end of file treated as newline"));
 }
 
 
 
 /* This function is for matching a string against a prefix of the
-   input stream.  If the string matches the input, the input is
-   discarded, otherwise the characters read are pushed back again.
-   The function is used only when multicharacter quotes or comment
-   delimiters are used.
+   input stream.  If the string S matches the input and CONSUME is
+   true, the input is discarded; otherwise any characters read are
+   pushed back again.  The function is used only when multicharacter
+   quotes or comment delimiters are used.
 
    All strings herein should be unsigned.  Otherwise sign-extension
    of individual chars might break quotes with 8-bit chars in it.  */
-static int
-match_input (m4 *context, const unsigned char *s)
+static bool
+match_input (m4 *context, const unsigned char *s, bool consume)
 {
   int n;                       /* number of characters matched */
   int ch;                      /* input character */
   const unsigned char *t;
   m4_obstack *st;
+  bool result = false;
 
-  ch = m4_peek_input (context);
+  ch = peek_char (context);
   if (ch != *s)
-    return 0;                  /* fail */
-  (void) next_char (context);
+    return false;                      /* fail */
 
   if (s[1] == '\0')
-    return 1;                  /* short match */
+    {
+      if (consume)
+       next_char (context);
+      return true;                     /* short match */
+    }
 
-  for (n = 1, t = s++; (ch = m4_peek_input (context)) == *s++; n++)
+  next_char (context);
+  for (n = 1, t = s++; (ch = peek_char (context)) == *s++; )
     {
-      (void) next_char (context);
+      next_char (context);
+      n++;
       if (*s == '\0')          /* long match */
-       return 1;
+       {
+         if (consume)
+           return true;
+         result = true;
+         break;
+       }
     }
 
-  /* Failed, push back input.  */
+  /* Failed or shouldn't consume, push back input.  */
   st = m4_push_string_init (context);
   obstack_grow (st, t, n);
   m4_push_string_finish ();
-  return 0;
+  return result;
 }
 
-/* The macro MATCH() is used to match a string against the input.  The
-  first character is handled inline, for speed.  Hopefully, this will not
-  hurt efficiency too much when single character quotes and comment
-  delimiters are used.  */
-#define MATCH(C, ch, s) \
-  ((s)[0] == (ch) \
-   && (ch) != '\0' \
-   && ((s)[1] == '\0' \
-       || (match_input ((C), (s) + 1) ? (ch) = m4_peek_input (C), 1 : 0)))
+/* The macro MATCH() is used to match an unsigned char string S
+  against the input.  The first character is handled inline, for
+  speed.  Hopefully, this will not hurt efficiency too much when
+  single character quotes and comment delimiters are used.  If
+  CONSUME, then CH is the result of next_char, and a successful match
+  will discard the matched string.  Otherwise, CH is the result of
+  peek_char, and the input stream is effectively unchanged.  */
+#define MATCH(C, ch, s, consume)                                       \
+  ((s)[0] == (ch)                                                      \
+   && (ch) != '\0'                                                     \
+   && ((s)[1] == '\0' || (match_input (C, (s) + (consume), consume))))
 
 
 
-/* Inititialise input stacks, and quote/comment characters.  */
+/* Inititialize input stacks, and quote/comment characters.  */
 void
 m4_input_init (m4 *context)
 {
@@ -681,10 +745,11 @@
   m4_set_current_line (context, 0);
 
   obstack_init (&token_stack);
-  obstack_init (&input_stack);
-  obstack_init (&wrapup_stack);
 
-  current_input = &input_stack;
+  current_input = (m4_obstack *) xmalloc (sizeof (m4_obstack));
+  obstack_init (current_input);
+  wrapup_stack = (m4_obstack *) xmalloc (sizeof (m4_obstack));
+  obstack_init (wrapup_stack);
 
   obstack_1grow (&token_stack, '\0');
   token_bottom = obstack_finish (&token_stack);
@@ -699,9 +764,8 @@
 void
 m4_input_exit (void)
 {
-  obstack_free (&wrapup_stack, NULL);
-  obstack_free (&input_stack, NULL);
-  obstack_free (&token_stack, NULL);
+  assert (current_input == NULL);
+  assert (wrapup_stack == NULL);
 }
 
 
@@ -713,9 +777,9 @@
    that is not a part of any of the previous types.
 
    M4__next_token () returns the token type, and passes back a pointer to
-   the token data through VALUE.  The token text is collected on the obstack
+   the token data through TOKEN.  The token text is collected on the obstack
    token_stack, which never contains more than one token text at a time.
-   The storage pointed to by the fields in VALUE is therefore subject to
+   The storage pointed to by the fields in TOKEN is therefore subject to
    change the next time m4__next_token () is called.  */
 m4__token_type
 m4__next_token (m4 *context, m4_symbol_value *token)
@@ -725,30 +789,39 @@
   m4__token_type type;
 
   do {
+    const char *file = m4_get_current_file (context);
+    int line = m4_get_current_line (context);
+
     obstack_free (&token_stack, token_bottom);
     obstack_1grow (&token_stack, '\0');
     token_bottom = obstack_finish (&token_stack);
 
-    ch = m4_peek_input (context);
+    /* Must consume an input character, but not until CHAR_BUILTIN is
+       handled.  */
+    ch = peek_char (context);
     if (ch == CHAR_EOF)                        /* EOF */
       {
 #ifdef DEBUG_INPUT
        fprintf (stderr, "next_token -> EOF\n");
 #endif
+       next_char (context);
        return M4_TOKEN_EOF;
       }
 
     if (ch == CHAR_BUILTIN)            /* BUILTIN TOKEN */
       {
        init_builtin_token (context, token);
-       (void) next_char (context);
+       next_char (context);
 #ifdef DEBUG_INPUT
        m4_print_token ("next_token", M4_TOKEN_MACDEF, token);
 #endif
        return M4_TOKEN_MACDEF;
       }
 
-    (void) next_char (context);
+    next_char (context); /* Consume character we already peeked at.  */
+    /* FIXME - other implementations, such as Solaris, parse macro
+       names, then quotes, then comments.  We should probably
+       rearrange this to match.  */
     if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_BCOMM))
       {                                        /* COMMENT, SHORT DELIM */
        obstack_1grow (&token_stack, ch);
@@ -757,22 +830,28 @@
          obstack_1grow (&token_stack, ch);
        if (ch != CHAR_EOF)
          obstack_1grow (&token_stack, ch);
-       type = m4_get_discard_comments_opt (context)
-               ? M4_TOKEN_NONE : M4_TOKEN_STRING;
+       else
+         m4_error_at_line (context, EXIT_FAILURE, 0, file, line,
+                           _("end of file in comment"));
+       type = (m4_get_discard_comments_opt (context)
+               ? M4_TOKEN_NONE : M4_TOKEN_STRING);
       }
     else if (!m4_is_syntax_single_comments (M4SYNTAX)
-            && MATCH (context, ch, context->syntax->bcomm.string))
+            && MATCH (context, ch, context->syntax->bcomm.string, true))
       {                                        /* COMMENT, LONGER DELIM */
        obstack_grow (&token_stack, context->syntax->bcomm.string,
                      context->syntax->bcomm.length);
        while ((ch = next_char (context)) != CHAR_EOF
-              && !MATCH (context, ch, context->syntax->ecomm.string))
+              && !MATCH (context, ch, context->syntax->ecomm.string, true))
          obstack_1grow (&token_stack, ch);
        if (ch != CHAR_EOF)
          obstack_grow (&token_stack, context->syntax->ecomm.string,
                        context->syntax->ecomm.length);
-       type = m4_get_discard_comments_opt (context)
-               ? M4_TOKEN_NONE : M4_TOKEN_STRING;
+       else
+         m4_error_at_line (context, EXIT_FAILURE, 0, file, line,
+                           _("end of file in comment"));
+       type = (m4_get_discard_comments_opt (context)
+               ? M4_TOKEN_NONE : M4_TOKEN_STRING);
       }
     else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ESCAPE))
       {                                        /* ESCAPED WORD */
@@ -790,7 +869,7 @@
                  }
 
                if (ch != CHAR_EOF)
-                 unget_input(ch);
+                 unget_input (ch);
              }
            else
              {
@@ -814,23 +893,20 @@
            obstack_1grow (&token_stack, ch);
          }
        if (ch != CHAR_EOF)
-         unget_input(ch);
+         unget_input (ch);
 
-       type = m4_is_syntax_macro_escaped (M4SYNTAX)
-               ? M4_TOKEN_STRING : M4_TOKEN_WORD;
+       type = (m4_is_syntax_macro_escaped (M4SYNTAX)
+               ? M4_TOKEN_STRING : M4_TOKEN_WORD);
       }
     else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_LQUOTE))
       {                                        /* QUOTED STRING, SINGLE QUOTES 
*/
-       const char *current_file = m4_get_current_file (context);
-       int current_line = m4_get_current_line (context);
        quote_level = 1;
        while (1)
          {
            ch = next_char (context);
            if (ch == CHAR_EOF)
-             error_at_line (EXIT_FAILURE, 0,
-                             current_file, current_line,
-                             _("end of file in string"));
+             m4_error_at_line (context, EXIT_FAILURE, 0, file, line,
+                               _("end of file in string"));
 
            if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_RQUOTE))
              {
@@ -849,26 +925,23 @@
        type = M4_TOKEN_STRING;
       }
     else if (!m4_is_syntax_single_quotes (M4SYNTAX)
-            && MATCH (context, ch, context->syntax->lquote.string))
+            && MATCH (context, ch, context->syntax->lquote.string, true))
       {                                        /* QUOTED STRING, LONGER QUOTES 
*/
-       const char *current_file = m4_get_current_file (context);
-       int current_line = m4_get_current_line (context);
        quote_level = 1;
        while (1)
          {
            ch = next_char (context);
            if (ch == CHAR_EOF)
-             error_at_line (EXIT_FAILURE, 0,
-                             current_file, current_line,
-                             _("end of file in string"));
-           if (MATCH (context, ch, context->syntax->rquote.string))
+             m4_error_at_line (context, EXIT_FAILURE, 0, file, line,
+                               _("end of file in string"));
+           if (MATCH (context, ch, context->syntax->rquote.string, true))
              {
                if (--quote_level == 0)
                  break;
                obstack_grow (&token_stack, context->syntax->rquote.string,
                              context->syntax->rquote.length);
              }
-           else if (MATCH (context, ch, context->syntax->lquote.string))
+           else if (MATCH (context, ch, context->syntax->lquote.string, true))
              {
                quote_level++;
                obstack_grow (&token_stack, context->syntax->lquote.string,
@@ -879,6 +952,26 @@
          }
        type = M4_TOKEN_STRING;
       }
+    else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ACTIVE))
+      {                                        /* ACTIVE CHARACTER */
+       obstack_1grow (&token_stack, ch);
+       type = M4_TOKEN_WORD;
+      }
+    else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_OPEN))
+      {                                        /* OPEN PARENTHESIS */
+       obstack_1grow (&token_stack, ch);
+       type = M4_TOKEN_OPEN;
+      }
+    else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_COMMA))
+      {                                        /* COMMA */
+       obstack_1grow (&token_stack, ch);
+       type = M4_TOKEN_COMMA;
+      }
+    else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_CLOSE))
+      {                                        /* CLOSE PARENTHESIS */
+       obstack_1grow (&token_stack, ch);
+       type = M4_TOKEN_CLOSE;
+      }
     else if (m4_is_syntax_single_quotes (M4SYNTAX)
             && m4_is_syntax_single_comments (M4SYNTAX))
       {                        /* EVERYTHING ELSE (SHORT QUOTES AND COMMENTS) 
*/
@@ -888,7 +981,7 @@
            || m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_NUM)
            || m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_DOLLAR))
          {
-           while (((ch = next_char(context)) != CHAR_EOF)
+           while (((ch = next_char (context)) != CHAR_EOF)
                   && (m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_OTHER)
                       || m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_NUM)
                       || m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_DOLLAR)))
@@ -897,7 +990,7 @@
              }
 
            if (ch != CHAR_EOF)
-             unget_input(ch);
+             unget_input (ch);
            type = M4_TOKEN_STRING;
          }
        else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_SPACE))
@@ -909,12 +1002,10 @@
                  obstack_1grow (&token_stack, ch);
 
                if (ch != CHAR_EOF)
-                 unget_input(ch);
+                 unget_input (ch);
              }
            type = M4_TOKEN_SPACE;
          }
-       else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ACTIVE))
-         type = M4_TOKEN_WORD;
        else
          type = M4_TOKEN_SIMPLE;
       }
@@ -928,8 +1019,6 @@
          type = M4_TOKEN_STRING;
        else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_SPACE))
          type = M4_TOKEN_SPACE;
-       else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ACTIVE))
-         type = M4_TOKEN_WORD;
        else
          type = M4_TOKEN_SIMPLE;
       }
@@ -943,12 +1032,125 @@
   VALUE_MAX_ARGS (token)       = -1;
 
 #ifdef DEBUG_INPUT
-  m4_print_token("next_token", type, token);
+  m4_print_token ("next_token", type, token);
 #endif
 
   return type;
 }
 
+/* Peek and return the type of the next single token from the input
+   stream.  When peeking to see if changequote (or friends) are
+   followed by an open parentheses, it is possible that the token type
+   we peek at now will change by the time we parse it with
+   next_token.  */
+m4__token_type
+m4__peek_token (m4 *context)
+{
+  int ch = peek_char (context);
+
+  if (ch == CHAR_EOF)
+    {
+#ifdef DEBUG_INPUT
+      fprintf (stderr, "peek_token -> EOF\n");
+#endif
+      return M4_TOKEN_EOF;
+    }
+  if (ch == CHAR_BUILTIN)
+    {
+#ifdef DEBUG_INPUT
+      fprintf (stderr, "peek_token -> BUILTIN\n");
+#endif
+      return M4_TOKEN_MACDEF;
+    }
+  if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_BCOMM)
+      || (!m4_is_syntax_single_comments (M4SYNTAX)
+         && MATCH (context, ch, context->syntax->bcomm.string, false)))
+    {
+#ifdef DEBUG_INPUT
+      fprintf (stderr, "peek_token -> COMMENT\n");
+#endif
+      return M4_TOKEN_STRING;
+    }
+  if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ESCAPE))
+    {
+      int c;
+      next_char (context);
+      c = peek_char (context);
+      unget_input (ch);
+#ifdef DEBUG_INPUT
+      fprintf (stderr, "peek_token -> %s\n",
+              c == CHAR_EOF ? "SIMPLE" : "ESCAPE_WORD");
+#endif
+      return c == CHAR_EOF ? M4_TOKEN_SIMPLE : M4_TOKEN_WORD;
+    }
+  if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ALPHA))
+    {
+#ifdef DEBUG_INPUT
+      fprintf (stderr, "peek_token -> %s\n",
+              m4_is_syntax_macro_escaped (M4SYNTAX) ? "STRING" : "WORD");
+#endif
+      return (m4_is_syntax_macro_escaped (M4SYNTAX)
+             ? M4_TOKEN_STRING : M4_TOKEN_WORD);
+    }
+  if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_LQUOTE)
+      || (!m4_is_syntax_single_quotes (M4SYNTAX)
+         && MATCH (context, ch, context->syntax->lquote.string, false)))
+    {
+#ifdef DEBUG_INPUT
+      fprintf (stderr, "peek_token -> QUOTE\n");
+#endif
+      return M4_TOKEN_STRING;
+    }
+  if (m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_ACTIVE))
+    {
+#ifdef DEBUG_INPUT
+      fprintf (stderr, "peek_token -> ACTIVE\n");
+#endif
+      return M4_TOKEN_WORD;
+    }
+  if (m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_OPEN))
+    {
+#ifdef DEBUG_INPUT
+      fprintf (stderr, "peek_token -> OPEN\n");
+#endif
+      return M4_TOKEN_OPEN;
+    }
+  if (m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_COMMA))
+    {
+#ifdef DEBUG_INPUT
+      fprintf (stderr, "peek_token -> COMMA\n");
+#endif
+      return M4_TOKEN_COMMA;
+    }
+  if (m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_CLOSE))
+    {
+#ifdef DEBUG_INPUT
+      fprintf (stderr, "peek_token -> CLOSE\n");
+#endif
+      return M4_TOKEN_CLOSE;
+    }
+  if (m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_OTHER)
+      || m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_NUM)
+      || m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_DOLLAR))
+    {
+#ifdef DEBUG_INPUT
+      fprintf (stderr, "peek_token -> STRING\n");
+#endif
+      return M4_TOKEN_STRING;
+    }
+  if (m4_is_syntax (M4SYNTAX, ch, M4_SYNTAX_SPACE))
+    {
+#ifdef DEBUG_INPUT
+      fprintf (stderr, "peek_token -> SPACE\n");
+#endif
+      return M4_TOKEN_SPACE;
+    }
+#ifdef DEBUG_INPUT
+  fprintf (stderr, "peek_token -> SIMPLE\n");
+#endif
+  return M4_TOKEN_SIMPLE;
+}
+
 
 
 #ifdef DEBUG_INPUT
Index: m4/m4module.h
===================================================================
RCS file: /sources/m4/m4/m4/m4module.h,v
retrieving revision 1.83
diff -u -r1.83 m4module.h
--- m4/m4module.h       5 Sep 2006 23:16:39 -0000       1.83
+++ m4/m4module.h       7 Sep 2006 23:45:38 -0000
@@ -112,7 +112,11 @@
 
 /* Error handling.  */
 extern void m4_error (m4 *, int, int, const char *, ...) M4_GNUC_PRINTF (4, 5);
+extern void m4_error_at_line (m4 *, int, int, const char *, int,
+                             const char *, ...)         M4_GNUC_PRINTF (6, 7);
 extern void m4_warn  (m4 *, int, const char *, ...)      M4_GNUC_PRINTF (3, 4);
+extern void m4_warn_at_line  (m4 *, int, const char *, int,
+                             const char *, ...)         M4_GNUC_PRINTF (5, 6);
 
 
 /* --- CONTEXT MANAGEMENT --- */
@@ -213,6 +217,8 @@
 extern bool            m4_set_symbol_name_traced (m4_symbol_table*,
                                                   const char *);
 
+#define m4_is_symbol_void(symbol)                                      \
+       (m4_is_symbol_value_void (m4_get_symbol_value (symbol)))
 #define m4_is_symbol_text(symbol)                                      \
        (m4_is_symbol_value_text (m4_get_symbol_value (symbol)))
 #define m4_is_symbol_func(symbol)                                      \
@@ -372,12 +378,11 @@
 
 extern void    m4_input_init   (m4 *context);
 extern void    m4_input_exit   (void);
-extern int     m4_peek_input   (m4 *context);
 extern void    m4_skip_line    (m4 *context);
 
 /* push back input */
 
-extern void    m4_push_file    (m4 *context, FILE *, const char *);
+extern void    m4_push_file    (m4 *context, FILE *, const char *, bool);
 extern void    m4_push_single  (int ch);
 extern void    m4_push_builtin (m4_symbol_value *);
 extern m4_obstack *m4_push_string_init (m4 *context);
Index: m4/m4private.h
===================================================================
RCS file: /sources/m4/m4/m4/m4private.h,v
retrieving revision 1.59
diff -u -r1.59 m4private.h
--- m4/m4private.h      5 Sep 2006 23:16:39 -0000       1.59
+++ m4/m4private.h      7 Sep 2006 23:45:38 -0000
@@ -310,11 +310,15 @@
   M4_TOKEN_STRING,             /* a quoted string */
   M4_TOKEN_SPACE,              /* whitespace */
   M4_TOKEN_WORD,               /* an identifier */
+  M4_TOKEN_OPEN,               /* argument list start */
+  M4_TOKEN_COMMA,              /* argument separator */
+  M4_TOKEN_CLOSE,              /* argument list end */
   M4_TOKEN_SIMPLE,             /* a single character */
-  M4_TOKEN_MACDEF              /* a macros definition (see "defn") */
+  M4_TOKEN_MACDEF              /* a macro's definition (see "defn") */
 } m4__token_type;
 
-extern m4__token_type m4__next_token (m4 *context, m4_symbol_value *);
+extern m4__token_type m4__next_token (m4 *, m4_symbol_value *);
+extern m4__token_type m4__peek_token (m4 *);
 
 
 
Index: m4/macro.c
===================================================================
RCS file: /sources/m4/m4/m4/macro.c,v
retrieving revision 1.52
diff -u -r1.52 macro.c
--- m4/macro.c  5 Sep 2006 13:25:24 -0000       1.52
+++ m4/macro.c  7 Sep 2006 23:45:38 -0000
@@ -92,6 +92,9 @@
     case M4_TOKEN_MACDEF:
       break;
 
+    case M4_TOKEN_OPEN:
+    case M4_TOKEN_COMMA:
+    case M4_TOKEN_CLOSE:
     case M4_TOKEN_SIMPLE:
     case M4_TOKEN_STRING:
     case M4_TOKEN_SPACE:
@@ -101,18 +104,16 @@
     case M4_TOKEN_WORD:
       {
        char *textp = text;
-       int ch;
 
        if (m4_has_syntax (M4SYNTAX, *textp, M4_SYNTAX_ESCAPE))
          ++textp;
 
        symbol = m4_symbol_lookup (M4SYMTAB, textp);
+       assert (! symbol || ! m4_is_symbol_void (symbol));
        if (symbol == NULL
-           || symbol->value->type == M4_SYMBOL_VOID
            || (symbol->value->type == M4_SYMBOL_FUNC
                && BIT_TEST (SYMBOL_FLAGS (symbol), VALUE_BLIND_ARGS_BIT)
-               && (ch = m4_peek_input (context)) < CHAR_EOF
-               && !m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_OPEN)))
+               && m4__peek_token (context) != M4_TOKEN_OPEN))
          {
            m4_shipout_text (context, obs, text, strlen (text));
          }
@@ -160,11 +161,9 @@
     {
       switch (type)
        {                       /* TOKSW */
-       case M4_TOKEN_SIMPLE:
-         text = m4_get_symbol_value_text (&token);
-         if ((m4_has_syntax (M4SYNTAX, *text,
-                             M4_SYNTAX_COMMA|M4_SYNTAX_CLOSE))
-             && paren_level == 0)
+       case M4_TOKEN_COMMA:
+       case M4_TOKEN_CLOSE:
+         if (paren_level == 0)
            {
 
              /* The argument MUST be finished, whether we want it or not.  */
@@ -175,11 +174,12 @@
                {
                  m4_set_symbol_value_text (argp, text);
                }
-             return m4_has_syntax (M4SYNTAX,
-                                   *m4_get_symbol_value_text (&token),
-                                   M4_SYNTAX_COMMA);
+             return type == M4_TOKEN_COMMA;
            }
-
+         /* fallthru */
+       case M4_TOKEN_OPEN:
+       case M4_TOKEN_SIMPLE:
+         text = m4_get_symbol_value_text (&token);
          if (m4_has_syntax (M4SYNTAX, *text, M4_SYNTAX_OPEN))
            paren_level++;
          else if (m4_has_syntax (M4SYNTAX, *text, M4_SYNTAX_CLOSE))
@@ -286,7 +286,6 @@
 collect_arguments (m4 *context, const char *name, m4_symbol *symbol,
                   m4_obstack *argptr, m4_obstack *arguments)
 {
-  int ch;                      /* lookahead for ( */
   m4_symbol_value token;
   m4_symbol_value *tokenp;
   bool more_args;
@@ -299,8 +298,7 @@
                                      sizeof (token));
   obstack_grow (argptr, (void *) &tokenp, sizeof (tokenp));
 
-  ch = m4_peek_input (context);
-  if ((ch < CHAR_EOF) && m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_OPEN))
+  if (m4__peek_token (context) == M4_TOKEN_OPEN)
     {
       m4__next_token (context, &token);                /* gobble parenthesis */
       do
Index: m4/utility.c
===================================================================
RCS file: /sources/m4/m4/m4/utility.c,v
retrieving revision 1.47
diff -u -r1.47 utility.c
--- m4/utility.c        31 Aug 2006 03:21:35 -0000      1.47
+++ m4/utility.c        7 Sep 2006 23:45:38 -0000
@@ -128,6 +128,26 @@
   m4_set_exit_status (context, EXIT_FAILURE);
 }
 
+/* Issue an error.  The message is printf-style, based on FORMAT and
+   any other arguments, and the program name and location (from FILE
+   and LINE) are automatically prepended.  If ERRNUM is non-zero,
+   include strerror output in the message.  If STATUS is non-zero, or
+   if errors are fatal, call exit immediately; otherwise, remember
+   that an error occurred so that m4 cannot exit with success later
+   on.*/
+void
+m4_error_at_line (m4 *context, int status, int errnum, const char *file,
+                 int line, const char *format, ...)
+{
+  va_list args;
+  va_start (args, format);
+  if (status == EXIT_SUCCESS && m4_get_fatal_warnings_opt (context))
+    status = EXIT_FAILURE;
+  verror_at_line (status, errnum, line ? file : NULL,
+                 line, format, args);
+  m4_set_exit_status (context, EXIT_FAILURE);
+}
+
 /* Issue a warning, if they are not being suppressed.  The message is
    printf-style, based on FORMAT and any other arguments, and the
    program name, location (if we are currently parsing an input file),
@@ -154,3 +174,29 @@
       free (full_format);
     }
 }
+
+/* Issue a warning, if they are not being suppressed.  The message is
+   printf-style, based on FORMAT and any other arguments, and the
+   program name, location (from FILE and LINE), and "Warning:" are
+   automatically prepended.  If ERRNUM is non-zero, include strerror
+   output in the message.  If warnings are fatal, call exit
+   immediately, otherwise exit status is unchanged.  */
+void
+m4_warn_at_line (m4 *context, int errnum, const char *file, int line,
+                const char *format, ...)
+{
+  if (!m4_get_suppress_warnings_opt (context))
+    {
+      va_list args;
+      int status = EXIT_SUCCESS;
+      char *full_format = xasprintf(_("Warning: %s"), format);
+      va_start (args, format);
+      if (m4_get_fatal_warnings_opt (context))
+       status = EXIT_FAILURE;
+      /* If the full_format failed (unlikely though that may be), at
+        least fall back on the original format.  */
+      verror_at_line (status, errnum, line ? file : NULL, line,
+                     full_format ? full_format : format, args);
+      free (full_format);
+    }
+}
Index: modules/m4.c
===================================================================
RCS file: /sources/m4/m4/modules/m4.c,v
retrieving revision 1.68
diff -u -r1.68 m4.c
--- modules/m4.c        31 Aug 2006 03:21:35 -0000      1.68
+++ modules/m4.c        7 Sep 2006 23:45:38 -0000
@@ -650,7 +650,7 @@
       return;
     }
 
-  m4_push_file (context, fp, name);
+  m4_push_file (context, fp, name, true);
   free (name);
 }
 
Index: src/main.c
===================================================================
RCS file: /sources/m4/m4/src/main.c,v
retrieving revision 1.79
diff -u -r1.79 main.c
--- src/main.c  5 Sep 2006 23:16:40 -0000       1.79
+++ src/main.c  7 Sep 2006 23:45:38 -0000
@@ -236,6 +236,7 @@
   macro_definition *defines;
   FILE *fp;
   char *filename;
+  bool read_stdin = false;     /* true iff we have read from stdin */
 
   m4 *context;
 
@@ -549,14 +550,18 @@
   exit_status = EXIT_SUCCESS;
   if (optind == argc)
     {
-      m4_push_file (context, stdin, "stdin");
+      m4_push_file (context, stdin, "stdin", false);
+      read_stdin = true;
       m4_macro_expand_input (context);
     }
   else
     for (; optind < argc; optind++)
       {
        if (strcmp (argv[optind], "-") == 0)
-         m4_push_file (context, stdin, "stdin");
+         {
+           m4_push_file (context, stdin, "stdin", false);
+           read_stdin = true;
+         }
        else
          {
            fp = m4_path_search (context, argv[optind], &filename);
@@ -568,7 +573,7 @@
              }
            else
              {
-               m4_push_file (context, fp, filename);
+               m4_push_file (context, fp, filename, true);
                free (filename);
              }
          }
@@ -576,10 +581,16 @@
       }
 
   /* Now handle wrapup text.  */
-
   while (m4_pop_wrapup ())
     m4_macro_expand_input (context);
 
+  /* Change debug stream back to stderr, to force flushing the debug
+     stream and detect any errors it might have encountered.  Close
+     stdin if we read from it, to detect any errors.  */
+  m4_debug_set_output (context, NULL);
+  if (read_stdin && fclose (stdin) == EOF)
+    m4_error (context, 0, errno, _("error closing stdin"));
+
   if (frozen_file_to_write)
     produce_frozen_state (context, frozen_file_to_write);
   else







reply via email to

[Prev in Thread] Current Thread [Next in Thread]