/* $Id$ */
/* Copyright (c) 2007-2014 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Devel cpp */
/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. */
/* FIXME:
 * - potential memory leak with tokens' data
 * - add a filter for the "%" operator
 * - add a way to tokenize input from a string (and handle "#" and "##") */



#include <assert.h>
#include <sys/stat.h>
#include <libgen.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include "parser.h"
#include "common.h"

#ifdef DEBUG
# define DEBUG_FILTER() fprintf(stderr, "DEBUG: %s('%c' 0x%x)\n", __func__, \
		*c, *c);
# define DEBUG_CALLBACK() fprintf(stderr, "DEBUG: %s('%c' 0x%x)\n", __func__, \
		c, c);
#else
# define DEBUG_FILTER()
# define DEBUG_CALLBACK()
#endif


/* CppParser */
/* private */
/* types */
typedef struct _CppOperator
{
	CppCode code;
	char const * string;
} CppOperator;

struct _CppParser
{
	Cpp * cpp;
	CppParser * parent;

	Parser * parser;
	int filters;

	/* for cpp_filter_inject */
	char * inject;
	int inject_first;
	/* for cpp_filter_newlines */
	int newlines_last;
	int newlines_last_cnt;
	/* for cpp_filter_trigraphs */
	int trigraphs_last;
	int trigraphs_last_cnt;
	/* for cpp_callback_directive */
	int directive_newline;
	int directive_control;
	/* to queue a token */
	int queue_ready;
	TokenCode queue_code;
	String * queue_string;

	CppParser * subparser;
};


/* variables */
/* operators */
static const CppOperator _cpp_operators[] =
{
	{ CPP_CODE_OPERATOR_AEQUALS,	"&="	},
	{ CPP_CODE_OPERATOR_DAMPERSAND,	"&&"	},
	{ CPP_CODE_OPERATOR_AMPERSAND,	"&"	},
	{ CPP_CODE_OPERATOR_RBRACKET,	":>"	},
	{ CPP_CODE_OPERATOR_COLON,	":"	},
	{ CPP_CODE_OPERATOR_BEQUALS,	"|="	},
	{ CPP_CODE_OPERATOR_DBAR,	"||"	},
	{ CPP_CODE_OPERATOR_BAR,	"|"	},
	{ CPP_CODE_OPERATOR_DIVEQUALS,	"/="	},
	{ CPP_CODE_OPERATOR_DIVIDE,	"/"	},
	{ CPP_CODE_OPERATOR_DOTDOTDOT,	"..."	},
	{ CPP_CODE_OPERATOR_DOT,	"."	},
	{ CPP_CODE_OPERATOR_DEQUALS,	"=="	},
	{ CPP_CODE_OPERATOR_EQUALS,	"="	},
	{ CPP_CODE_OPERATOR_DGEQUALS,	">>="	},
	{ CPP_CODE_OPERATOR_GEQUALS,	">="	},
	{ CPP_CODE_OPERATOR_DGREATER,	">>"	},
	{ CPP_CODE_OPERATOR_GREATER,	">"	},
	{ CPP_CODE_OPERATOR_DHASH,	"##"	},
	{ CPP_CODE_OPERATOR_HASH,	"#"	},
	{ CPP_CODE_OPERATOR_LBRACE,	"{"	},
	{ CPP_CODE_OPERATOR_LBRACKET,	"["	},
	{ CPP_CODE_OPERATOR_DLEQUALS,	"<<="	},
	{ CPP_CODE_OPERATOR_DLESS,	"<<"	},
	{ CPP_CODE_OPERATOR_LBRACKET,	"<:"	},
	{ CPP_CODE_OPERATOR_LBRACE,	"<%"	},
	{ CPP_CODE_OPERATOR_LEQUALS,	"<="	},
	{ CPP_CODE_OPERATOR_LESS,	"<"	},
	{ CPP_CODE_OPERATOR_LPAREN,	"("	},
	{ CPP_CODE_OPERATOR_MGREATER,	"->"	},
	{ CPP_CODE_OPERATOR_DMINUS,	"--"	},
	{ CPP_CODE_OPERATOR_MEQUALS,	"-="	},
	{ CPP_CODE_OPERATOR_MINUS,	"-"	},
	{ CPP_CODE_OPERATOR_RBRACE,	"%>"	},
	{ CPP_CODE_OPERATOR_DHASH,	"%:%:"	},
	{ CPP_CODE_OPERATOR_HASH,	"%:"	},
	{ CPP_CODE_OPERATOR_MODEQUALS,	"%="	},
	{ CPP_CODE_OPERATOR_MODULO,	"%"	},
	{ CPP_CODE_OPERATOR_NEQUALS,	"!="	},
	{ CPP_CODE_OPERATOR_NOT,	"!"	},
	{ CPP_CODE_OPERATOR_DPLUS,	"++"	},
	{ CPP_CODE_OPERATOR_PEQUALS,	"+="	},
	{ CPP_CODE_OPERATOR_PLUS,	"+"	},
	{ CPP_CODE_OPERATOR_QUESTION,	"?"	},
	{ CPP_CODE_OPERATOR_RBRACE,	"}"	},
	{ CPP_CODE_OPERATOR_RBRACKET,	"]"	},
	{ CPP_CODE_OPERATOR_RPAREN,	")"	},
	{ CPP_CODE_OPERATOR_SEMICOLON,	";"	},
	{ CPP_CODE_OPERATOR_TILDE,	"~"	},
	{ CPP_CODE_OPERATOR_TEQUALS,	"*="	},
	{ CPP_CODE_OPERATOR_TIMES,	"*"	},
	{ CPP_CODE_OPERATOR_XEQUALS,	"^="	},
	{ CPP_CODE_OPERATOR_XOR,	"^"	}
};
static const size_t _cpp_operators_cnt = sizeof(_cpp_operators)
	/ sizeof(*_cpp_operators);

/* directives */
static const char * _cpp_directives[] =
{
	"", "define", "elif", "else", "endif", "error", "if", "ifdef",
	"ifndef", "include", "pragma", "undef", "warning", NULL
};


/* prototypes */
/* useful */
static int _cpp_isword(int c);
static char * _cpp_parse_word(Parser * parser, int c);
static int _cpp_token_set(CppParser * cp, Token * token, TokenCode code,
		char const * string);

/* filters */
static int _cpp_filter_inject(int * c, void * data);
static int _cpp_filter_newlines(int * c, void * data);
static int _cpp_filter_trigraphs(int * c, void * data);

/* callbacks */
static int _cpp_callback_inject(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_dequeue(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_header(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_control(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_whitespace(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_newline(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_otherspace(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_comment(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_comma(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_operator(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_quote(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_directive(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_word(Parser * parser, Token * token, int c,
		void * data);
static int _cpp_callback_unknown(Parser * parser, Token * token, int c,
		void * data);


/* CppParser */
/* private */
/* cpp_isword */
static int _cpp_isword(int c)
{
	return isalnum(c) || c == '_' || c == '$';
}


/* cpp_parse_word */
static char * _cpp_parse_word(Parser * parser, int c)
{
	char * str = NULL;
	size_t len = 0;
	char * p;

	do
	{
		if((p = realloc(str, len + 2)) == NULL)
		{
			error_set_code(1, "%s", strerror(errno));
			free(str);
			return NULL;
		}
		str = p;
		str[len++] = c;
	}
	while(_cpp_isword((c = parser_scan_filter(parser))));
	str[len] = '\0';
	return str;
}


/* cpp_token_set */
static int _cpp_token_set(CppParser * cp, Token * token, TokenCode code,
		char const * string)
{
	if(token_set_string(token, string) != 0)
		return -1;
	if(cp->queue_code == CPP_CODE_NULL)
	{
		token_set_code(token, code);
		return 0;
	}
	/* we are parsing a directive */
	token_set_code(token, CPP_CODE_META_DATA);
	if(code == CPP_CODE_COMMENT)
		/* comments within directives are whitespaces */
		string = " ";
	if(cp->queue_string == NULL)
	{
		/* left-trim spaces */
		for(; isspace((unsigned char)string[0]); string++);
		if((cp->queue_string = string_new(string)) == NULL)
			return -1;
	}
	else if(string_append(&cp->queue_string, string) != 0)
		return -1;
	return 0;
}


/* filters */
/* cpp_filter_inject */
static int _cpp_filter_inject(int * c, void * data)
{
	CppParser * cp = data;
	size_t len;
	int d;

	if(cp->inject == NULL)
		return 0;
	DEBUG_FILTER();
	if((len = strlen(cp->inject)) > 0)
	{
		d = *c;
		*c = cp->inject[0];
		memmove(cp->inject, &cp->inject[1], len--);
		if(cp->inject_first && d != EOF && d != '\0')
		{
			cp->inject[len++] = d;
			cp->inject[len] = '\0';
			cp->inject_first = 0;
		}
	}
	if(len > 0)
		return 1;
	free(cp->inject);
	cp->inject = NULL;
	return 0;
}


/* cpp_filter_newlines */
static int _cpp_filter_newlines(int * c, void * data)
{
	CppParser * cpp = data;

	if(cpp->newlines_last_cnt != 0)
	{
		cpp->newlines_last_cnt--;
		*c = cpp->newlines_last;
		return 0;
	}
	if(*c != '\\')
		return 0;
	if((*c = parser_scan(cpp->parser)) == '\n')
	{
		*c = parser_scan(cpp->parser); /* skip the newline */
		return 0;
	}
	cpp->newlines_last = *c;
	cpp->newlines_last_cnt = 1;
	*c = '\\';
	return 1;
}


/* cpp_filter_trigraphs */
static int _trigraphs_get(int last, int * c);

static int _cpp_filter_trigraphs(int * c, void * data)
{
	CppParser * cpp = data;

	if(cpp->trigraphs_last_cnt == 2)
	{
		cpp->trigraphs_last_cnt--;
		*c = '?';
		return 0;
	}
	else if(cpp->trigraphs_last_cnt == 1)
	{
		cpp->trigraphs_last_cnt--;
		*c = cpp->trigraphs_last;
		return 0;
	}
	if(*c != '?')
		return 0;
	if((cpp->trigraphs_last = parser_scan(cpp->parser)) != '?')
	{
		cpp->trigraphs_last_cnt = 1;
		return 1;
	}
	cpp->trigraphs_last = parser_scan(cpp->parser);
	if(_trigraphs_get(cpp->trigraphs_last, c) != 0)
	{
#ifdef DEBUG
		fprintf(stderr, "DEBUG: last=%c\n", cpp->trigraphs_last);
#endif
		cpp->trigraphs_last_cnt = 2;
		return 2;
	}
#ifdef DEBUG
	fprintf(stderr, "DEBUG: filtered \"??%c\" into \"%c\"\n",
			cpp->trigraphs_last, *c);
#endif
	return 0;
}

static int _trigraphs_get(int last, int * c)
{
	switch(last)
	{
		case '=':
			*c = '#';
			break;
		case '/':
			*c = '\\';
			break;
		case '\'':
			*c = '^';
			break;
		case '(':
			*c = '[';
			break;
		case ')':
			*c = ']';
			break;
		case '!':
			*c = '|';
			break;
		case '<':
			*c = '{';
			break;
		case '>':
			*c = '}';
			break;
		case '-':
			*c = '~';
			break;
		default:
			return 1;
	}
	return 0;
}


/* callbacks */
/* cpp_callback_whitespace */
static int _cpp_callback_whitespace(Parser * parser, Token * token, int c,
		void * data)
{
	CppParser * cpp = data;
	char * str = NULL;
	size_t len = 0;
	char * p;

	if(!isspace(c))
		return 1;
	DEBUG_CALLBACK();
	do
	{
		if(c != '\n')
			continue;
		if((p = realloc(str, len + 2)) == NULL)
		{
			free(str);
			return -1;
		}
		str = p;
		str[len++] = c;
	}
	while(isspace((c = parser_scan_filter(parser))));
	token_set_code(token, CPP_CODE_WHITESPACE);
	if(str != NULL) /* some newlines were encountered */
	{
		str[len] = '\0';
		token_set_string(token, str);
		free(str);
		cpp->directive_newline = 1;
		cpp->queue_ready = 1;
		return 0;
	}
	token_set_string(token, " ");
	if(cpp->queue_code != CPP_CODE_NULL && cpp->queue_string != NULL)
		string_append(&cpp->queue_string, " ");
	return 0;
}


/* cpp_callback_newline */
static int _cpp_callback_newline(Parser * parser, Token * token, int c,
		void * data)
{
	CppParser * cpp = data;

	if(c != '\n')
		return 1;
	DEBUG_CALLBACK();
	cpp->directive_newline = 1;
	cpp->queue_ready = 1;
	parser_scan_filter(parser);
	token_set_code(token, CPP_CODE_NEWLINE);
	token_set_string(token, "\n");
	return 0;
}


/* cpp_callback_otherspace */
static int _cpp_callback_otherspace(Parser * parser, Token * token, int c,
		void * data)
{
	CppParser * cp = data;
	char * str = NULL;
	size_t len = 0;
	char * p;

	assert(c != '\n');
	if(!isspace(c))
		return 1;
	DEBUG_CALLBACK();
	do
	{
		if((p = realloc(str, len + 2)) == NULL)
		{
			free(str);
			return -1;
		}
		str = p;
		str[len++] = c;
	}
	while(isspace((c = parser_scan_filter(parser))) && c != '\n');
	if(str != NULL)
	{
		str[len] = '\0';
		_cpp_token_set(cp, token, CPP_CODE_WHITESPACE, str);
		free(str);
	}
	else
		_cpp_token_set(cp, token, CPP_CODE_WHITESPACE, " ");
	return 0;
}


/* cpp_callback_comment */
static int _cpp_callback_comment(Parser * parser, Token * token, int c,
		void * data)
{
	CppParser * cp = data;
	char * str = NULL;
	size_t len = 2;
	char * p;

	if(c != '/')
		return 1;
	DEBUG_CALLBACK();
	if((c = parser_scan_filter(parser)) != '*')
		return _cpp_token_set(cp, token, CPP_CODE_OPERATOR_DIVIDE, "/");
	for(c = parser_scan_filter(parser); c != EOF;)
	{
		if(!(cp->filters & CPP_FILTER_COMMENT))
		{
			if((p = realloc(str, len + 3)) == NULL)
				return -error_set_code(1, "%s", strerror(
							errno));
			str = p;
			str[len++] = c;
		}
		if(c == '*')
		{
			if((c = parser_scan_filter(parser)) == '/')
				break;
		}
		else
			c = parser_scan_filter(parser);
	}
	if(c == EOF)
		return -error_set_code(1, "%s", "End of file within a comment");
	parser_scan_filter(parser);
	if(str == NULL)
		return _cpp_token_set(cp, token, CPP_CODE_WHITESPACE, " ");
	str[0] = '/';
	str[1] = '*';
	str[len++] = '/';
	str[len] = '\0';
	_cpp_token_set(cp, token, CPP_CODE_COMMENT, str); /* XXX may fail */
	free(str);
	return 0;
}


/* cpp_callback_inject */
static int _cpp_callback_inject(Parser * parser, Token * token, int c,
		void * data)
{
	CppParser * cp = data;
	char buf[2] = "\0";

	if(cp->inject_first == 0)
		return 1;
	DEBUG_CALLBACK();
	if(c == EOF)
		return 1;
	/* the current character actually goes after the substitution */
	buf[0] = c;
	if(string_append(&cp->inject, buf) != 0)
		return -1;
	parser_scan_filter(parser);
	return 1;
}


/* cpp_callback_dequeue */
static int _cpp_callback_dequeue(Parser * parser, Token * token, int c,
		void * data)
{
	int ret = 0;
	CppParser * cpp = data;

	if(cpp->queue_ready == 0)
		return 1;
	cpp->queue_ready = 0;
	if(cpp->queue_code == CPP_CODE_NULL && cpp->queue_string == NULL)
		return 1;
	DEBUG_CALLBACK();
	token_set_code(token, cpp->queue_code);
	switch(cpp->queue_code)
	{
		case CPP_CODE_META_DEFINE:
		case CPP_CODE_META_ELIF:
		case CPP_CODE_META_IF:
		case CPP_CODE_META_IFDEF:
		case CPP_CODE_META_IFNDEF:
		case CPP_CODE_META_INCLUDE:
		case CPP_CODE_META_UNDEF:
			token_set_string(token, "");
			token_set_data(token, cpp->queue_string);
			cpp->queue_string = NULL;
			break;
		case CPP_CODE_META_ERROR:
		case CPP_CODE_META_WARNING:
			token_set_string(token, (cpp->queue_string != NULL)
					? cpp->queue_string : "");
			break;
		default:
			token_set_string(token, "");
			break;
	}
	cpp->queue_code = CPP_CODE_NULL;
	string_delete(cpp->queue_string);
	cpp->queue_string = NULL;
	cpp->directive_newline = 1;
	cpp->directive_control = 0;
	return ret;
}


/* cpp_callback_header */
static int _cpp_callback_header(Parser * parser, Token * token, int c,
		void * data)
{
	CppParser * cp = data;
	char end;
	char * str = NULL;
	size_t len = 0;
	char * p;

	if(cp->directive_control != 1 || cp->queue_code != CPP_CODE_META_INCLUDE
			|| (c != '<' && c != '"'))
		return 1;
	DEBUG_CALLBACK();
	end = (c == '<') ? '>' : '"';
	while((p = realloc(str, len + 3)) != NULL)
	{
		str = p;
		str[len++] = c;
		if((c = parser_scan_filter(parser)) == EOF || c == '\n')
			break;
		else if(c == end)
			break;
	}
	if(p == NULL) /* there was an error with realloc() */
	{
		error_set_code(1, "%s", strerror(errno));
		free(str);
		return -1;
	}
	else if(c == end) /* the header name is properly closed */
	{
		str[len++] = c;
		parser_scan_filter(parser);
	}
	str[len] = '\0';
	token_set_code(token, CPP_CODE_META_DATA);
	token_set_string(token, str);
	if(cp->queue_string == NULL || strlen(cp->queue_string) == 0)
	{
		free(cp->queue_string);
		cp->queue_string = str;
	}
	else
	{
		free(str);
		cp->queue_code = CPP_CODE_META_ERROR;
		free(cp->queue_string);
		/* XXX may be followed by junk */
		cp->queue_string = strdup("Syntax error");
	}
	return 0;
}


/* cpp_callback_control */
static int _cpp_callback_control(Parser * parser, Token * token, int c,
		void * data)
{
	CppParser * cpp = data;

	if(cpp->directive_newline != 1 || c != '#')
	{
		cpp->directive_newline = 0;
		return 1;
	}
	DEBUG_CALLBACK();
	parser_scan_filter(parser);
	token_set_code(token, CPP_CODE_META_DATA);
	token_set_string(token, "#");
	cpp->directive_newline = 0;
	cpp->directive_control = 1;
	cpp->queue_code = CPP_CODE_NULL;
	return 0;
}


/* cpp_callback_comma */
static int _cpp_callback_comma(Parser * parser, Token * token, int c,
		void * data)
{
	CppParser * cp = data;

	if(c != ',')
		return 1;
	DEBUG_CALLBACK();
	parser_scan_filter(parser);
	return _cpp_token_set(cp, token, CPP_CODE_COMMA, ",");
}


/* cpp_callback_operator */
static int _cpp_callback_operator(Parser * parser, Token * token, int c,
		void * data)
	/* FIXME probably fails for ".." and similar cases */
{
	CppParser * cp = data;
	size_t i;
	const size_t j = sizeof(_cpp_operators) / sizeof(*_cpp_operators);
	size_t pos;

	for(i = 0; i < _cpp_operators_cnt; i++)
		if(_cpp_operators[i].string[0] == c)
			break;
	if(i == _cpp_operators_cnt) /* nothing found */
		return 1;
	DEBUG_CALLBACK();
	for(pos = 0; i < j;)
	{
		if(_cpp_operators[i].string[pos] == '\0')
			break;
		if(c == _cpp_operators[i].string[pos])
		{
			c = parser_scan_filter(parser);
			pos++;
		}
		else
			i++;
	}
	if(i == j) /* should not happen */
		return -1;
	return _cpp_token_set(cp, token, _cpp_operators[i].code,
			_cpp_operators[i].string);
}


/* cpp_callback_quote */
static int _cpp_callback_quote(Parser * parser, Token * token, int c,
		void * data)
{
	CppParser * cp = data;
	int escape = 0;
	char * str = NULL;
	size_t len = 0;
	char * p;

	if(c == '\'')
		token_set_code(token, CPP_CODE_SQUOTE);
	else if(c == '"')
		token_set_code(token, CPP_CODE_DQUOTE);
	else
		return 1;
	DEBUG_CALLBACK();
	while((p = realloc(str, len + 3)) != NULL)
	{
		str = p;
		str[len++] = c;
		if((c = parser_scan_filter(parser)) == EOF || c == '\n')
			break;
		if(escape)
			escape = 0;
		else if(c == str[0])
			break;
		else if(c == '\\')
			escape = 1;
	}
	if(p == NULL) /* there was an error with realloc() */
	{
		error_set_code(1, "%s", strerror(errno));
		free(str);
		return -1;
	}
	else if(c == str[0]) /* the quoted string is properly closed */
	{
		str[len++] = str[0];
		parser_scan_filter(parser);
	} /* XXX else we should probably issue a warning */
	str[len] = '\0';
	/* XXX keep code earlier, may fail */
	_cpp_token_set(cp, token, token_get_code(token), str);
	free(str);
	return 0;
}


/* cpp_callback_directive */
static int _cpp_callback_directive(Parser * parser, Token * token, int c,
		void * data)
{
	CppParser * cpp = data;
	char * str;
	size_t i;

	if(cpp->directive_control != 1 || cpp->queue_code != CPP_CODE_NULL
			|| !_cpp_isword(c))
		return 1;
	DEBUG_CALLBACK();
	if((str = _cpp_parse_word(parser, c)) == NULL)
		return -1;
	for(i = 0; _cpp_directives[i] != NULL; i++)
		if(strcmp(str, _cpp_directives[i]) == 0)
			break;
	if(_cpp_directives[i] != NULL)
	{
		cpp->queue_code = CPP_CODE_META_FIRST + i;
		cpp->queue_string = NULL;
	}
	else
	{
		cpp->queue_code = CPP_CODE_META_ERROR;
		cpp->queue_string = string_new_append("Invalid directive: #",
				str, ":", NULL); /* XXX check for errors */
	}
	token_set_code(token, CPP_CODE_META_DATA);
	token_set_string(token, str);
	free(str);
	return 0;
}


/* cpp_callback_word */
static int _cpp_callback_word(Parser * parser, Token * token, int c,
		void * data)
{
	int ret;
	CppParser * cp = data;
	char * str;

	if(!_cpp_isword(c))
		return 1;
	DEBUG_CALLBACK();
	if((str = _cpp_parse_word(parser, c)) == NULL)
		return -1;
	ret = _cpp_token_set(cp, token, CPP_CODE_WORD, str);
	free(str);
	return ret;
}


/* cpp_callback_unknown */
static int _cpp_callback_unknown(Parser * parser, Token * token, int c,
		void * data)
{
	CppParser * cp = data;
	char buf[2] = "\0";

	if(c == EOF)
		return 1;
	DEBUG_CALLBACK();
	buf[0] = c;
	parser_scan_filter(parser);
	return _cpp_token_set(cp, token, CPP_CODE_UNKNOWN, buf);
}


/* public */
/* functions */
/* cppparser_new */
CppParser * cppparser_new(Cpp * cpp, CppParser * parent, char const * filename,
		int filters)
{
	/* FIXME factor code */
	CppParser * cp;

	if((cp = object_new(sizeof(*cp))) == NULL)
		return NULL;
	cp->cpp = cpp;
	cp->parent = parent;
	cp->parser = parser_new(filename);
	cp->filters = filters;
	cp->inject = NULL;
	cp->inject_first = 0;
	cp->newlines_last = 0;
	cp->newlines_last_cnt = 0;
	cp->trigraphs_last = 0;
	cp->trigraphs_last_cnt = 0;
	cp->directive_newline = 1;
	cp->directive_control = 0;
	cp->queue_ready = 0;
	cp->queue_code = CPP_CODE_NULL;
	cp->queue_string = NULL;
	cp->subparser = NULL;
	if(cp->parser == NULL)
	{
		cppparser_delete(cp);
		return NULL;
	}
	parser_add_filter(cp->parser, _cpp_filter_inject, cp);
	parser_add_filter(cp->parser, _cpp_filter_newlines, cp);
	if(cp->filters & CPP_FILTER_TRIGRAPH)
		parser_add_filter(cp->parser, _cpp_filter_trigraphs, cp);
	parser_add_callback(cp->parser, _cpp_callback_inject, cp);
	parser_add_callback(cp->parser, _cpp_callback_dequeue, cp);
	if(cp->filters & CPP_FILTER_WHITESPACE)
		parser_add_callback(cp->parser, _cpp_callback_whitespace, cp);
	else
	{
		parser_add_callback(cp->parser, _cpp_callback_newline, cp);
		parser_add_callback(cp->parser, _cpp_callback_otherspace, cp);
	}
	parser_add_callback(cp->parser, _cpp_callback_comment, cp);
	parser_add_callback(cp->parser, _cpp_callback_header, cp);
	parser_add_callback(cp->parser, _cpp_callback_control, cp);
	parser_add_callback(cp->parser, _cpp_callback_comma, cp);
	parser_add_callback(cp->parser, _cpp_callback_operator, cp);
	parser_add_callback(cp->parser, _cpp_callback_quote, cp);
	parser_add_callback(cp->parser, _cpp_callback_directive, cp);
	parser_add_callback(cp->parser, _cpp_callback_word, cp);
	parser_add_callback(cp->parser, _cpp_callback_unknown, cp);
	return cp;
}


/* cppparser_new_string */
CppParser * cppparser_new_string(Cpp * cpp, CppParser * parent,
		char const * string, int filters)
{
	CppParser * cp;

	if((cp = object_new(sizeof(*cp))) == NULL)
		return NULL;
	cp->cpp = cpp;
	cp->parent = parent;
	cp->parser = parser_new_string(string, strlen(string));
	cp->filters = filters;
	cp->inject = NULL;
	cp->inject_first = 0;
	cp->newlines_last = 0;
	cp->newlines_last_cnt = 0;
	cp->trigraphs_last = 0;
	cp->trigraphs_last_cnt = 0;
	cp->directive_newline = 1;
	cp->directive_control = 0;
	cp->queue_ready = 0;
	cp->queue_code = CPP_CODE_NULL;
	cp->queue_string = NULL;
	cp->subparser = NULL;
	if(cp->parser == NULL)
	{
		cppparser_delete(cp);
		return NULL;
	}
	parser_add_filter(cp->parser, _cpp_filter_inject, cp);
	parser_add_filter(cp->parser, _cpp_filter_newlines, cp);
	if(cp->filters & CPP_FILTER_TRIGRAPH)
		parser_add_filter(cp->parser, _cpp_filter_trigraphs, cp);
	parser_add_callback(cp->parser, _cpp_callback_inject, cp);
	parser_add_callback(cp->parser, _cpp_callback_dequeue, cp);
	if(cp->filters & CPP_FILTER_WHITESPACE)
		parser_add_callback(cp->parser, _cpp_callback_whitespace, cp);
	else
	{
		parser_add_callback(cp->parser, _cpp_callback_newline, cp);
		parser_add_callback(cp->parser, _cpp_callback_otherspace, cp);
	}
	parser_add_callback(cp->parser, _cpp_callback_comment, cp);
	parser_add_callback(cp->parser, _cpp_callback_header, cp);
	parser_add_callback(cp->parser, _cpp_callback_control, cp);
	parser_add_callback(cp->parser, _cpp_callback_comma, cp);
	parser_add_callback(cp->parser, _cpp_callback_operator, cp);
	parser_add_callback(cp->parser, _cpp_callback_quote, cp);
	parser_add_callback(cp->parser, _cpp_callback_directive, cp);
	parser_add_callback(cp->parser, _cpp_callback_word, cp);
	parser_add_callback(cp->parser, _cpp_callback_unknown, cp);
	return cp;
}


/* cppparser_delete */
void cppparser_delete(CppParser * cp)
{
	string_delete(cp->queue_string);
	if(cp->subparser != NULL)
		cppparser_delete(cp->subparser);
	if(cp->parser != NULL)
		parser_delete(cp->parser);
	string_delete(cp->inject);
	object_delete(cp);
}


/* accessors */
/* cppparser_get_filename */
char const * cppparser_get_filename(CppParser * cpp)
{
	return parser_get_filename(cpp->parser);
}


/* useful */
/* cppparser_include */
static char * _include_path(CppParser * cpp, char const * str);
static char * _path_lookup(CppParser * cp, char const * path, int system);

int cppparser_include(CppParser * cp, char const * include)
{
	char * path;

	if((path = _include_path(cp, include)) == NULL)
		return -1;
	for(; cp->subparser != NULL; cp = cp->subparser);
	cp->subparser = cppparser_new(cp->cpp, cp, path, cp->filters);
	free(path);
	return (cp->subparser != NULL) ? 0 : -1;
}

static char * _include_path(CppParser * cp, char const * str)
{
	int system;
	int d;
	size_t i;
	char * path = NULL;
	char * p;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%p, \"%s\")\n", __func__, (void *)cp, str);
#endif
	system = (str[0] == '<') ? 1 : ((str[0] == '"') ? 0 : -1);
	if(system == 1)
		d = '>';
	else if(system == 0)
		d = '"';
	else
	{
		error_set("%s", "Invalid include directive");
		return NULL;
	}
	for(i = 1; str[i] != '\0' && str[i] != d; i++);
	/* FIXME also check what's behind the directive */
	if(i == 1 || str[i] != d)
	{
		error_set("%s", "Invalid include directive");
		return NULL;
	}
	if((path = strdup(&str[1])) == NULL)
	{
		error_set("%s", strerror(errno));
		return NULL;
	}
	path[i - 1] = '\0';
	p = _path_lookup(cp, path, system);
	free(path);
	return p;
}

static char * _path_lookup(CppParser * cp, char const * path, int system)
{
	Cpp * cpp = cp->cpp;
	char const * filename;
	char * p;
	char * q;
	char * r;
	struct stat st;

	if(system != 0)
		return cpp_path_lookup(cpp, path, 1);
	for(; cp->subparser != NULL; cp = cp->subparser);
	for(; cp != NULL; cp = cp->parent)
	{
		filename = parser_get_filename(cp->parser);
		if((p = string_new(filename)) == NULL)
			return NULL;
		q = dirname(p);
		r = string_new_append(q, "/", path, NULL);
		string_delete(p);
		if(r == NULL)
			return NULL;
#ifdef DEBUG
		fprintf(stderr, "DEBUG: stat(\"%s\", %p)\n", r, (void *)&st);
#endif
		if(stat(r, &st) == 0)
			return r;
		error_set("%s: %s", r, strerror(errno));
		string_delete(r);
	}
	return cpp_path_lookup(cpp, path, 0);
}


/* cppparser_inject */
/* FIXME should take a buffer as input? */
int cppparser_inject(CppParser * cp, char const * string)
{
	if(string == NULL || string[0] == '\0')
		return 0; /* don't bother */
	for(; cp->subparser != NULL; cp = cp->subparser);
	if(cp->inject == NULL)
	{
		if((cp->inject = string_new(string)) == NULL)
			return 1;
	}
	else if(string_append(&cp->inject, string) != 0)
		return 1;
	cp->inject_first = 1;
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%p, \"%s\") => \"%s\"\n", __func__,
			(void *)cp, string, cp->inject);
#endif
	return 0;
}


/* cppparser_scan */
int cppparser_scan(CppParser * cp, Token ** token)
{
	if(cp->subparser != NULL)
	{
		if(cppparser_scan(cp->subparser, token) != 0)
			return 1;
		if(*token != NULL)
			return 0;
		cppparser_delete(cp->subparser); /* end of file */
		cp->subparser = NULL;
	}
	return parser_get_token(cp->parser, token);
}
