/*
 * Routines to implement a C preprocessor
 */
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include "ansi.h"
#include "files.h"
#include "hash.h"
#include "host.h"
#include "buffer.h"
#include "cpp.h"
#include "cpp_hide.h"
#include "cpp_eval.h"
#include "allocate.h"
#include "config.h"

extern int translate_comments;

#undef NULL
#define NULL			0L

#ifndef MAX_SEARCH
#define MAX_SEARCH		32
#endif

static cpp_file_t *open_files;
static scan_position_t	*curpos;

unsigned char cpp_char_class[128];
static char class_initialized;

#define add(buf,c)				buf_add(buf, (int)(c))

#define UNHANDLED()				unhandled(__FILE__, __LINE__)

static buffer_t cppbuf;
static buffer_initialized;

static char *search_paths[MAX_SEARCH];
static int search_index;

/*
 * The following vars control the state of conditionals
 */
static cpp_control_state_t control_state;

#define scanning()			(control_state.cur_scope == control_state.gen_scope)
#define skipping()			(control_state.cur_scope != control_state.gen_scope)
#define parsing()			(control_state._parsing)

#define want_comments(x)	((translate_comments) ? x : NULL)

static char *orig_file;
static char *last_file;
static int last_line;

#define FN					fname()
#define LN					fline()

static int grok_actual_param _ANSI_PROTO_((int));
static int scan_default _ANSI_PROTO_((buffer_t*,int));

static char*
fname()
{
	if (curpos == NULL) {
		assert(last_file != NULL);
		return last_file;
	}
	if (curpos->scan_pos == 0) return NULL;
	return file_name(curpos->scan_pos);
}

static int
fline()
{
	if (curpos == NULL) {
		assert(last_line != 0);
		return last_line;
	}
	return line_number(curpos->scan_pos);
}

static void
range_check(n, max, msg)
	int n, max;
	char *msg;
{
	if (n >= max) {
		fatal(FN, LN, "%s: %d > %d", msg, n+1, max);
	}
}

static void
unhandled(file, line)
	char *file;
	int line;
{
	fatal(FN, LN, "Unhandled case %s:%d", file, line);
}

static void
bad_directive()
{
	if (scanning()) {
		error(FN, LN, "Undefined preprocessor directive");
	}
}

static void
bad_include()
{
	error(FN,LN,"Malformed include directive");
}

static void
unexpected(msg)
	char *msg;
{
	error(FN, LN, "Unexpected %s", msg);
}

static void
unexpected_eof(msg)
	char *msg;
{
	char buf[128];
	sprintf(buf, "end of file %s", msg);
	unexpected(buf);
}

static void
unexpected_eol(msg)
	char *msg;
{
	char buf[128];
	sprintf(buf, "end of line %s", msg);
	unexpected(buf);
}

static char*
charstr(c)
	int c;
{
	if (is_eof(c))		return "end of file";
	if (is_eol(c))		return "end of line";
	if (is_alpha(c))	return "identifier";
	if (is_digit(c))	return "number";
	if (is_white(c))	return "white space";
	if (is_punct(c))	return "puctuation";
	return "crap";
}

static void
expected(msg, c)
	char *msg;
	int c;
{
	error(FN, LN, "Expected %s got %s", msg, charstr(c));
}

static int
levels_nested()
{
	scan_position_t *p;
	int level = 0;

	for (p = curpos; p; p = p->scan_next) {
		if (p->scan_kind == scan_file) {
			level++;
		}
	}

	return level;
}

static void
add_position_directive(buf, new_line)
	buffer_t *buf;
	int new_line;
{
	char b[40];

	sprintf(b, "\n# %d \"", LN);
	buf_add_str(buf, b);
	buf_add_str(buf, FN);
	sprintf(b, "\" %d", levels_nested());
	buf_add_str(buf, b);
	if (new_line) {
		add(buf, '\n');
	}
}

static void
init_char_class()
{
	int i;

	if (class_initialized) return;

	cpp_char_class[0] = END_INPUT;
	cpp_char_class[PARAM_START] = PARAM_START;
	cpp_char_class['#'] = MSTART;
	cpp_char_class['_'] = ALPHA;
	cpp_char_class['$'] = ALPHA;

	for (i = 'g'; i <= 'z'; i++) {
		cpp_char_class[i] = ALPHA;
	}

	for (i = 'G'; i <= 'Z'; i++) {
		cpp_char_class[i] = ALPHA;
	}

	for (i = 'A'; i <= 'F'; i++) {
		cpp_char_class[i] = ALPHA | XDIGIT;
	}

	for (i = 'a'; i <= 'f'; i++) {
		cpp_char_class[i] = ALPHA | XDIGIT;
	}

	for (i = '0'; i <= '7'; i++) {
		cpp_char_class[i] = DIGIT | XDIGIT;
	}

	cpp_char_class['8'] = DIGIT | XDIGIT;
	cpp_char_class['9'] = DIGIT | XDIGIT;

	cpp_char_class[' '] = WHITE;
	cpp_char_class[''] = WHITE;
	cpp_char_class['\t'] = WHITE;
	cpp_char_class['\n'] = END_OF_LINE;

	class_initialized = 1;
}

static void
init_cppbuf()
{
	if (buffer_initialized) {
		return;
	}
	buf_init(&cppbuf);
}

static void
rm_file_from_list(f)
	cpp_file_t *f;
{
	cpp_file_t *p, *last;

	for (p = open_files, last = NULL; p; p = p->next_cpp_file) {
		if (p == f) break;
		last = p;
	}
	assert(p == f);
	if (last == NULL) {
		open_files = f->next_cpp_file;
	}
	else {
		last->next_cpp_file = f->next_cpp_file;
	}
}

static cpp_file_t*
find_open_file(path)
	char *path;
{
	cpp_file_t *f;

	for (f = open_files; f; f = f->next_cpp_file) {
		assert(f->next_cpp_file != f);
		if (! strcmp(f->path, path)) {
			return f;
		}
	}

	return NULL;
}

void
cpp_search_path(path)
	char *path;
{
	buffer_t buf;

	if (search_index >= MAX_SEARCH) {
		fatal(__FILE__,__LINE__,"Too many search paths added");
	}

	buf_init(&buf);
	buf_add_str(&buf, path);
	for (; *path && path[1]; path++);
	if (*path != '/') {
		add(&buf, '/');
	}

	search_paths[search_index++] = buf_get_str(&buf);
}

static cpp_file_t*
attempt_open(path)
	char *path;
{
	cpp_file_t *f;
	char *mapaddr;
	size_t fsize;
	int fd;

	f = find_open_file(path);

	if (f != NULL) {
		f->reference_count++;
		return f;
	}

	if ((fd = open(path, O_RDONLY)) == -1) {
		/* Can't access for reading */
		return NULL;
	}

	if ((fsize = sizeof_file(fd)) == -1) {
		close(fd);
		return NULL;
	}

	mapaddr = (char*) map_file(fd, fsize);

	if (mapaddr == (char*)-1) {
		/* failed mmap */
		close(fd);
		return NULL;
	}

	f = (cpp_file_t*) allocate(sizeof(cpp_file_t));
	f->path = new_string(path);
	f->fd = fd;
	f->text = mapaddr;
	f->file_size = fsize;
	f->reference_count = 1;
	assert(open_files != f);
	f->next_cpp_file = open_files;
	open_files = f;

	return f;
}

static void
push_file(buf, f)
	buffer_t *buf;
	cpp_file_t *f;
{
	scan_position_t *pos;

	pos = (scan_position_t*) allocate(sizeof(scan_position_t));
	pos->scan_kind = scan_file;
	pos->scan.file = f;
	pos->scan_index = 0;
	pos->scan_pos = add_file(f->path);
	pos->scan_next = curpos;
	curpos = pos;

	add_position_directive(buf,1);
}

int
cpp_open(path)
	char *path;
{
	cpp_file_t *f;

	init_char_class();
	init_cppbuf();

	control_state.skip_else = 0;
	control_state.cur_scope = 0;
	control_state.gen_scope = 0;
	control_state._parsing = 0;

	f = attempt_open(path);

	if (f == NULL) {
		return 1;
	}

	orig_file = new_string(path);
	push_file(&cppbuf,f);

	return 0;
}

void
cpp_cleanup()
{
	cpp_file_t *f, *next;

	curpos = NULL;

	for (f = open_files; f; f = next) {
		next = f->next_cpp_file;
		unmap_file(f->text, f->file_size);
		close(f->fd);
		deallocate(f);
	}

	open_files = NULL;

	buf_destroy(&cppbuf);
}

void
cpp_set_state(newpos, new_state, savepos, save_state)
	scan_position_t *newpos, **savepos;
	cpp_control_state_t *new_state, *save_state;
{
	*savepos = curpos;
	*save_state = control_state;
	control_state = *new_state;
	curpos = newpos;
}

static void
kill_file(f)
	cpp_file_t *f;
{
	unmap_file(f->text, f->file_size);
	close(f->fd);
	rm_file_from_list(f);
	deallocate(f);
}

static void
finished_with(s)
	scan_position_t *s;
{
	cpp_file_t *f;

	last_file = file_name(s->scan_pos);
	last_line = line_number(s->scan_pos);

	switch (s->scan_kind) {
	  case scan_file:
		f = curpos->scan.file;
		deallocate(s);
		f->reference_count--;
		if (f->reference_count == 0) {
			kill_file(f);
		}
		break;
	  case scan_macro_expansion:
	  case scan_text:
		deallocate(s);
		break;
	  default:
		assert(0);
		break;
	}
}

static void
unget_char()
{
	if (curpos == NULL) return;
	assert(curpos->scan_index > 0);
	curpos->scan_index--;
}

static int
next_char()
{
	register scan_position_t *cp = curpos; /* Get global curpos into a reg */
	cpp_file_t *f;
	macro_t *m;
	scan_position_t *next;
	scan_kind_t kind;
	int result;

  top:
	if (cp == NULL) {
		return 0;
	}

	kind = cp->scan_kind;

	if (kind == scan_file) {
		f = cp->scan.file;
		if (cp->scan_index >= f->file_size) {
			next = cp->scan_next;
			finished_with(cp);
			cp = curpos = next;
			goto top;
		}
		result = f->text[cp->scan_index++];
	}
	else if (kind == scan_macro_expansion) {
		m = cp->scan.expansion.expand_macro;
		if (cp->scan_index >= m->macro_body_len) {
			next = cp->scan_next;
			finished_with(cp);
			cp = curpos = next;
			goto top;
		}
		result = m->macro_body[cp->scan_index++];
	}
	else {
		assert(kind == scan_text);
		result = cp->scan.text[cp->scan_index++];
		if (result == 0) {
			next = cp->scan_next;
			finished_with(cp);
			cp = curpos = next;
			goto top;
		}
	}

	return result;
}

static void
incline(sync)
	int sync;
{
	if (sync) {
		control_state.position++;
	}
	else {
		/* force file position directive to be added */
		control_state.position = 0;
	}
	switch (curpos->scan_kind) {
	  case scan_file:
		curpos->scan_pos++;
		break;
	  default:
		break;
	}
}

static void
check_position(buf)
	buffer_t *buf;
{
	if (parsing()) return;
	switch (curpos->scan_kind) {
	  case scan_file:
		if (control_state.position != curpos->scan_pos) {
			control_state.position = curpos->scan_pos;
			add_position_directive(buf, 0);
		}
		break;
	  default:
		assert(0);
		break;
	}
}

static void
comment_start(buf)
	buffer_t *buf;
{
	if (translate_comments && buf != NULL) {
		add(buf, '/');
		add(buf, '*');
	}
}

static void
cpp_comment_start(buf)
	buffer_t *buf;
{
	if (translate_comments && buf != NULL) {
		add(buf, '/');
		add(buf, '/');
	}
}

static int
scan_white(buf, c)
	buffer_t *buf;
	int c;
{
	while (is_white(c)) {
		if (buf != NULL) {
			add(buf, c);
		}
		c = next_char();
	}
	return c;
}

static int
skip_white(c)
	int c;
{
	return scan_white(NULL, c);
}

static int
skip_c_comment(buf, c)
	buffer_t *buf;
	int c;
{
	c = next_char();

	for (;;) {
		switch (classof(c)) {
		  case END_INPUT:
			return c;
		  case END_OF_LINE:
			incline(0);
			add(buf,c);
			c = next_char();
			break;
		  case PUNCT:
			switch (c) {
			  case '*':
				add(buf,'*');
				c = next_char();
				if (c == '/') {
					add(buf,'/');
					return next_char();
				}
				break;
			  case '\\':
				add(buf,c);
				c = next_char();
				add(buf,c);
				if (is_eol(c)) {
					incline(0);
				}
				else if (is_eof(c)) {
					return c;
				}
				c = next_char();
				break;
			  default:
				add(buf,c);
				c = next_char();
				break;
			}
			break;
		  default:
			c = scan_default(buf, c);
			break;
		}
	}
}

static int
skip_cpp_comment(buf, c)
  buffer_t *buf;
  int c;
{
  buffer_t lbuf;
  c = next_char();

  for (;;) {
      switch (classof(c)) {
        case END_INPUT:
          return c;
        case DIGIT | XDIGIT:
        case ALPHA:
        case ALPHA | XDIGIT:
        case WHITE:
        case MSTART:
        case PARAM_START:
        case PUNCT:
          add(buf,c);
          c = next_char();
          break;
        case END_OF_LINE:
          add(buf,c);
          /* incline(0); */
          /* c = next_char(); */
          return c;
        default:
          UNHANDLED();
          break;
      }
  }
}

static int
scan_to_end(buf, c)
	buffer_t *buf;
	int c;
{
	while (! (is_eol(c) || is_eof(c))) {
		switch (c) {
		  case '/':
			c = next_char();
			if (c == '*') {
				comment_start(buf);
				c = skip_c_comment(want_comments(buf), c);
			}
			else if (c == '/') {
				cpp_comment_start(buf);
				c = skip_cpp_comment(want_comments(buf), c);
			}
			else {
				add(buf,'/');
			}
			break;
		  case '\\':
			c = next_char();
			if (is_eol(c)) {
				incline(0);
				c = next_char();
			}
			else if (is_eof(c)) {
				if (buf != NULL) {
					add(buf, '\\');
				}
				return c;
			}
			else if (c == '\\') {
				if (buf != NULL) {
					add(buf, '\\');
					add(buf, '\\');
				}
				c = next_char();
			}
			else if (buf != NULL) {
				add(buf, '\\');
			}
			break;
		  default:
			if (buf != NULL) {
				add(buf, c);
			}
			c = next_char();
			break;
		}
	}

	return c;
}

static int
skip_to_end(c)
	int c;
{
	return scan_to_end(NULL, c);
}

static int
finish_num(buf, c, mask)
	buffer_t *buf;
	int c, mask;
{
	while (! is_eof(c)) {
		if ((cpp_char_class[c] & mask) == 0) break;
		add(buf, c);
		c = next_char();
 	}

	return c;
}

static int
maybe_magnitude(buf, c)
	buffer_t *buf;
	int c;
{
	if (c == 'e' || c == 'E') {
		add(buf, c);
		c = next_char();
		if (c == '+' || c == '-') {
			add(buf, c);
			c = next_char();
		}
		c = finish_num(buf, c, DIGIT);
	}

	return c;
}

static int
scan_number(buf, c)
	buffer_t *buf;
	int c;
{
	add(buf, c);

	if (c == '0') {
		c = next_char();
		if (c == 'x' || c == 'X') {
			add(buf, c);
			c = finish_num(buf, next_char(), XDIGIT);
		}
		else {
			c = finish_num(buf, c, DIGIT);
		}
	}
	else {
		c = finish_num(buf, next_char(), DIGIT);
	}

	if (int_modifier(c)) {
		do {
			add(buf, c);
			c = next_char();
		} while (int_modifier(c));
		return c;
	}

	if (c == '.') {
		add(buf, c);
		c = finish_num(buf, next_char(), DIGIT);
		c = maybe_magnitude(buf, c);
		if (float_modifier(c)) {
			add(buf, c);
			c = next_char();
		}
		return c;
	}

	c = maybe_magnitude(buf, c);

	if (int_modifier(c)) {
		do {
			add(buf, c);
			c = next_char();
		} while (int_modifier(c));
	}

	return c;
}

static int
scan_ident(buf, c)
	buffer_t *buf;
	int c;
{
	for (;;) {
		add(buf, c);
		c = next_char();
		if (!is_alpha_numeric(c)) break;
	}

	return c;
}

static int
scan_to_del(buf, c, del)
	buffer_t *buf;
	int c, del;
{
	for (;;) {
		switch (classof(c)) {
		  case END_INPUT:
		  case END_OF_LINE:
			return c;
		  case PUNCT:
			switch (c) {
			  case '\\':
				c = next_char();
				if (is_eol(c)) {
					incline(0);
					c = next_char();
				}
				else if (c == '\'' || c == '"' || c == '\\') {
					add(buf, '\\');
					add(buf, c);
					c = next_char();
				}
				else {
					add(buf, '\\');
				}
				break;
			  default:
				if (c == del) {
					return c;
				}
				add(buf, c);
				c = next_char();
				break;
			}
			break;
		  default:
			c = scan_default(buf,c);
			break;
		}
	}
}

static int
scan_string(buf, c)
	buffer_t *buf;
	int c;
{
	c = scan_to_del(buf, c, '"');
	if (c != '"') {
		expected("'\"'", c);
	}
	else {
		add(buf, c);
		return next_char();
	}
}

static int
scan_char_const(buf, c)
	buffer_t *buf;
	int c;
{
	c = scan_to_del(buf, c, '\'');
	if (c != '\'') {
		expected("single quote", c);
	}
	else {
		add(buf, c);
		return next_char();
	}
}

static int
grok_formals(formals, nformals, buf, c)
	char ***formals;
	int *nformals;
	buffer_t *buf;
	int c;
{
	char *f[256];
	char **fp;
	int nf = 0;
	int i;

	for (;;) {
		c = skip_white(c);
		if (! is_alpha(c)) break;
		c = scan_ident(buf, c);
		range_check(nf, 256, "Too many formal parameters");
		f[nf++] = buf_get_str(buf);
		c = skip_white(c);
		if (c != ',') break;
		c = next_char();
	}

	*nformals = nf;

	if (nf == 0) {
		*formals = NULL;
	}
	else {
		fp = (char**) malloc(sizeof(char*) * nf);
		for (i = 0; i < nf; i++) {
			fp[i] = f[i];
		}
		*formals = fp;
	}

	return c;
}

static char*
local_copy(buf, str, size)
	buffer_t *buf;
	char *str;
	int size;
{
	int len = buf_count(buf);

	if (len < size) {
		buf_move_to(buf, str);
		return str;
	}

	return buf_get_str(buf);
}

static void
grok_param(outbuf, idbuf, formals, nformals, xpect)
	buffer_t *outbuf, *idbuf;
	char *formals[];
	int nformals, xpect;
{
	char ident[128];
	char *name;
	int i;

	name = local_copy(idbuf, ident, sizeof(ident));

	for (i = 0; i < nformals; i++) {
		if (! strcmp(name, formals[i])) {
			add(outbuf, PARAM_START);
			add(outbuf, i+1);
			goto end_subp;
		}
	}

	if (xpect) {
		error(FN, LN, "Expected macro formal got %s", name);
	}

	buf_add_str(outbuf, name);

  end_subp:
	if (name != ident) {
		free(name);
	}
}

static int
scan_default(buf, c)
	buffer_t *buf;
	int c;
{
	switch (classof(c)) {
	  case END_INPUT:
		return c;
	  case END_OF_LINE:
		incline(buf != NULL);
		add(buf, c);
		c = next_char();
		break;
	  case MSTART:
		add(buf, c);
		c = next_char();
		break;
	  case WHITE:
		add(buf, c);
		return next_char();
	  case ALPHA:
	  case ALPHA | XDIGIT:
		c = scan_ident(buf, c);
		break;
	  case DIGIT | XDIGIT:
		return scan_number(buf, c);
	  case PARAM_START:
		if (buf) {
			c = grok_actual_param(next_char());
		} else {
			c = next_char();
			c = next_char();
		}
		break;
	  case PUNCT:
		switch (c) {
		  case '"':
			add(buf, c);
			c = scan_string(buf, next_char());
			break;
		  case '\'':
			add(buf, c);
			c = scan_char_const(buf, next_char());
			break;
		  case '/':
			c = next_char();
			if (c == '*') {
				comment_start(buf);
				c = skip_c_comment(want_comments(buf), c);
			}
			else if (c == '/') {
				cpp_comment_start(buf);
				c = skip_cpp_comment(want_comments(buf), c);
			}
			else {
				add(buf,'/');
			}
			break;
		  case '\\':
			c = next_char();
			switch (c) {
			  case '\n':
				incline(0);
				c = next_char();
				break;
			  case '\\':
				add(buf, '\\');
				add(buf, '\\');
				c = next_char();
				break;
			  default:
				add(buf, '\\');
				break;
			}
			break;
		  default:
			add(buf, c);
			c = next_char();
			break;
		}
		break;
	  default:
		UNHANDLED();
		break;
	}

	return c;
}

static int
grok_define(buf, c)
	buffer_t *buf;
	int c;
{
	buffer_t lbuf;
	char *macro_name;
	char *macro_body;
	char **formals;
	file_pos_t defpos;
	int nformals;
	int body_len;

	c = skip_white(c);

	if (!is_alpha(c)) {
		expected("idetifier", c);
		return skip_to_end(c);
	}

	defpos = curpos->scan_pos;

	buf_init(buf);
	c = scan_ident(buf, c);
	macro_name = buf_get_str(buf);

	if (c == '(') {
		c = grok_formals(&formals, &nformals, buf, next_char());
		if (c == ')') {
			c = next_char();
		}
		else {
			expected("')'", c);
		}
	}
	else {
		nformals = -1;
	}

	c = skip_white(c);

	if (nformals == -1) {
		buf_init(buf);
		c = scan_to_end(buf, c);
		body_len = buf_count(buf);
		macro_body = buf_get_str(buf);
	}
	else {
		buf_init(buf);

		while (!is_eol(c) && !is_eof(c)) {
			switch (classof(c)) {
			  case MSTART:
				c = next_char();
				if (c == '#') {
					c = next_char();
					break;
				}
				c = skip_white(c);
				if (! is_alpha(c)) {
					expected("macro formal parameter name", c);
				}
				else {
					add(buf, '"');
					buf_init(&lbuf);
					c = scan_ident(&lbuf, c);
					grok_param(buf, &lbuf, formals, nformals, 1);
					add(buf, '"');
				}
				break;
			  case ALPHA:
			  case ALPHA | XDIGIT:
				buf_init(&lbuf);
				c = scan_ident(&lbuf, c);
				grok_param(buf, &lbuf, formals, nformals, 0);
				break;
			  default:
				c = scan_default(buf, c);
				break;
			}
		}

		body_len = buf_count(buf);
		macro_body = buf_get_str(buf);
	}

	macro_def(macro_name, macro_body, body_len, nformals, defpos);
	return c;
}

static int
grok_if(buf, c)
	buffer_t *buf;
	int c;
{
	char ident[256];
	char *name;
	cpp_eval_result_t result;

	assert(control_state.cur_scope >= 0);
	assert(control_state.gen_scope >= 0);

	buf_init(buf);
	c = scan_to_end(buf, c);

	name = local_copy(buf, ident, sizeof(ident));

	result = cpp_eval(name);

	++control_state.cur_scope;
	control_state.skip_else = 0;

	if (EVAL_FAILED(result) || !IS_EVAL_INT(result)) {
		;
	}
	else {
		if (EVAL_INT(result) != 0) {
			++control_state.gen_scope;
		}
	}

  end_subp:
	if (name != ident) {
		free(name);
	}

	return c;
}

static int
grok_elif(buf, c)
	buffer_t *buf;
	int c;
{
	char ident[256];
	char *name;
	cpp_eval_result_t result;

	assert(control_state.cur_scope >= 0);
	assert(control_state.gen_scope >= 0);

	if (control_state.cur_scope == 0) {
		error(FN,LN,"elif directive found without a matching if directive");
		return skip_to_end(c);
	}

	if (control_state.skip_else) {
		return skip_to_end(c);
	}

	if (scanning()) {
		control_state.skip_else = 1;
		control_state.gen_scope--;
		return skip_to_end(c);
	}

	if (control_state.gen_scope != control_state.cur_scope - 1) {
		return skip_to_end(c);
	}

	buf_init(buf);
	c = scan_to_end(buf, c);

	name = local_copy(buf, ident, sizeof(ident));

	result = cpp_eval(name);

/*
	++control_state.cur_scope;
*/
/*
	control_state.skip_else = 0;
*/

	if (EVAL_FAILED(result) || !IS_EVAL_INT(result)) {
		;
	}
	else {
		if (EVAL_INT(result) != 0) {
			++control_state.gen_scope;
		}
	}

  end_subp:
	if (name != ident) {
		free(name);
	}

	return c;
}

static int
grok_ifdef(buf, c, sense)
	buffer_t *buf;
	int c;
	int sense;
{
	char ident[128];
	char *name;
	macro_t *m;

	control_state.skip_else = 0;

	c = skip_white(c);

	if (! is_alpha(c)) {
		expected("identifier", c);
		return skip_to_end(c);
	}

	c = scan_ident(buf, c);
	name = local_copy(buf, ident, sizeof(ident));

	++control_state.cur_scope;

	m = macro_find(name);
	if ((m != NULL && !sense) || (m == NULL && sense)) {
		++control_state.gen_scope;
	}

	if (name != ident) {
		free(name);
	}

	return skip_to_end(c);
}

static int
grok_else(buf, c)
	buffer_t *buf;
	int c;
{
	int was_skipping = skipping();

	assert(control_state.cur_scope >= 0);
	assert(control_state.gen_scope >= 0);

	if (control_state.cur_scope == 0) {
		error(FN, LN, "Unmatched else directive");
		return skip_to_end(c);
	}
	else {
		if (! control_state.skip_else) {
			control_state.skip_else = 0;
			if (control_state.gen_scope == control_state.cur_scope) {
				--control_state.gen_scope;
			}
			else if (control_state.gen_scope == control_state.cur_scope -1 ) {
				++control_state.gen_scope;
			}
		}
	}

	if (scanning() && was_skipping) {
		add_position_directive(buf, 0);
	}

	return skip_to_end(c);
}

static int
grok_endif(buf, c)
	buffer_t *buf;
	int c;
{
	int was_skipping = skipping();

	assert(control_state.cur_scope >= 0);
	assert(control_state.gen_scope >= 0);

	if (control_state.cur_scope == 0) {
		error(FN, LN, "Unmatched endif directive");
		return skip_to_end(c);
	}
	else {
		control_state.skip_else = 0;
		if (control_state.gen_scope == control_state.cur_scope) {
			--control_state.gen_scope;
		}
		--control_state.cur_scope;
	}

	if (scanning() && was_skipping) {
		add_position_directive(buf, 0);
	}

	return skip_to_end(c);
}

static void
grok_pathof(buf, path)
	buffer_t *buf;
	char *path;
{
	char *last = NULL;
	char *p;

	for (p = path; *p; p++) {
		if (*p == '/') {
			last = p+1;
		}
	}

	if (last != NULL) {
		for (p = path; p != last; p++) {
			add(buf, *p);
		}
	}
}

static int
search_for_file(buf, lbuf, name, stdinc)
	buffer_t *buf, *lbuf;
	char *name;
	int stdinc;
{
	char *path;
	cpp_file_t *f;
	int i;
	char ident[128];

	if (name[0] == '/') {
		f = attempt_open(name);

		if (f != NULL) {
			push_file(buf, f);
			goto end_of_subp;
		}

		goto failed;
	}

	if (!stdinc) {
		f = attempt_open(name);

		if (f != NULL) {
			push_file(buf, f);
			goto end_of_subp;
		}

		/*
		** #include "file" semantics are to search for "file"
		** in the directory of the original source file.
		*/
		buf_init(lbuf);
		grok_pathof(lbuf, orig_file);

		if (buf_count(lbuf)) {
			buf_add_str(lbuf, name);

			path = local_copy(lbuf, ident, sizeof(ident));

			f = attempt_open(path);

			if (path != ident) {
				free(path);
			}

			if (f != NULL) {
				push_file(buf, f);
				goto end_of_subp;
			}
		}
	}

	for (i = 0; i < search_index; i++) {
		buf_init(lbuf);
		buf_add_str(lbuf, search_paths[i]);
		buf_add_str(lbuf, name);
		
		path = local_copy(lbuf, ident, sizeof(ident));

		f = attempt_open(path);

		if (path != ident) {
			free(path);
		}

		if (f != NULL) {
			push_file(buf,f);
			goto end_of_subp;
		}
	}

	buf_init(lbuf);
	buf_add_str(lbuf, "/usr/include/");
	buf_add_str(lbuf, name);
		
	path = local_copy(lbuf, ident, sizeof(ident));

	f = attempt_open(path);

	if (path != ident) {
		free(path);
	}

	if (f != NULL) {
		push_file(buf,f);
		goto end_of_subp;
	}

  failed:
	error(FN,LN,"Couldn't open %s", name);

  end_of_subp:
	return next_char();
}

static int
grok_include(buf, lbuf, c)
	buffer_t *buf, *lbuf;
	int c;
{
	char ident[64];
	char *name;
	int del;

	del = skip_white(c);

	switch (del) {
	  case '<':
		del = '>';
		/* fall through */
	  case '"':
		buf_init(lbuf);
		c = scan_to_del(lbuf, next_char(), del);
		if (c != del) {
			goto bad_input;
		}
		c = skip_to_end(c);
		unget_char();
		break;
	  default:
	  bad_input:
		bad_include();
		return skip_to_end(c);
	}

	name = local_copy(lbuf, ident, sizeof(ident));

	c = search_for_file(buf, lbuf, name, del == '>');

	if (name != ident) {
		free(name);
	}

	return c;
}

static int
grok_error(buf, c)
	buffer_t *buf;
	int c;
{
	char ident[128];
	char *msg;

	buf_init(buf);
	c = skip_white(c);

	c = scan_to_end(buf, c);
	msg = local_copy(buf, ident, sizeof(ident));

	error(FN,LN,msg);

	if (msg != ident) {
		free(msg);
	}

	return c;
}

static int
grok_undef(buf, c)
	buffer_t *buf;
	int c;
{
	char ident[128];
	char *name;

	c = skip_white(c);

	if (! is_alpha(c)) {
		expected("macro identifier", c);
		return skip_to_end(c);
	}

	buf_init(buf);
	c = scan_ident(buf, c);
	name = local_copy(buf, ident, sizeof(ident));

	macro_undef(name);

	if (name != ident) {
		free(name);
	}

	return skip_to_end(c);
}

static int
scan_directive(buf, c)
	buffer_t *buf;
	int c;
{
#ifdef PUBLIC
	struct resword {char *name; int token;};
	extern struct resword *cpp_rsvd();
	struct resword *r;
#endif
	buffer_t lbuf;
	char ident[32];
	int len;
	int keywd;

	c = skip_white(c);

	if (is_eof(c) || is_eol(c)) {
		return c;
	}

	if (!is_alpha(c)) {
		bad_directive();
		return skip_to_end(c);
	}

	buf_init(&lbuf);

	c = scan_ident(&lbuf, c);
	len = buf_count(&lbuf);

	if (len > sizeof(ident)) {
		buf_destroy(&lbuf);
		bad_directive();
		return skip_to_end(c);
	}

	buf_move_to(&lbuf, ident);
	buf_destroy(&lbuf);

#ifdef PUBLIC
	if ((r = cpp_rsvd(ident, len)) == NULL) {
#else
	if ((keywd = cpp_rsvd(ident)) == -1) {
#endif
		bad_directive();
		return skip_to_end(c);
	}

#ifdef PUBLIC
	keywd = r->token;
#endif

	if (scanning()) {
		switch (keywd) {
		  case Define:			return grok_define(&lbuf, c);
		  case Elif:			return grok_elif(&lbuf, c);
		  case Else:			return grok_else(buf, c);
		  case Endif:			return grok_endif(buf, c);
		  case Error:			return grok_error(&lbuf, c);
		  case If:				return grok_if(&lbuf, c);
		  case Ifdef:			return grok_ifdef(&lbuf, c, 0);
		  case Ifndef:			return grok_ifdef(&lbuf, c, 1);
		  case Include:			return grok_include(buf, &lbuf, c);
		  case Line:
		  case Pragma:
		  case Ident:			return skip_to_end(c);
		  case Undef:			return grok_undef(&lbuf, c);
		}
	}
	else {
		switch (keywd) {
		  case Define:
		  case Include:
		  case Pragma:
		  case Undef:
		  case Error:
		  case Ident:
		  case Line:
			return skip_to_end(c);
		  case Elif:
			return grok_elif(&lbuf,c);
		  case Else:
			return grok_else(buf, c);
		  case Endif:
			return grok_endif(buf, c);
		  case If:
		  case Ifdef:
		  case Ifndef:
			++control_state.cur_scope;
			return skip_to_end(c);
		}
	}

	return c;
}

static int
scan_actual(buf,c, level)
	buffer_t *buf;
	int c;
	int level;
{
	for (;;) {
		switch (classof(c)) {
		  case END_INPUT:
			unexpected_eof("in macro call");
			return c;
		  case PUNCT:
			switch (c) {
			  case '(':
				add(buf,c);
				c = scan_actual(buf,next_char(),level+1);
				if (c != ')') {
					expected("')'", c);
				}
				else {
					add(buf, c);
					c = next_char();
				}
				break;
			  case ')':
				return c;
			  case ',':
				if (level == 0) {
					return c;
				}
				add(buf, c);
				c = next_char();
				break;
			  case '"':
				add(buf, c);
				c = scan_string(buf, next_char());
				break;
			  case '\'':
				add(buf, c);
				c = scan_char_const(buf, next_char());
				break;
			  case '/':
			  case '\\':
				c = next_char();
				if (is_eof(c)) {
					add(buf,'\\');
					return c;
				}
				switch (c) {
				  case '\n':
					incline(0);
					c = next_char();
					break;
				  default:
					add(buf,'\\');
					add(buf,c);
					c = next_char();
					break;
				}
				break;
			  default:
				add(buf, c);
				c = next_char();
				break;
			}
			break;
		  default:
			c = scan_default(buf,c);
			break;
		}
	}
}

static int
grok_actuals(actuals, nactuals, buf, c)
	char ***actuals;
	int *nactuals;
	buffer_t *buf;
	int c;
{
	char *a[256];
	char **ap;
	int na = 0;
	int i;

	for (;;) {
		c = skip_white(c);
		if (is_eof(c)) {
			unexpected_eof("in macro call");
			return c;
		}
		if (c == ')') break;
		buf_init(buf);
		c = scan_actual(buf,c,0);
		range_check(na, 256, "Too many actual parameters");
		a[na++] = buf_get_str(buf);
		c = skip_white(c);
		if (c != ',') break;
		c = next_char();
	}

	*nactuals = na;

	if (na == 0) {
		*actuals = NULL;
	}
	else {
		ap = (char**) malloc(sizeof(char*) * na);
		for (i = 0; i < na; i++) {
			ap[i] = a[i];
		}
		*actuals = ap;
	}

	return c;
}

static int
push_expansion(mac, actuals, nactuals)
	macro_t *mac;
	char **actuals;
	int nactuals;
{
	scan_position_t *npos;

	npos = (scan_position_t*) allocate(sizeof(scan_position_t));

	npos->scan_kind = scan_macro_expansion;
	npos->scan.expansion.expand_macro = mac;
	npos->scan.expansion.expand_actuals = actuals;
	npos->scan.expansion.expand_nactuals = nactuals;
	if (curpos != NULL) {
		npos->scan_pos = curpos->scan_pos;
	}
	npos->scan_index = 0;
	npos->scan_next = curpos;
	curpos = npos;

	return next_char();
}

static int
push_string(str)
	char *str;
{
	scan_position_t *npos;

	npos = (scan_position_t*) allocate(sizeof(scan_position_t));

	npos->scan_kind = scan_text;
	npos->scan.text = str;
	npos->scan_pos = curpos->scan_pos;
	npos->scan_index = 0;
	npos->scan_next = curpos;
	curpos = npos;

	return next_char();
}

static void
grok_builtin_macro(buf, mac)
	buffer_t *buf;
	macro_t *mac;
{
	char buffer[32];

	switch (mac->macro_params) {
	  case BUILTIN_FILE:
		add(buf, '"');
		buf_add_str(buf, FN);
		add(buf, '"');
		break;
	  case BUILTIN_LINE:
		sprintf(buffer, "%d", LN);
		buf_add_str(buf, buffer);
		break;
	  default:
		assert(0);
		break;
	}
}

static int
grok_macro_instance(buf, lbuf, c, mac)
	buffer_t *buf, *lbuf;
	int c;
	macro_t *mac;
{
	char **actuals;
	int nactuals;

	if (mac->macro_params < -1) {
		grok_builtin_macro(buf, mac);
		return c;
	}
	if (mac->macro_params != -1) {
		buf_init(lbuf);
		c = scan_white(lbuf, c);
		if (c != '(') {
			buf_add_str(buf, mac->macro_name);
			buf_concat(buf, lbuf);
			return c;
		}
		buf_init(lbuf);
		c = grok_actuals(&actuals, &nactuals, lbuf, next_char());
		if (c != ')') {
			expected("')'", c);
		}
	}
	else {
		unget_char();
	}

	return push_expansion(mac, actuals, nactuals);
}

static int
parenthesized_ident(buf, c)
	buffer_t *buf;
	int c;
{
	for (;;) {
		c = skip_white(c);
		if (c == '(') {
			c = parenthesized_ident(buf, next_char());
			if (c != ')') {
				return 0;
			}
			c = next_char();
		}
		else if (is_alpha(c)) {
			do {
				add(buf, c);
				c = next_char();
			} while (is_alpha_numeric(c));
		}
		else {
			return c;
		}
	}
}

static int
grok_defined(buf, lbuf, c)
	buffer_t *buf, *lbuf;
	int c;
{
	char ident[128];
	char *name;

	c = skip_white(c);

	buf_init(lbuf);

	if (c == '(') {
		c = parenthesized_ident(lbuf, next_char());
		if (c != ')') {
			add(buf, BAD_INPUT);
			return c;
		}
		c = next_char();
	}
	else {
		c = scan_ident(lbuf, c);
	}

	name = local_copy(lbuf, ident, sizeof(ident));
	
	add(buf, ' ');

	if (macro_find(name)) {
		add(buf, '1');
	}
	else {
		add(buf, '0');
	}

	add(buf, ' ');

	if (name != ident) {
		free(name);
	}

	return c;
}

static int
grok_ident(buf, c)
	buffer_t *buf;
	int c;
{
	buffer_t lbuf;
	char ident[128];
	char *name;
	macro_t *mac;

	buf_init(&lbuf);
	c = scan_ident(&lbuf, c);

	name = local_copy(&lbuf, ident, sizeof(ident));
	
	if (parsing() && !strcmp(name,"defined")) {
		return grok_defined(buf, &lbuf, c);
	}

	if (mac = macro_find(name)) {
		c = grok_macro_instance(buf, &lbuf, c, mac);
	}
	else {
		buf_add_str(buf, name);
	}

	if (name != ident) {
		free(name);
	}

	return c;
}

static int
grok_actual_param(param_ord)
	int param_ord;
{
	char **actuals;
	char *param;
	int nactuals;

	if (curpos->scan_kind != scan_macro_expansion) {
		fatal(FN,LN,"bad param %s:%d %s:%d",__FILE__,__LINE__,
			  last_file, last_line);
	}
	assert(curpos->scan_kind == scan_macro_expansion);

	actuals = curpos->scan.expansion.expand_actuals;
	nactuals = curpos->scan.expansion.expand_nactuals;

	if (actuals == NULL || param_ord > nactuals) {
		return next_char();
	}

	param = actuals[param_ord - 1];

	if (param == NULL) {
		return next_char();
	}

	return push_string(param);
}

static int
skip(buf, c)
	buffer_t *buf;
	int c;
{
	buffer_t lbuf;

	assert(!parsing());

	for (;;) {
		if (scanning()) {
			return c;
		}

		switch (classof(c)) {
		  case END_INPUT:
			return c;
		  case MSTART:
			c = scan_directive(buf, next_char());
			break;
		  case PUNCT:
			switch (c) {
			  case '"':
				buf_init(&lbuf);
				c = scan_string(&lbuf, next_char());
				buf_destroy(&lbuf);
				break;
			  case '\'':
				buf_init(&lbuf);
				c = scan_char_const(&lbuf, next_char());
				buf_destroy(&lbuf);
				break;
			  case '/':
				c = next_char();
				if (c == '*') {
					c = skip_c_comment(NULL,c);
				}
				else if (c == '/') {
					c = skip_cpp_comment(NULL,c);
				}
				break;
			  case '\\':
				UNHANDLED();
				break;
			  default:
				c = next_char();
				break;
			}
			break;
		  default:
			c = scan_default(NULL, c);
			break;
		}
	}
}

static int
scan(buf)
	buffer_t *buf;
{
	int c;

	c = next_char();

	for (;;) {
		if (skipping()) {
			c = skip(buf, c);
		}

		assert(scanning());

		switch (classof(c)) {
		  case END_INPUT:
			return -1;
		  case WHITE:
			add(buf, c);
			return 0;
		  case END_OF_LINE:
			incline(1);
			check_position(buf);
			add(buf, c);
			return 0;
		  case ALPHA:
		  case ALPHA | XDIGIT:
			c = grok_ident(buf, c);
			break;
		  case MSTART:
			if (parsing()) {
				add(buf, BAD_INPUT);
				return 0;
			}
			c = scan_directive(buf, next_char());
			break;
		  default:
			c = scan_default(buf,c);
			break;
		}
	}
}

#define GET_FROM_BUF(buf)\
	while (buf_empty(buf)) {\
		if (scan(buf) == -1) break;\
	}\
	return buf_get(buf);

int
cpp_getc_from(buf)
	buffer_t *buf;
{
	GET_FROM_BUF(buf);
}

int
cpp_getc()
{
	buffer_t *buf = &cppbuf;
	GET_FROM_BUF(buf);
}


syntax highlighted by Code2HTML, v. 0.9.1