/* reader-sub.c: -*- C -*-  This was an old library I wrote a long time ago. */

/*  Copyright (c) 1998 Brian J. Fox
    Author: Brian J. Fox (bfox@ai.mit.edu) Fri Jun 26 19:52:49 1998.  */

typedef struct
{
  char *comment_beg;	   /* Sequence of characters which begin a comment. */
  char *comment_end;	   /* Sequence of characters which end a comment. */
  char *self_delimiting;   /* List of characters which are self-delimiting. */
  char *token_delimiters;  /* Characters which terminate a token. */
  int reader_flags;	   /* Flags controlling actions of the reader. */
} Reader_Behaviour;

/* Valid values to be found in reader_flags. */
#define reader_NESTED_COMMENTS	0x01	/* Allow comment nesting. */
#define reader_SKIP_COMMENTS	0x02	/* Ignore comments while reading. */
#define reader_SKIP_TOKEN_DELIM	0x04	/* Skip all token delimiters before
					   returning the next token. */
#define reader_LEAVE_NEWLINE	0x08	/* Leave the newline in the string. */
#define reader_COPY_TEXT	0x10	/* Return a COPY of the text read. */
#define reader_IGNORE_LEADING	0x20	/* Ignore leading whitespace when
					   reading lines or tokens. */
#define reader_IGNORE_TRAILING	0x40	/* Ignore trailing whitespace when
					   reading lines or tokens. */
#define reader_IGNORE_EMPTY	0x80	/* Ignore empty lines. */
#define reader_XLATE_CRLF      0x100	/* Turn CRLF into LF. */


#define reader_DEFAULTS (reader_SKIP_COMMENTS | reader_LEAVE_NEWLINE | \
			 reader_IGNORE_LEADING | reader_IGNORE_TRAILING | \
			 reader_IGNORE_EMPTY | reader_XLATE_CRLF)

#if 0
static void reader_push_string (char *string, FILE *stream);
static int reader_recent_newlines_count (void);
static Reader_Behaviour *reader_get_behaviour (void);
static Reader_Behaviour *reader_default_behaviour (void);
static void reader_set_behaviour ();
extern char *reader_get_line (), *reader_get_token ();
extern char *reader_get_delimited_expr ();
extern void reader_stream_closed ();
extern FILE *reader_dummy_stream ();
#endif

/* Some defines to make the code more readable. */
#define nested_comments \
	(reader_behaviour.reader_flags & reader_NESTED_COMMENTS)

#define skip_comments \
	(reader_behaviour.reader_flags & reader_SKIP_COMMENTS)

#define skip_token_delim \
	(reader_behaviour.reader_flags & reader_SKIP_TOKEN_DELIM)

#define leave_newline \
	(reader_behaviour.reader_flags & reader_LEAVE_NEWLINE)

#define copy_text \
	(reader_behaviour.reader_flags & reader_COPY_TEXT)

#define ignore_leading \
	(reader_behaviour.reader_flags & reader_IGNORE_LEADING)

#define ignore_trailing \
	(reader_behaviour.reader_flags & reader_IGNORE_TRAILING)

#define ignore_empty \
	(reader_behaviour.reader_flags & reader_IGNORE_EMPTY)

#define xlate_crlf \
	(reader_behaviour.reader_flags & reader_XLATE_CRLF)

#define COMMENT_BEG (reader_behaviour.comment_beg)
#define COMMENT_END (reader_behaviour.comment_end)

#if !defined (strdup)
#  define strdup(x) (char *)strcpy ((char *)xmalloc (1 + strlen (x)), x)
#endif /* !strdup */

#if !defined (whitespace)
#  define whitespace(c) ((c == ' ') || (c == '\t'))
#endif /* !whitespace */

#if !defined (whitespace_or_newline)
#  define whitespace_or_newline(c) (whitespace (c) || (c == '\n'))
#endif /* !whitespace_or_newline */

#if !defined (maybe_free)
#define maybe_free(x) do { if (x) free (x); } while (0)
#endif /* !maybe_free */

/* Variable which holds the number of newlines encountered during the
   last call to reader_xxx (). */
static int newlines_found = 0;


/* **************************************************************** */
/*								    */
/*		   Modifying the Reader's Behaviour		    */
/*								    */
/* **************************************************************** */

static Reader_Behaviour reader_behaviour;

static void
clear_behaviour (void)
{
  reader_behaviour.comment_end = (char *)NULL;
  reader_behaviour.comment_beg = (char *)NULL;
  reader_behaviour.self_delimiting = (char *)NULL;
  reader_behaviour.reader_flags = 0;
}

static Reader_Behaviour *
reader_get_behaviour (void)
{
  return (&reader_behaviour);
}

static Reader_Behaviour *
reader_default_behaviour (void)
{
  Reader_Behaviour *b;

  b = (Reader_Behaviour *)xmalloc (sizeof (Reader_Behaviour));

  b->comment_beg = "#";
  b->comment_end = (char *)NULL;
  b->self_delimiting = (char *)NULL;
  b->token_delimiters = (char *)NULL;
  b->reader_flags = reader_DEFAULTS;

  return (b);
}

static void
reader_set_behaviour (Reader_Behaviour *behaviour)
{
  if (behaviour->comment_beg != reader_behaviour.comment_beg)
    {
      maybe_free (reader_behaviour.comment_beg);
      reader_behaviour.comment_beg = (char *)NULL;
      if (behaviour->comment_beg)
	reader_behaviour.comment_beg = strdup (behaviour->comment_beg);
    }

  if (behaviour->comment_end != reader_behaviour.comment_end)
    {
      maybe_free (reader_behaviour.comment_end);
      reader_behaviour.comment_end = (char *)NULL;
      if (behaviour->comment_end)
	reader_behaviour.comment_end = strdup (behaviour->comment_end);
    }

  if (behaviour->self_delimiting != reader_behaviour.self_delimiting)
    {
      maybe_free (reader_behaviour.self_delimiting);
      reader_behaviour.self_delimiting = (char *)NULL;
      if (behaviour->self_delimiting)
	reader_behaviour.self_delimiting =
	  strdup (behaviour->self_delimiting);
    }

  reader_behaviour.reader_flags = behaviour->reader_flags;
}

/* **************************************************************** */
/*								    */
/*		 Keeping Track of Already Read Text		    */
/*								    */
/* **************************************************************** */

/* A buffer containing characters which we have already read, but
   subsequently decided to unread.  For example, when skipping a
   comment begin, we might find that only 8 of the necessary 10
   characters match.  We then push all of the characters read back
   onto this buffer, where reader_get_next () will find it. */

typedef struct
{
  char *buffer;			/* Where the pushed text is stored. */
  int buffer_size;		/* Number of bytes allocated. */
  int push_index;		/* Where to stuff the next character. */
  FILE *associated_stream;	/* The stream that these characters were
				   read from. */
  int flags;			/* If IS_DUMMY, then the associated stream
				   doesn't really exist. */
} Read_Ahead_Buffer;

#define DUMMY_STREAM 0x1

/* An array of such buffers, one for each stream. */
static Read_Ahead_Buffer **read_ahead_buffers = (Read_Ahead_Buffer **)NULL;
static int read_ahead_buffers_slots = 0;
static int read_ahead_buffers_index = 0;

/* Get the right read ahead buffer structure for STREAM. */
static Read_Ahead_Buffer *
find_read_ahead_buffer (FILE *stream)
{
  register int i;
  Read_Ahead_Buffer *found;

  found = (Read_Ahead_Buffer *)NULL;

  for (i = 0; ((read_ahead_buffers) && (found = read_ahead_buffers[i])); i++)
    if (found->associated_stream == stream)
      break;

  return (found);
}

/* Notify the reader that STREAM has been closed. */
static void
reader_stream_closed (FILE *stream)
{
  register int i;
  Read_Ahead_Buffer *buffer;

  buffer = (Read_Ahead_Buffer *)NULL;

  for (i = 0; ((read_ahead_buffers) && (buffer = read_ahead_buffers[i])); i++)
    if (buffer->associated_stream == stream)
      break;

  /* If this buffer exists, delete it from the chain. */
  if (buffer)
    {
      if (buffer->buffer_size)
	free (buffer->buffer);

      while ((read_ahead_buffers[i] = read_ahead_buffers[i + 1]) != '\0')
	i++;

      read_ahead_buffers_index--;
      free (buffer);
    }
}

/* Find or create a read ahead buffer for STREAM. */
static Read_Ahead_Buffer *
get_read_ahead_buffer (FILE *stream)
{
  Read_Ahead_Buffer *buffer;

  buffer = find_read_ahead_buffer (stream);
  if (!buffer)
    {
      buffer = (Read_Ahead_Buffer *)xmalloc (sizeof (Read_Ahead_Buffer));
      buffer->buffer = (char *)NULL;
      buffer->buffer_size = 0;
      buffer->push_index = 0;
      buffer->associated_stream = stream;
      buffer->flags = 0;

      if (read_ahead_buffers_index + 2 > read_ahead_buffers_slots)
	read_ahead_buffers = (Read_Ahead_Buffer **) xrealloc
	  (read_ahead_buffers,
	   ((read_ahead_buffers_slots += 10) * sizeof (Read_Ahead_Buffer)));

      read_ahead_buffers[read_ahead_buffers_index++] = buffer;
      read_ahead_buffers[read_ahead_buffers_index] =
	(Read_Ahead_Buffer *)NULL;
    }

  return (buffer);
}

/* Make a new dummy stream. */
static FILE *
reader_dummy_stream (void)
{
  Read_Ahead_Buffer *buffer;
  int dummy_counter = 0;

  while ((buffer = find_read_ahead_buffer ((FILE *)dummy_counter))
	 != (Read_Ahead_Buffer *)NULL)
    dummy_counter++;

  buffer = get_read_ahead_buffer ((FILE *)dummy_counter);
  buffer->flags |= DUMMY_STREAM;
  return (buffer->associated_stream);
}

/* Push STRING onto the read ahead buffer for STREAM. */
static void
reader_push_string (char *string, FILE *stream)
{
  Read_Ahead_Buffer *buffer;
  register int i, len;

  buffer = get_read_ahead_buffer (stream);
  len = strlen (string);

  if ((buffer->push_index + len + 1) > buffer->buffer_size)
    buffer->buffer = (char *)
      xrealloc (buffer->buffer, (buffer->buffer_size += (100 + len)));


  /* Copy the string on backwards. */
  for (i = strlen (string) - 1; i > -1; i--)
    buffer->buffer[buffer->push_index++] = string[i];
  buffer->buffer[buffer->push_index] = '\0';
}

/* Push CHARACTER onto the read ahead buffer for STREAM. */
static void
push_character (int character, FILE *stream)
{
  Read_Ahead_Buffer *buffer;

  buffer = get_read_ahead_buffer (stream);

  if ((buffer->push_index + 2) > buffer->buffer_size)
    buffer->buffer = (char *)
      xrealloc (buffer->buffer, (buffer->buffer_size += 100));

  buffer->buffer[buffer->push_index++] = character;
  buffer->buffer[buffer->push_index] = '\0';
}

/* Push COUNT characters from STRING back onto STREAM.  Additionally push
   CHARACTER at the end of the STRING.  This is used by backtracking code,
   such as the comment skipper. */
static void
put_back_with_char (char *string, int count, int character, FILE *stream)
{
  char *put_back;

  put_back = (char *)xmalloc (2 + count);
  strncpy (put_back, string, count);
  put_back[count] = character;
  put_back[count + 1] = '\0';
  reader_push_string (put_back, stream);
  free (put_back);
}

/* Get the next character from STREAM, or from STREAM's associated
   read ahead buffer if present and not empty. */
static int
read_next_char (FILE *stream)
{
  int character;
  Read_Ahead_Buffer *buffer;

  buffer = find_read_ahead_buffer (stream);

  if (buffer && buffer->push_index)
    {
      buffer->push_index--;
      character = buffer->buffer[buffer->push_index];
      buffer->buffer[buffer->push_index] = '\0';
    }
  else if (buffer && (buffer->flags & DUMMY_STREAM))
    character = EOF;
  else
    character = getc (stream);

  if ((xlate_crlf) && (character == '\r'))
    {
      if (buffer && buffer->push_index)
	{
	  if (buffer->buffer[buffer->push_index - 1] == '\n')
	    {
	      buffer->push_index--;
	      buffer->buffer[buffer->push_index] = '\0';
	      character = '\n';
	    }
	}
      else
	{
	  int next;

	  next = getc (stream);
	  if (next == '\n')
	    character = '\n';
	  else
	    ungetc (next, stream);
	}
    }
  return (character);
}

/* Return this CHARACTER to STREAM.  Actually, just place it into
   our read ahead buffer for this stream. */
static void
unread_this_char (int character, FILE *stream)
{
  push_character (character, stream);
}


/* **************************************************************** */
/*								    */
/*	       Reading Input Lines, Tokens, and Comments	    */
/*								    */
/* **************************************************************** */

/* A buffer that we use to gather and process text.  Depending on the
   requested behaviour, either this buffer or a copy of it is returned
   to the caller. */
static char *reader_buffer = (char *)NULL;
static int buffer_size = 0;
static int buffer_index = 0;


/* **************************************************************** */
/*								    */
/*	   Utility Functions for Operating on READER_BUFFER	    */
/*								    */
/* **************************************************************** */

/* Append STRING to the reader buffer. */
static void
append_string (char *string)
{
  int len = strlen (string);

  if ((buffer_index + 2 + len) < buffer_size)
    reader_buffer = (char *)
      xrealloc (reader_buffer, (buffer_size += (100 + len)));

  strcpy (reader_buffer + buffer_index, string);
  buffer_index += len;
  reader_buffer[buffer_index] = '\0';
}

/* Append CHARACTER to the reader buffer. */
static void
append_character (int character)
{
  if ((buffer_index + 2) > buffer_size)
    reader_buffer = (char *)
      xrealloc (reader_buffer, (buffer_size += 100));

  reader_buffer[buffer_index++] = character;
  reader_buffer[buffer_index] = '\0';
}

/* Return the contents of the reader buffer, perhaps appending a newline
   if the caller requested it, and if FOUND_NEWLINE is non-zero. */
static char *
return_reader_buffer (int found_newline)
{
  /* Fix up the buffer if possible. */
  if (found_newline && buffer_index && leave_newline &&
      (reader_buffer[buffer_index - 1] != '\n'))
    append_character ('\n');

  if (copy_text)
    return (strdup (reader_buffer));
  else
    return (reader_buffer);
}

static void
initialize_reader_buffer (void)
{
  /* Force our reader buffer to point to something, and initialize the
     buffer to an empty string. */
  buffer_index = 0;
  append_character ('\n');	/* Force the existance of the buffer. */
  reader_buffer[0] = '\0';
  buffer_index = 0;
}

/* Skip whitespace if the caller requested it, but at any rate, return the
   next character to read. */
static int
reader_skip_whitespace (FILE *stream)
{
  int character;

  if (ignore_leading)
    {
      while ((character = read_next_char (stream)) != 0)
	if (!whitespace (character))
	  break;
    }
  else
    character = read_next_char (stream);

  return (character);
}

/* Remove any whitespace appearing at the end of READER_BUFFER.  Reset
   BUFFER_INDEX to point to the new end of the string. */
static void
reader_skip_trailing (FILE *stream)
{
  register int i;

  for (i = buffer_index - 1; i != -1; i--)
    {
      if ((!leave_newline) && (reader_buffer[i] == '\n'))
	continue;

      if (!whitespace (reader_buffer[i]))
	break;
    }

  buffer_index = i + 1;

  if (reader_buffer)
    reader_buffer[buffer_index] = '\0';
}

/* Skip the comment that we are looking at if the caller requested it.
   Return non-zero if we skipped a comment, or zero otherwise.  If the
   comment is skipped, the read pointer is right at the character
   following the matched end delimiter. */
static int
reader_skip_comment (FILE *stream)
{
  register int i, len;
  int character;

  if (!skip_comments || !COMMENT_BEG)
    return (0);

  len = strlen (COMMENT_BEG);

  /* Handle pathalogical case of "" as the comment starter. */
  if (!len)
    return (0);

  /* Get a character, and check it out.  Hack easiest case first. */
  character = read_next_char (stream);

  if (character == '\n')
    newlines_found++;

  if (character != COMMENT_BEG[0])
    {
      unread_this_char (character, stream);

      if (character == '\n')
	newlines_found--;

      return (0);
    }

  /* First character matches.  Check the rest. */
  for (i = 1; i < len; i++)
    {
      character = read_next_char (stream);

      if (character == '\n')
	newlines_found++;

      if (character != COMMENT_BEG[i])
	{
	  put_back_with_char (COMMENT_BEG, i, character, stream);

	  if (character == '\n')
	    newlines_found--;

	  return (0);
	}
    }

  /* We found a comment starter.  Ignore all text until the comment end. */
  if (COMMENT_END)
    len = strlen (COMMENT_END);
  else
    len = 0;

  /* Keep reading until we find the string that matches the comment end. */
  i = 0;

  /* Note that a length of 0 indicates that the comment ends just before
     the newline; i.e., the newline can still be read, and is not part of
     the comment.  If the caller wants a different behaviour, she should
     set comment_end to "\n". */
  while ((character = read_next_char (stream)) != 0)
    {
      if (character == EOF)
	return (1);

      if (character == '\n' && len == 0)
	{
	  unread_this_char (character, stream);
	  return (1);
	}

      /* If we handle nested comments, call this code to read the comment
	 inside of this one. */
      if ((nested_comments) && (character == COMMENT_BEG[0]))
	{
	  int found;
	  unread_this_char (character, stream);
	  found = reader_skip_comment (stream);
	  if (found)
	    {
	      i = 0;
	      continue;
	    }
	  else
	    character = read_next_char (stream);
	}

      if (character == '\n')
	newlines_found--;

      if (len > 0)
	{
	  if (COMMENT_END[i] == character)
	    {
	      i++;
	      if (i == len)
		return (1);
	    }
	  else if (i)
	    {
	      /* If they don't match, restart the search, backtracking. */
	      put_back_with_char (COMMENT_END, i - 1, character, stream);
	      if (character == '\n')
		newlines_found--;
	      i = 0;
	    }
	}
    }
  /* NOT REACHED. */
  return (0);
}

/* **************************************************************** */
/*								    */
/*		 User Visible Functions for Getting Input	    */
/*								    */
/* **************************************************************** */

/* Function which returns the number of newlines encountered during the
   last call to reader_xxx () */
static int
reader_recent_newlines_count (void)
{
  return (newlines_found);
}

/* Return a pointer to a line of text.  The line may only be valid until
   the next call to reader_xxx () unless reader_COPY_TEXT is set in the
   behaviour flags. */
static char *
reader_get_line (FILE *stream)
{
  int character, done_reading;

  /* Make sure we have a place to read the line into. */
  initialize_reader_buffer ();
  newlines_found = 0;

  /* We haven't started yet, let alone finished. */
  done_reading = 0;

  while (!done_reading)
    {
      /* Get at least one character from the input stream.  Perhaps skip
	 whitespace if the caller requests it. */
      if (ignore_leading)
	{
	  character = reader_skip_whitespace (stream);
	  unread_this_char (character, stream);
	}

      /* Read text into the buffer, being careful to skip comments as we
	 read.  Return when we find a newline. */
      while ((character = read_next_char (stream)) != 0)
	{
	  /* If this character is the EOF character, then return the line
	     as is, or return an empty line. */
	  if (character == EOF)
	    {
	      if (ignore_trailing)
		reader_skip_trailing (stream);

	      if (buffer_index)
		return (return_reader_buffer (0));
	      else
		return ((char *)NULL);
	    }

	  /* Handle newline. */
	  if (character == '\n')
	    {
	      newlines_found++;
	      if (ignore_trailing)
		reader_skip_trailing (stream);

	      if (ignore_empty && !buffer_index)
		{
		  /* Break out of this loop and skip leading whitespace. */
		  break;
		}
	      else
		return (return_reader_buffer (1));
	    }

	  /* Handle potential comments. */
	  if (skip_comments && COMMENT_BEG &&
	      (character == COMMENT_BEG[0]))
	    {
	      unread_this_char (character, stream);
	      if (reader_skip_comment (stream))
		continue;
	      character = read_next_char (stream);
	    }

	  /* Check for escaped characters.  This just automagically adds
	     the following character to the buffer, irregardless of what
	     it might be, excepting EOF.   A backslash/newline pair is
	     invisible. */
	  if (character == '\\')
	    {
	      character = read_next_char (stream);

	      /* Once again, handle EOF. */
	      if (character == EOF)
		{
		  if (buffer_index)
		    return (return_reader_buffer (0));
		  else
		    return ((char *)NULL);
		}

	      /* Escaped newline just disappears. */
	      if (character == '\n')
		{
		  newlines_found++;
		  continue;
		}

	      /* Anything else is added... */
	    }

	  append_character (character);
	}
    }
  /* NOT REACHED */
  return ((char *)NULL);
}

/* Read a single token from STREAM, and return a pointer to that token.
   Return a NULL pointer if there are tokens left in STREAM.  The pointer
   may only be valid until the next call to reader_xxx () unless
   reader_COPY_TEXT is set in the behaviour flags. */
static char *
reader_get_token (FILE *stream)
{
  register int i, c;
  int character;
  char *token_delimiters;
  
  /* Make sure we have a place to read the line into. */
  initialize_reader_buffer ();

  newlines_found = 0;

  /* Get default values for token delimiters, if none given. */
  token_delimiters = reader_behaviour.token_delimiters;

  if (!token_delimiters)
    token_delimiters = "\n\r\t\f ";

  /* We always skip leading whitespace when reading tokens. */
  {
    int saved_flags = reader_behaviour.reader_flags;

    reader_behaviour.reader_flags |= reader_IGNORE_LEADING;
    while ((character = reader_skip_whitespace (stream)) == '\n');
    unread_this_char (character, stream);
    reader_behaviour.reader_flags = saved_flags;
  }

  /* Read text into the buffer, being careful to skip comments as we
     read.  Return when we find the end of a token. */
  while ((character = read_next_char (stream)) != 0)
    {
      /* If this character is the EOF character, then return the token
	 as is, or return an empty token. */
      if (character == EOF)
	{
	  if (ignore_trailing)
	    reader_skip_trailing (stream);

	  if (buffer_index)
	    return (return_reader_buffer (0));
	  else
	    return ((char *)NULL);
	}

      if (character == '\n')
	newlines_found++;

      /* Is this character a self-delimiting one? */
      if (reader_behaviour.self_delimiting)
	{
	  for (i = 0; (c = reader_behaviour.self_delimiting[i]) != 0; i++)
	    if (character == c)
	      {
		/* Okay, this character is self-delimiting.  If there
		   is any text in the reader buffer, unread this character
		   so that the next call will see it.  Otherwise, the
		   result is this character. */
		if (buffer_index)
		  unread_this_char (character, stream);
		else
		  {
		    append_character (character);
		    if (ignore_trailing)
		      reader_skip_trailing (stream);
		  }
		return (return_reader_buffer (0));
	      }
	}

      /* Is this character a token delimiter? */
      for (i = 0; (c = token_delimiters[i]) != '\0'; i++)
	{
	  if (character == c)
	    {
	      /* If the caller wants to skip all of the token delimiters
		 that appear following this one, then do so now. */
	      if (skip_token_delim)
		{
		  int is_delim = 0;

		  while ((character = read_next_char (stream)) != 0)
		    {
		      if (character == '\n')
			newlines_found++;

		      for (i = 0; (c = token_delimiters[i]) != '\0'; i++)
			{
			  if (character == c)
			    {
			      is_delim = 1;
			      break;
			    }
			}

		      if (!is_delim)
			break;
		    }
		  unread_this_char (character, stream);
		  if (character == '\n')
		    newlines_found--;
		}
	      return (return_reader_buffer (0));
	    }
	}

      /* Not a self-delimiting character, and not a token delimiter.
	 Might this be a comment? */
      if (skip_comments && COMMENT_BEG &&
	  (character == COMMENT_BEG[0]))
	{
	  unread_this_char (character, stream);
	  if (reader_skip_comment (stream))
	    {
	      /* We have just skipped a comment.  If we have nothing in
		 the reader buffer, then skip the following whitespace,
		 since the only thing we have seen so far is the comment. */
	      if (!buffer_index)
		{
		  int saved_flags = reader_behaviour.reader_flags;

		  reader_behaviour.reader_flags |= reader_IGNORE_LEADING;
		  while
		    ((character = reader_skip_whitespace (stream)) == '\n');
		  unread_this_char (character, stream);
		  reader_behaviour.reader_flags = saved_flags;
		}
	      continue;
	    }
	  character = read_next_char (stream);
	}

      /* Check for escaped characters.  This just automagically adds
	 the following character to the buffer, irregardless of what
	 it might be, excepting EOF.   A backslash/newline pair is
	 invisible. */
      if (character == '\\')
	{
	  character = read_next_char (stream);

	  /* Once again, handle EOF. */
	  if (character == EOF)
	    {
	      if (buffer_index)
		return (return_reader_buffer (0));
	      else
		return ((char *)NULL);
	    }

	  /* Escaped newline just disappears. */
	  if (character == '\n')
	    {
	      newlines_found++;
	      continue;
	    }

	  /* Anything else is added... */
	}

      append_character (character);
    }
  /* NOT REACHED */
  return ((char *)NULL);
}

/* Read an expression from STREAM, delimited by OPEN_, and CLOSE_TOKEN.
   If the first token read doesn't match OPEN_TOKEN, then leave that token
   to be read, and return a NULL_POINTER.  Otherwise, return the entire
   expression, including any nested expressions found within.  The value
   is built up of the separate tokens which we read while searching for
   CLOSE_TOKEN, and are separated by a SPC character if that character
   appears within token_delimiters, or if there are no token delimiters.
   In the case that there are token delimiters, but none of them are a
   SPC character, the tokens are separated with the first character of
   token_delimiters. */
static char *
reader_get_delimited_expr (FILE *stream, char *open_token, char *close_token)
{
  register int i, separator;
  char *token, **tokens;
  int tokens_size, tokens_index;
  int saved_flags = reader_behaviour.reader_flags;
  int delimited_depth = 0;

  tokens = (char **)NULL;
  tokens_size = tokens_index = 0;

  /* If the caller doesn't specify both open and close token strings,
     avoid the infinite loop, and make them lose immediately. */
  if (!open_token || !close_token)
    return ((char *)NULL);

  /* Don't make multiple copies of the intermediate tokens. */
  reader_behaviour.reader_flags &= ~reader_COPY_TEXT;

  token = reader_get_token (stream);

  if (!token || (strcmp (token, open_token) != 0))
    {
      if (token)
	reader_push_string (token, stream);
      return ((char *)NULL);
    }

  delimited_depth = 1;

  /* Read subsequent tokens, remembering each one read, until we read a
     token which matches CLOSE_TOKEN, or until we find EOF. */
  while ((token = reader_get_token (stream)) != (char *)NULL)
    {
      /* If this is an open token, return it with our list, but keep
	 track of what level we are at. */
      if (strcmp (token, open_token) == 0)
	delimited_depth++;
      else if (strcmp (token, close_token) == 0)
	delimited_depth--;

      /* If we just read the matching close token, time to stop reading. */
      if (!delimited_depth)
	break;

      /* Otherwise, add this token to our list of tokens. */
      if ((tokens_index + 2) > tokens_size)
	tokens = (char **)
	  xrealloc (tokens, (tokens_size += 20) * sizeof (char *));

      tokens[tokens_index++] = strdup (token);
      tokens[tokens_index] = (char *)NULL;
    }

  /* We have the list of tokens, so time to return them.  First, restore
     the behaviour to what the caller specified. */
  reader_behaviour.reader_flags = saved_flags;

  /* If we didn't read any tokens at all (perhaps at EOF), then return
     an empty string. */
  if (!tokens)
    return ((char *)NULL);

  /* Build a string containing all of the tokens just read. */

  /* We will build the string in the reader buffer. */
  initialize_reader_buffer ();

  /* Determine what the separator character should be.  Try hard to use SPC,
     if that is reasonable, otherwise, the first of the token_delimiters. */
  separator = ' ';
  if (reader_behaviour.token_delimiters)
    {
      for (i = strlen (reader_behaviour.token_delimiters);
	   (separator = reader_behaviour.token_delimiters[i]) != '\0';
	   i++)
	if (separator == ' ')
	  break;
    }

  /* Build the return string. */
  for (i = 0; i < tokens_index; i++)
    {
      append_string (tokens[i]);
      append_character (separator);
      free (tokens[i]);
    }

  return (return_reader_buffer (0));
}

#if defined (TEST)

char *line;

main (argc, argv)
     int argc;
     char **argv;
{
  Reader_Behaviour behave;
  char *line;

#if defined (TEST_GET_LINE)
  behave.comment_beg = "hello";
  behave.comment_end = "goodbye";
  behave.self_delimiting = (char *)NULL;
  behave.token_delimiters = (char *)NULL;
  behave.reader_flags = reader_DEFAULTS;
  behave.reader_flags |= reader_COPY_TEXT | reader_NESTED_COMMENTS;
  
  reader_set_behaviour (&behave);

  while (line = reader_get_line (stdin))
    printf (line);
#endif

#if defined (TEST_TOKEN_READER)
  /* Test the delimited expression reader. */
  behave.comment_beg = "#";
  behave.comment_end = (char *)NULL;
  behave.self_delimiting = "{}";
  behave.token_delimiters = (char *)NULL;
  behave.reader_flags = reader_DEFAULTS;
  behave.reader_flags &= ~reader_NESTED_COMMENTS;

  reader_set_behaviour (&behave);

  while (line = reader_get_delimited_expr (stdin, "{", "}"))
    printf ("%s\n", line);
#endif /* TEST_TOKEN_READER */
}

#endif /* TEST */
