nifty routine

Jonathan Walther djw@reactor-core.org
Mon, 11 Sep 2000 19:40:45 -0700 (PDT)


I hacked this up today to see if I understood function
pointers, and also because my little CGI library needs
a way to read in data and I didn't want to leave a bunch
of cut and paste droppings all over the code.  So I
invented read_until(), a wrapper to raw read() which allows
almost unlimited pushback of the IO stream.

To use it, you just specify your own condition() function which
is called after every character is read in, and decide whether
you've read in enough for one chunk, or that you are finished,
or that there is an error in the stream and to abort.  If invalid
input is in the stream, it is flushed with extreme prejudice.

I hope you enjoy it as much as I do.

----------- snip -----------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
	char           *data;
	int             len;
}               read_until_t;

enum {
	CONTINUE, ERROR, DONE
};

read_until_t   *read_until(int, char*, size_t, int (*) (char *, int));

/* The variable "stack" and information about its size
 * solve the lookahead problem.  If your condition() function
 * requires characters to know when to terminate that you can't
 * process now, but want to process at a later date, stack allows
 * it to eat all it wants.  At the end of this function, pass in
 * the pointer to the returned string at the point where you
 * want the pushback to start, and its size.
 *
 * Consider the following example: in RFC 822 format headers,
 *  a header may be split across lines, if after each line break
 *  it starts the next line with some whitespace.
 * To read in a full header line requires not only detecting the
 * new line, but making sure the first character of the next line
 * isn't a space.
 *
 * If the first character of the next line is NOT a space, you
 * need to push it back to be read again.  with read() you cannot
 * do this.  With read_until() you might see the following code:
 *
 * while ((r = read_until(stdin, NULL, 0, end_of_rfc822_header)) != NULL)
 *   ...do stuff with r; if r ends in "\n\n", thats the end of the headers...
 */
read_until_t   *
read_until(int fd, char* stack, size_t stack_size, int (*condition) (char *, int))
{
	static char    *accumulator;	/* accumulator */
	static int      size, i, c, r;
	read_until_t   *result = (read_until_t *) malloc(sizeof(read_until_t));

	if ((accumulator = realloc(NULL, 128 > stack_size ? 128:stack_size+128)) == NULL)
		return NULL;

	size = 128 > stack_size ? 128 : stack_size + 128;

	if (memcpy(accumulator, stack, stack_size) == -1) {
		free(accumulator);
		return NULL;
	}

	for (i = 0; (c = condition(accumulator, i)) == CONTINUE; i++) {
		if (i == size && (accumulator = realloc(accumulator, size += 128)) == NULL) {
			return NULL;
		}
		if ((r = read(fd, accumulator + i, 1)) == -1) {
			free(accumulator);
			return NULL;
		} else if (r == 0)
			i--;
	}

	if (c == ERROR) {
		free(accumulator);
		return NULL;
	}
	result->data = accumulator;
	result->len = i;
	return result;
}

/*
int             d(int);
int             f(int (*) (int));

int
main(void)
{
	f(d);
}

int
d(int x)
{
	return 2 * x;
}

int
f(int (*x) (int))
{
	int             i;
	for (i = 0; i < 10; i++)
		printf("%d\n", x(i));
	return 0;
}
*/