head - Re: branch-1_4 read stdin twice

Eric Blake <[email protected]>
Newsgroups gmane.comp.gnu.m4.patches
Message-ID <[email protected]>
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  <[email protected]>

	* 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.