/*
 * Copyright (c) 2002, The Tendra Project <http://www.ten15.org/>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice unmodified, this list of conditions, and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *    		 Crown Copyright (c) 1997
 *
 *    This TenDRA(r) Computer Program is subject to Copyright
 *    owned by the United Kingdom Secretary of State for Defence
 *    acting through the Defence Evaluation and Research Agency
 *    (DERA).  It is made available to Recipients with a
 *    royalty-free licence for its use, reproduction, transfer
 *    to other parties and amendment for any purpose not excluding
 *    product development provided that any such use et cetera
 *    shall be deemed to be acceptance of the following conditions:-
 *
 *        (1) Its Recipients shall ensure that this Notice is
 *        reproduced upon any copies or amended versions of it;
 *
 *        (2) Any amended version of it shall be clearly marked to
 *        show both the nature of and the organisation responsible
 *        for the relevant amendment or amendments;
 *
 *        (3) Its onward transfer from a recipient to another
 *        party shall be deemed to be that party's acceptance of
 *        these conditions;
 *
 *        (4) DERA gives no warranty or assurance as to its
 *        quality or suitability for any purpose and DERA accepts
 *        no liability whatsoever in relation to any use to which
 *        it may be put.
 *
 * $TenDRA: tendra/src/producers/common/parse/preproc.c,v 1.18 2005/11/09 17:09:48 stefanf Exp $
 */


#include "config.h"
#include "producer.h"

#include "msgcat.h"
#include "tenapp.h"

#include "system.h"
#include "c_types.h"
#include "exp_ops.h"
#include "hashid_ops.h"
#include "id_ops.h"
#include "loc_ext.h"
#include "member_ops.h"
#include "nat_ops.h"
#include "str_ops.h"
#include "tok_ops.h"
#include "type_ops.h"
#include "error.h"
#include "catalog.h"
#include "option.h"
#include "buffer.h"
#include "char.h"
#include "compile.h"
#include "constant.h"
#include "convert.h"
#include "dump.h"
#include "file.h"
#include "hash.h"
#include "identifier.h"
#include "lex.h"
#include "literal.h"
#include "macro.h"
#include "namespace.h"
#include "parse.h"
#include "pragma.h"
#include "predict.h"
#include "preproc.h"
#include "print.h"
#include "statement.h"
#include "symbols.h"
#include "syntax.h"
#include "tokdef.h"
#include "token.h"
#include "ustring.h"


/*
 *    FORWARD DECLARATIONS
 *
 *    The following functions, which are concerned with processing assertions,
 *    are used in read_if_exp before they are defined.
 */

static int eq_pptok(PPTOKEN *, PPTOKEN *);
static PPTOKEN *skip_predicate(PPTOKEN **, int);
static int check_assert(HASHID, PPTOKEN *, int);


/*
 *    PREPROCESSING FLAGS
 *
 *    The flag in_preproc_dir is set to true in a preprocessing directive.
 *    The flag preproc_only causes only the preprocessor to be run.  Finally
 *    preproc_loc records the position of the start of each preprocessing
 *    directive.
 */

int in_preproc_dir = 0;
int no_preproc_dir = 0;
int in_pragma_dir = 0;
int in_hash_if_exp = 0;
int inclusion_dependencies = DEP_NONE;
const char *inclusion_obj_suffix = ".o";
int pragma_number = 0;
int preproc_only = 0;
int preproc_space = 0;
LOCATION preproc_loc = NULL_loc;


/*
 *    NEGATE A CONDITIONAL
 *
 *    This routine negates the conditional cond.  Note that skipped and
 *    unresolved conditions negate to themselves.
 */

static unsigned
negate_cond(unsigned cond)
{
	if (cond == PP_TRUE) return (PP_FALSE);
	if (cond == PP_FALSE) return (PP_TRUE);
	if (cond == PP_PAST) return (PP_FALSE);
	return (cond);
}


/*
 *    CONDITIONAL COMPILATION STACK
 *
 *    The stack preproc_stack gives all the active conditional compilation
 *    states.  In addition loc_stack records the corresponding file locations.
 *    preproc_depth gives the depth of conditional compilation within the
 *    current file.
 */

static STACK (unsigned) preproc_stack = NULL_stack (unsigned);
static STACK (LOCATION) loc_stack = NULL_stack (LOCATION);
static unsigned preproc_depth = 0;


/*
 *    SET UP CONDITIONAL COMPILATION STACK
 *
 *    This routine sets up the conditional compilation stack at the start
 *    of a source file by pushing an end marker.
 */

void
start_preproc_if(void)
{
	PUSH_unsigned (preproc_depth, preproc_stack);
	PUSH_unsigned (PP_END, preproc_stack);
	PUSH_loc (crt_loc, loc_stack);
	preproc_depth = 0;
	return;
}


/*
 *    CLEAR CONDITIONAL COMPILATION STACK
 *
 *    This routine is called at the end of each source file to check for
 *    any unmatched '#if', '#elif' or '#else' directives.  It clears the
 *    conditional compilation stack down as far as the end marker set up
 *    by the previous routine.  It is possible for the routine to be
 *    called more than once for the main source file, hence the necessity
 *    to check that the stack is not empty.  The routine returns true if
 *    no unmatched directives are found.
 */

int
clear_preproc_if(void)
{
	int ok = 1;
	while (!IS_NULL_stack (preproc_stack)) {
		int dir;
		LOCATION loc;
		unsigned cond;
		POP_unsigned (cond, preproc_stack);
		POP_loc (loc, loc_stack);
		if (cond == PP_END) {
			/* Restore stored preprocessing depth */
			POP_unsigned (preproc_depth, preproc_stack);
			break;
		}
		if (cond & PP_HAVE_ELSE) {
			dir = lex_else;
		} else if (cond & PP_HAVE_ELIF) {
			dir = lex_elif;
		} else {
			dir = lex_if;
		}
		report (loc, ERR_cpp_cond_if_match (dir, lex_endif));
		decr_value (OPT_VAL_hash_if_depth);
		preproc_depth--;
		ok = 0;
	}
	return (ok);
}


/*
 *    MACRO-LIKE TOKEN IDENTIFIER
 *
 *    If check_macro finds a macro-like token then the corresponding identifier
 *    is stored in this variable.
 */

IDENTIFIER token_macro = NULL_id;


/*
 *    CHECK WHETHER A MACRO IS DEFINED
 *
 *    This routine checks whether the hash table entry macro represents a
 *    valid macro.  It returns PP_TRUE if macro is already defined and
 *    PP_FALSE otherwise.  It also reports on ISO keywords and other invalid
 *    macro identifiers.  If used is true then the macro is marked as having
 *    been used.
 */

unsigned
check_macro(HASHID macro, int used)
{
	/* Check for simple macros */
	DECL_SPEC ds;
	IDENTIFIER id;
	if (IS_NULL_hashid (macro)) {
		/* Special case for protection macros */
		return (PP_TRUE);
	}
	id = DEREF_id (hashid_id (macro));
	switch (TAG_id (id)) {
	case id_obj_macro_tag :
	case id_func_macro_tag : {
		if (used) {
			ds = DEREF_dspec (id_storage (id));
			ds |= dspec_used;
			COPY_dspec (id_storage (id), ds);
			if (do_macro && do_usage) dump_use (id, &preproc_loc, 1);
		}
		return (PP_TRUE);
	}
	case id_iso_keyword_tag : {
		if (used) report (preproc_loc, ERR_lex_key_iso (macro));
		break;
	}
	}

	/* Check for tokenised values */
	if (preproc_only) {
		id = underlying_id (id);
		ds = DEREF_dspec (id_storage (id));
		if (ds & dspec_token) {
			/* May be a token */
			token_macro = id;
			return (PP_TOKEN | PP_UNRESOLVED);
		}

	} else {
		id = find_id (macro);
		while (!IS_NULL_id (id)) {
			IDENTIFIER tid = find_token (id);
			if (IS_id_token (tid)) {
				IDENTIFIER sid = DEREF_id (id_token_alt (tid));
				ds = DEREF_dspec (id_storage (sid));
				if ((ds & dspec_token) && !(ds & dspec_template)) {
					TOKEN tok = DEREF_tok (id_token_sort (tid));
					switch (TAG_tok (tok)) {
					case tok_exp_tag :
					case tok_stmt_tag :
					case tok_nat_tag :
					case tok_snat_tag :
					case tok_func_tag :
					case tok_proc_tag : {
						/* These are in the macro namespace */
						if (used) use_id (id, 0);
						token_macro = id;
						ds = DEREF_dspec (id_storage (tid));
						if (ds & (dspec_pure | dspec_defn)) {
							return (PP_TOKEN | PP_TRUE);
						}
						return (PP_TOKEN | PP_FALSE);
					}
					}
				}
			}
			if (!IS_id_function_etc (id)) break;
			id = DEREF_id (id_function_etc_over (id));
		}
	}
	return (PP_FALSE);
}


/*
 *    TARGET DEPENDENT CONDITION
 *
 *    Any target dependent conditional compilation expressions encountered
 *    are stored in this variable.
 */

EXP crt_hash_if_exp = NULL_exp;


/*
 *    PATCH PREPROCESSING DIRECTIVE INTO PREPROCESSOR OUTPUT
 *
 *    This routine is used to patch the preprocessing directive given by
 *    the preprocessing tokens p into the main preprocessor output.  It is
 *    used to preserve target dependent conditionals and other directives
 *    which need to be passed through to the output.
 */

void
patch_preproc_dir(PPTOKEN *p)
{
	if (p) {
		PPTOKEN *q = p;
		while (q->next && q->next->tok != lex_newline) q = q->next;
		free_tok_list (q->next);
		q->next = crt_token->next;
		crt_token->next = p;
		p->pp_space = WHITE_SPACE;
	}
	return;
}


/*
 *    READ AN EXPRESSION COMPILATION CONDITION
 *
 *    This routine reads the constant expression following a '#if' or '#elif'
 *    preprocessing directive.  It returns a value indicating whether the
 *    expression is zero or nonzero.  The argument act is false to indicate
 *    that the directive is being skipped.  The expression consists of all
 *    the preprocessing tokens in the rest of the directive with any
 *    defined operations suitably expanded.  This is then macro expanded
 *    and finally has any remaining identifiers replaced by 0.  All the
 *    parsing is done using this list of tokens - no other tokens are read
 *    from the input file (the newline appended by read_line ensures that
 *    the parser doesn't spill off the end).
 */

static unsigned
read_if_exp(int act, int dir)
{
	EXP e = NULL_exp;
	unsigned cond = PP_SKIP;
	if (act) {
		/* Read the rest of the line */
		PARSE_STATE ps;
		HASHID def = KEYWORD (lex_defined);
		PPTOKEN *p = read_line (lex_ignore_token, lex_newline);
		PPTOKEN *q = p;

		/* Scan line for defined operations and assertions */
		while (q != NULL) {
			int t = q->tok;

			if (t == lex_identifier &&
				EQ_hashid (q->pp_data.id.hash, def)) {
				/* Deal with 'defined' */
				PPTOKEN *r = q->next;
				/* Because of final newline don't need to check r != NULL */
				t = r->tok;
				if (t == lex_identifier) {
					/* Operation of the form 'defined id' */
					HASHID macro = r->pp_data.id.hash;
					unsigned c = check_macro (macro, 1);
					c &= PP_COND_MASK;
					if (c == PP_UNRESOLVED) {
						q->tok = lex_defined;
						cond = PP_UNRESOLVED;
					} else {
						q->tok = lex_integer_Hlit;
						q->pp_data.text = ustrlit (c ? "1" : "0");
						q->next = r->next;
						r->next = NULL;
						free_tok_list (r);
					}

				} else if (t == lex_open_Hround &&
						   r->next->tok == lex_identifier &&
						   r->next->next->tok == lex_close_Hround) {
					/* Operation of the form 'defined (id)' */
					HASHID macro = r->next->pp_data.id.hash;
					unsigned c = check_macro (macro, 1);
					c &= PP_COND_MASK;
					if (c == PP_UNRESOLVED) {
						q->tok = lex_defined;
						cond = PP_UNRESOLVED;
					} else {
						q->tok = lex_integer_Hlit;
						q->pp_data.text = ustrlit (c ? "1" : "0");
						q->next = r->next->next->next;
						r->next->next->next = NULL;
						free_tok_list (r);
					}

				} else {
					/* Badly formed 'defined' operation */
					report (preproc_loc, ERR_cpp_cond_def_id ());
				}

			} else if ((t == lex_hash_H1 || t == lex_hash_H2) &&
					   q->next->tok == lex_identifier) {
				/* Deal with '#predicate' */
				const char *c = "0";
				PPTOKEN *r = q->next;
				HASHID pred = r->pp_data.id.hash;
				if (t == lex_hash_H2) IGNORE get_digraph (t);
				if (r->next->tok == lex_open_Hround) {
					/* Check for a particular predicate */
					PPTOKEN *s = r->next->next;
					q->next = skip_predicate (&s, dir);
					if (check_assert (pred, s, 0)) c = "1";
				} else {
					/* Check for any predicate */
					if (check_assert (pred, r, 1)) c = "1";
					q->next = r->next;
					r->next = NULL;
				}
				free_tok_list (r);
				q->tok = lex_integer_Hlit;
				q->pp_data.text = ustrlit (c);
			}
			q = q->next;
		}

		/* Macro expand the line */
		q = expand_tok_list (p);
		free_tok_list (p);
		p = q;

		/* Check for any remaining identifiers */
		while (q != NULL) {
			if (q->tok == lex_identifier) {
				HASHID nm = q->pp_data.id.hash;
				IDENTIFIER id = DEREF_id (hashid_id (nm));
				unsigned tag = TAG_id (id);
				if (tag == id_obj_macro_tag || tag == id_func_macro_tag) {
					/* Allow for unexpanded macros */
					id = DEREF_id (id_alias (id));
					tag = TAG_id (id);
				}
				if (tag == id_keyword_tag) {
					int u = (int) DEREF_ulong (id_no (id));
					if (u == lex_true || u == lex_false) {
						/* Preserve boolean literals */
						tag = id_iso_keyword_tag;
					}
				}
				if (tag == id_iso_keyword_tag) {
					/* Allow for ISO keywords */
					int u = (int) DEREF_ulong (id_no (id));
					int v = primary_form (u);
					if (v != u) {
						ERROR err = ERR_lex_digraph_iso (nm, v);
						report (preproc_loc, err);
					}
					q->tok = v;
				} else {
					unsigned c = check_macro (nm, 0);
					if (c & PP_TOKEN) {
						/* Preserve token identifiers */
						c &= PP_COND_MASK;
						if (c == PP_UNRESOLVED) {
							cond = PP_UNRESOLVED;
						}
					} else {
						/* Replace other identifiers by 0 */
						ERROR err;
						if (EQ_hashid (nm, def)) {
							/* Shouldn't have defined */
							err = ERR_cpp_cond_def_replace ();
							if (!IS_NULL_err (err)) {
								report (preproc_loc, err);
							}
						}
						/* QUERY: what about true and false? */
						err = ERR_cpp_cond_zero (nm);
						if (!IS_NULL_err (err)) {
							report (preproc_loc, err);
						}
						q->tok = lex_integer_Hlit;
						q->pp_data.text = ustrlit ("0");
					}
				}
			}
			q = q->next;
		}

		/* Parse the line for a constant expression */
		save_state (&ps, 0);
		init_parser (p);
		in_hash_if_exp++;
		crt_loc = preproc_loc;
		crt_line_changed = 1;
		ADVANCE_LEXER;
		if (cond == PP_UNRESOLVED) {
			/* Unresolved tokens when preprocessing */
			ASSERT (preproc_only);
			/* EMPTY */
		} else {
			/* Parse condition */
			cond = PP_FALSE;
			parse_nat (&e);
			if (crt_lex_token != lex_newline && !have_syntax_error) {
				/* Should have reached the end of the line */
				ERROR err = ERR_lex_parse (crt_token);
				err = concat_error (err, ERR_cpp_end (dir));
				report (preproc_loc, err);
			}
		}
		restore_state (&ps);

		/* Check the result expression */
		if (!IS_NULL_exp (e)) {
			/* Evaluate the expression */
			ERROR err = NULL_err;
			IGNORE make_nat_exp (e, &err);
			e = convert_boolean (e, exp_paren_tag, &err);
			if (!IS_NULL_err (err)) {
				err = concat_error (err, ERR_cpp_cond_if_const (dir));
				report (preproc_loc, err);
				cond = PP_FALSE;
			} else {
				unsigned b = eval_const_cond (e);
				if (b == BOOL_TRUE) {
					cond = PP_TRUE;
				} else if (b == BOOL_FALSE) {
					cond = PP_FALSE;
				} else {
					cond = PP_UNRESOLVED;
				}
			}
		}

		/* Restore the parser */
		p = restore_parser ();
		if (cond == PP_UNRESOLVED) {
			/* Save target dependent conditions */
			if (preproc_only) {
				/* Patch crt_token with tokens comprising condition */
				p = clean_tok_list (p);
				patch_preproc_dir (p);
				p = NULL;
			} else {
				/* Store condition in crt_hash_if_exp */
				report (preproc_loc, ERR_cpp_cond_if_ti (dir));
				crt_hash_if_exp = e;
			}
		}
		free_tok_list (p);
		in_hash_if_exp--;
	}
	if (in_preproc_dir) IGNORE skip_to_end ();
	return (cond);
}


/*
 *    READ A DEFINED COMPILATION CONDITION
 *
 *    This routine reads the macro identifier following a '#ifdef' or
 *    '#ifndef' preprocessing directive.  It returns a value indicating
 *    whether the macro is defined or not.  The argument act is false to
 *    indicate that the directive is being skipped, prev is as in
 *    read_preproc_dir.
 */

static unsigned
read_if_def(int act, int dir, int prev)
{
	unsigned cond;
	if (act) {
		int t = read_token ();
		update_column ();
		if (in_preproc_dir) preproc_loc = crt_loc;
		if (t == lex_identifier) {
			HASHID macro = token_hashid;
			cond = check_macro (macro, 1);
			cond &= PP_COND_MASK;
			if (prev == lex_included) {
				/* Protection macro begins '#ifndef macro' */
				protection_macro (macro, prev, dir);
			}
			if (in_preproc_dir && skip_to_end ()) {
				report (preproc_loc, ERR_cpp_end (dir));
			}
		} else {
			report (preproc_loc, ERR_cpp_cond_ifdef_id (dir));
			cond = PP_FALSE;
		}
	} else {
		cond = PP_SKIP;
	}
	if (in_preproc_dir) IGNORE skip_to_end ();
	return (cond);
}


/*
 *    DEAL WITH CONDITIONAL COMPILATIONS
 *
 *    This routine deals with the various conditional compilation preprocessing
 *    directives.  dir gives the directive identifier and c indicates the
 *    associated condition.  The skipping of unused code is incorporated into
 *    this routine.  The routine returns lex_ignore_token for simple '#if' and
 *    '#elif' directives, lex_end_condition for simple '#else' and '#endif'
 *    directives, and one of the values lex_hash_Hif, lex_hash_Helif,
 *    lex_hash_Helse and lex_hash_Hendif for target dependent conditions.
 */

static int
read_if(int dir, unsigned c, int prev)
{
	unsigned cond = c;
	int ret = lex_ignore_token;

	if (dir == lex_if || dir == lex_ifdef || dir == lex_ifndef) {
		/* Deal with '#if', '#ifdef' and '#ifndef' */
		if (cond == PP_UNRESOLVED) ret = lex_hash_Hif;
		if (prev != lex_included && preproc_depth == 0) {
			/* Can't have second '#if' in protection macro */
			protection_macro (NULL_hashid, lex_ignore_token, dir);
		}
		PUSH_unsigned (cond, preproc_stack);
		PUSH_loc (preproc_loc, loc_stack);
		IGNORE incr_value (OPT_VAL_hash_if_depth);
		preproc_depth++;

	} else {
		/* Get current condition for other directives */
		LOCATION loc;
		unsigned crt_cond;
		POP_unsigned (cond, preproc_stack);
		decr_value (OPT_VAL_hash_if_depth);
		preproc_depth--;
		/* Don't pop location yet */
		if (cond == PP_END) {
			/* No matching '#if' */
			ERROR err = ERR_cpp_cond_if_match (dir, lex_if);
			report (preproc_loc, err);
			PUSH_unsigned (cond, preproc_stack);
			PUSH_loc (preproc_loc, loc_stack);
			IGNORE incr_value (OPT_VAL_hash_if_depth);
			preproc_depth++;
			cond = c;
		}
		crt_cond = (cond & PP_COND_MASK);
		ret = lex_end_condition;

		if (dir == lex_endif) {
			/* Deal with '#endif' */
			if (crt_cond == PP_UNRESOLVED) {
				ret = lex_hash_Hendif;
				cond = PP_TRUE;
			} else if (crt_cond == PP_SKIP) {
				cond = PP_SKIP;
			} else {
				cond = PP_TRUE;
			}
			POP_loc (loc, loc_stack);
			UNUSED (loc);

		} else if (dir == lex_else) {
			/* Deal with '#else' */
			PTR (LOCATION) ploc;
			ploc = HEAD_list (LIST_stack (loc_stack));
			if (cond & PP_HAVE_ELSE) {
				/* Duplicate '#else' directives */
				ERROR err = ERR_cpp_cond_else_dup (dir, dir, ploc);
				report (preproc_loc, err);
			}
			if (crt_cond == PP_UNRESOLVED) ret = lex_hash_Helse;
			cond = (negate_cond (crt_cond) | PP_HAVE_ELSE);
			PUSH_unsigned (cond, preproc_stack);
			COPY_loc (ploc, preproc_loc);
			if (preproc_depth == 0) {
				/* Can't have '#else' in protection macro */
				protection_macro (NULL_hashid, lex_ignore_token, dir);
			}
			IGNORE incr_value (OPT_VAL_hash_if_depth);
			preproc_depth++;

		} else {
			/* Deal with '#elif' (fairly tricky) */
			unsigned new_cond;
			PTR (LOCATION) ploc;
			ploc = HEAD_list (LIST_stack (loc_stack));
			if (cond & PP_HAVE_ELSE) {
				/* '#elif' after '#else' */
				ERROR err = ERR_cpp_cond_else_dup (dir, lex_else, ploc);
				report (preproc_loc, err);
			}
			if (crt_cond == PP_TRUE || crt_cond == PP_PAST) {
				/* A previous '#if' or '#elif' was true */
				ret = lex_ignore_token;
				IGNORE read_if_exp (0, dir);
				c = PP_PAST;
				new_cond = (c | PP_HAVE_ELIF);
			} else if (crt_cond == PP_FALSE) {
				/* All previous '#if's and '#elif's were false */
				c = read_if_exp (1, dir);
				if (c == PP_UNRESOLVED) ret = lex_hash_Hif;
				new_cond = (c | PP_HAVE_ELIF);
			} else if (crt_cond == PP_UNRESOLVED) {
				/* Unresolved existing condition */
				c = read_if_exp (1, dir);
				if (c == PP_FALSE) {
					/* Overall condition is still unresolved */
					ret = lex_ignore_token;
					new_cond = (crt_cond | PP_HAVE_ELIF);
				} else if (c == PP_TRUE) {
					/* This terminates the conditional */
					ret = lex_hash_Hendif;
					new_cond = (c | PP_HAVE_ELIF);
				} else {
					/* A second unresolved condition */
					ret = lex_hash_Helif;
					new_cond = (c | PP_HAVE_ELIF);
				}
			} else {
				/* Skip this directive */
				ret = lex_ignore_token;
				c = read_if_exp (0, dir);
				new_cond = (c | PP_HAVE_ELIF);
			}
			PUSH_unsigned (new_cond, preproc_stack);
			COPY_loc (ploc, preproc_loc);
			if (preproc_depth == 0) {
				/* Can't have '#elif' in protection macro */
				protection_macro (NULL_hashid, lex_ignore_token, dir);
			}
			IGNORE incr_value (OPT_VAL_hash_if_depth);
			preproc_depth++;
			cond = c;
		}
	}
	ASSERT (!in_preproc_dir);

	/* Step over any unused code */
	cond &= PP_COND_MASK;
	if (cond == PP_FALSE || cond == PP_PAST || cond == PP_SKIP) {
		for (;;) {
			int t;
			unsigned long sp = skip_white (1);
			in_preproc_dir = 1;
			t = read_token ();
			update_column ();
			if (in_preproc_dir) preproc_loc = crt_loc;
			if (t == lex_hash_H2) t = get_digraph (t);
			if (t == lex_hash_H1) {
				/* Scan any nested preprocessing directives */
				int p;
				unsigned long sp2 = skip_white (0);
				update_column ();
				p = read_preproc_dir (0, lex_ignore_token);
				switch (p) {
				case lex_hash_Hif :
				case lex_hash_Helif :
				case lex_hash_Helse :
				case lex_hash_Hendif :
				case lex_end_condition : {
					/* These terminate the current condition */
					if (sp & (WHITE_SPACE | WHITE_ESC_NEWLINE)) {
						report (preproc_loc, ERR_cpp_indent ());
					}
					if (sp2 & (WHITE_SPACE | WHITE_ESC_NEWLINE)) {
						report (preproc_loc, ERR_cpp_indent_dir ());
					}
					in_preproc_dir = 0;
					return (p);
				}
				}
			} else if (t == lex_eof) {
				if (sp & (WHITE_SPACE | WHITE_ESC_NEWLINE)) {
					report (crt_loc, ERR_lex_phases_eof ());
				}
				break;
			} else {
				if (in_preproc_dir) IGNORE skip_to_end ();
			}
		}
	}
	in_preproc_dir = 0;
	return (ret);
}


/*
 *    PATCH TARGET DEPENDENT CONDITIONALS
 *
 *    Any list of statements in a target dependent conditional are treated
 *    as if they comprised a compound statement.  In particular any variables
 *    declared within the conditional are only in scope inside that condition.
 *    The neatest way to do this is for the preprocessor to patch the necessary
 *    open and close braces into the parser input.  This is the purpose of
 *    this routine.
 */

int
patch_cond(int t, int dir)
{
	HASHID nm;
	PPTOKEN *p;
	IDENTIFIER id;

	/* Compilation action */
	if (!preproc_only) {
		switch (t) {
		case lex_hash_Hif : {
			/* Create '#if {' */
			p = patch_tokens (1);
			p->tok = lex_open_Hbrace_H1;
			break;
		}
		case lex_hash_Helif :
		case lex_hash_Helse : {
			/* Create '} #elif {' and '} #else {' */
			p = patch_tokens (2);
			p->tok = t;
			token_parts (t, p);
			p->next->tok = lex_open_Hbrace_H1;
			t = lex_close_Hbrace_H1;
			break;
		}
		case lex_hash_Hendif : {
			/* Create '} #endif' */
			p = patch_tokens (1);
			p->tok = t;
			t = lex_close_Hbrace_H1;
			break;
		}
		}
		return (t);
	}

	/* Preprocessing action */
	id = token_macro;
	if (IS_NULL_id (id)) return (t);
	nm = DEREF_hashid (id_name (id));
	switch (t) {

	case lex_hash_Hif : {
		if (dir == lex_ifdef) {
			/* Create '#if defined x' */
			p = patch_tokens (2);
			p->tok = lex_identifier ,
				p->pp_data.id.hash = KEYWORD (lex_defined);
			p->next->tok = lex_identifier ,
				p->next->pp_data.id.hash = nm;
			p->next->pp_data.id.use = id;
		} else if (dir == lex_ifndef) {
			/* Create '#if !defined x' */
			p = patch_tokens (3);
			p->tok = lex_not_H1;
			p->next->tok = lex_identifier ,
				p->next->pp_data.id.hash = KEYWORD (lex_defined);
			p->next->pp_space = 0;
			p->next->next->tok = lex_identifier ,
				p->next->next->pp_data.id.hash = nm;
			p->next->next->pp_data.id.use = id;
		}
		break;
	}

	case lex_hash_Hop : {
		/* Create '#define x ...' or '#undef x' */
		if (dir == lex_define) {
			/* Patch in macro definition */
			PPTOKEN *q = NULL;
			unsigned tag = TAG_id (id);
			if (tag == id_obj_macro_tag) {
				q = DEREF_pptok (id_obj_macro_defn (id));
			} else if (tag == id_func_macro_tag) {
				q = DEREF_pptok (id_func_macro_defn (id));
			}
			q = expand_tok_list (q);
			q = clean_tok_list (q);
			patch_preproc_dir (q);
			if (tag == id_func_macro_tag) {
				unsigned n;
				LIST (HASHID) pars;
				pars = DEREF_list (id_func_macro_params (id));
				n = DEREF_unsigned (id_func_macro_no_params (id));
				p = patch_tokens ((int) (2 * n + 2));
				p->tok = lex_open_Hround;
				p->pp_space = 0;
				p->next->tok = lex_close_Hround;
				p->next->pp_space = 0;
				while (!IS_NULL_list (pars)) {
					HASHID par = DEREF_hashid (HEAD_list (pars));
					pars = TAIL_list (pars);
					p = p->next;
					p->tok = lex_identifier;
					p->pp_data.id.hash = par;
					p->pp_space = WHITE_SPACE;
					p = p->next;
					if (IS_NULL_list (pars)) {
						p->tok = lex_close_Hround;
						p->pp_space = WHITE_SPACE;
					} else {
						p->tok = lex_comma;
						p->pp_space = 0;
					}
				}
			}
		}
		p = patch_tokens (2);
		p->tok = lex_identifier;
		p->pp_data.id.hash = KEYWORD (dir);
		p->pp_space = 0;
		p->next->tok = lex_identifier ,
			p->next->pp_data.id.hash = nm;
		p->next->pp_data.id.use = id;
		break;
	}
	}
	return (t);
}


/*
 *    READ AN INCLUDE DIRECTIVE
 *
 *    This routine processes a '#include' or similar directive.  This consists
 *    of just a header name or a sequence of tokens which expand to a header
 *    name.  If act is true then the actual inclusion is initialised.
 *    The name of the preprocessing directive, dir, is passed in for the
 *    purposes of error reporting.  The routine returns lex_included to
 *    indicate that control has passed to the new file.
 */

int
read_include(int act, int dir)
{
	int ret = lex_ignore_token;
	if (act) {
		string s;
		character c;
		int end = 0;
		int next = 0;
		int legal = 1;
		int import = INCLUDE_NORMAL;
		character quote = 0;

		/* Look ahead for start of header name */
		if (dir == lex_import) import = INCLUDE_IMPORT;
		if (dir == lex_include_Hnext) next = 1;
		IGNORE skip_white (0);
		if (peek_char (char_less, &legal)) {
			quote = char_greater;
		} else if (peek_char (char_quote, &legal)) {
			quote = char_quote;
		} else if (dir == lex_pragma) {
			if (peek_char (char_open_square, &legal)) {
				quote = char_close_square;
				next = 1;
			}
		}
		update_column ();

		if (quote) {
			/* Read simple header name */
			int e = read_string ((int) quote, 0);
			if (e != lex_eof) {
				if (in_preproc_dir) end = skip_to_end ();
			}
			s = token_buff.start;
		} else {
			/* Expand complex header name */
			PPTOKEN *p = read_line (lex_ignore_token, lex_ignore_token);
			PPTOKEN *q = expand_tok_list (p);
			IGNORE quote_tok_list (q, 0, char_quote);
			s = token_buff.start;

			/* Check first character */
			c = s [0];
			if (c == char_less) {
				quote = char_greater;
			} else if (c == char_quote) {
				quote = char_quote;
			} else if (dir == lex_pragma && c == char_open_square) {
				quote = char_close_square;
				next = 1;
			} else {
				report (preproc_loc, ERR_cpp_include_bad ());
				act = 0;
			}

			/* Scan header name */
			if (quote) {
				string t = ++s;
				for (;;) {
					if (*t == quote) {
						*t = 0;
						if (t + 1 != token_buff.posn) end = 1;
						break;
					}
					if (t == token_buff.posn) {
						/* End of buffer reached */
						report (preproc_loc, ERR_cpp_include_incompl ());
						break;
					}
					t++;
				}
			}
			free_tok_list (p);
			free_tok_list (q);
		}
		if (end) report (preproc_loc, ERR_cpp_end (dir));
		in_preproc_dir = 0;
		if (act) {
			/* Check header name */
			string t = s;
			while (c = *(t++), c != 0) {
				if (c == char_quote ||
					c == char_single_quote || c == char_backslash ||
					(c == char_slash && *t == char_asterix) ||
					(c == char_slash && *t == char_slash)) {
					report (preproc_loc, ERR_cpp_include_undef (s));
					break;
				}
			}
			if (start_include (s, (int) quote, import, next)) {
				/* Control passed to new file */
				ret = lex_included;
			}
		}
	} else {
		/* Ignore rest of line */
		if (in_preproc_dir) IGNORE skip_to_end ();
	}
	return (ret);
}


/*
 *    CHECK THAT TWO PREPROCESSING TOKENS ARE EQUAL
 *
 *    This routine checks whether the lists of preprocessing tokens p and q
 *    are identical.  It returns 2 if they are equal including preceding
 *    white spaces, 1 if they are otherwise equal, and 0 otherwise.
 */

static int
eq_pptok(PPTOKEN *p, PPTOKEN *q)
{
	int eq = 2;
	while (p && q) {
		int t1 = p->tok;
		int t2 = q->tok;
		if (t1 != t2) return (0);
		if (p->pp_space != q->pp_space) eq = 1;
		switch (t1) {
		case lex_identifier : {
			/* Check identifiers */
			HASHID n1 = p->pp_data.id.hash;
			HASHID n2 = q->pp_data.id.hash;
			if (!EQ_hashid (n1, n2)) return (0);
			break;
		}
		case lex_integer_Hlit : {
			/* Check integer and floating-point literals */
			string s1 = p->pp_data.text;
			string s2 = q->pp_data.text;
			if (!ustreq (s1, s2)) return (0);
			break;
		}
		case lex_char_Hlit :
		case lex_string_Hlit :
		case lex_wchar_Hlit :
		case lex_wstring_Hlit : {
			/* Check string and characters literals */
			string s1 = p->pp_data.str.start;
			string s2 = q->pp_data.str.start;
			size_t n1 = (size_t) (p->pp_data.str.end - s1);
			size_t n2 = (size_t) (q->pp_data.str.end - s2);
			if (n1 != n2) return (0);
			if (xumemcmp (s1, s2, n1) != 0) return (0);
			break;
		}
		case lex_unknown : {
			/* Check unknown characters */
			string s1 = p->pp_data.buff;
			string s2 = q->pp_data.buff;
			size_t n1 = MULTI_WIDTH;
			if (xumemcmp (s1, s2, n1) != 0) return (0);
			break;
		}
		case lex_macro_Harg : {
			/* Check macro parameter applications */
			unsigned long m1 = p->pp_data.par.no;
			unsigned long m2 = q->pp_data.par.no;
			if (m1 != m2) return (0);
			break;
		}
		}
		p = p->next;
		q = q->next;
	}
	if (p || q) return (0);
	return (eq);
}


/*
 *    CHECK CONSISTENCY OF TWO MACRO DEFINITIONS
 *
 *    This routine checks that a definition of the macro given by id_new is
 *    consistent with the previous definition, id_old.  It returns an error
 *    message reporting on the level of consistency.
 */

static ERROR
check_macro_redef(IDENTIFIER id_new, IDENTIFIER id_old)
{
	int defn_ok;
	int pars_ok = 1;
	ERROR err = NULL_err;
	PTR (LOCATION) loc_old;
	PPTOKEN *defn_new, *defn_old;
	unsigned tag_new = TAG_id (id_new);
	unsigned tag_old = TAG_id (id_old);
	DECL_SPEC ds_old = DEREF_dspec (id_storage (id_old));

	/* Check on old macro definition */
	loc_old = id_loc (id_old);
	if ((ds_old & dspec_builtin) && crt_file_type == 0) {
		/* Built-in macro redefined */
		err = ERR_cpp_predef_redef (id_old);
	}

	/* Macro types must agree */
	if (tag_new != tag_old) {
		ERROR e = ERR_cpp_replace_redef_bad (id_old, loc_old);
		err = concat_error (err, e);
		return (err);
	}

	if (tag_new == id_obj_macro_tag) {
		/* Find the definitions for object-like macros */
		defn_new = DEREF_pptok (id_obj_macro_defn (id_new));
		defn_old = DEREF_pptok (id_obj_macro_defn (id_old));

	} else {
		/* Check parameter lists for function-like macros */
		unsigned no_pars_new, no_pars_old;
		LIST (HASHID) pars_new, pars_old;
		pars_new = DEREF_list (id_func_macro_params (id_new));
		pars_old = DEREF_list (id_func_macro_params (id_old));
		no_pars_new = DEREF_unsigned (id_func_macro_no_params (id_new));
		no_pars_old = DEREF_unsigned (id_func_macro_no_params (id_old));

		if (no_pars_new != no_pars_old) {
			/* Number of parameters must match */
			ERROR e = ERR_cpp_replace_redef_bad (id_old, loc_old);
			err = concat_error (err, e);
			return (err);
		}

		while (!IS_NULL_list (pars_new)) {
			/* Check that parameter names match */
			HASHID p_new = DEREF_hashid (HEAD_list (pars_new));
			HASHID p_old = DEREF_hashid (HEAD_list (pars_old));
			if (!EQ_hashid (p_new, p_old)) {
				/* Just clear pars_ok if they don't */
				pars_ok = 0;
				break;
			}
			pars_new = TAIL_list (pars_new);
			pars_old = TAIL_list (pars_old);
		}

		/* Find the definitions for function-like macros */
		defn_new = DEREF_pptok (id_func_macro_defn (id_new));
		defn_old = DEREF_pptok (id_func_macro_defn (id_old));
	}

	/* Check that the definitions match */
	defn_ok = eq_pptok (defn_new, defn_old);
	if (defn_ok == 0) {
		/* Inconsistent redefinition */
		ERROR e = ERR_cpp_replace_redef_bad (id_old, loc_old);
		err = concat_error (err, e);
		return (err);
	} else if (defn_ok == 1) {
		/* Consistent redefinition up to white space */
		ERROR e = ERR_cpp_replace_redef_space (id_old, loc_old);
		e = set_severity (e, OPT_macro_redef, -1);
		err = concat_error (err, e);
	}

	/* Prepare final error */
	if (pars_ok) {
		/* Consistent macro redefinition */
		if (IS_NULL_err (err)) {
			err = ERR_cpp_replace_redef_ok (id_old, loc_old);
		}
	} else {
		/* Consistent redefinition up to parameter names */
		ERROR e = ERR_cpp_replace_redef_weak (id_old, loc_old);
		e = set_severity (e, OPT_macro_redef, -1);
		err = concat_error (err, e);
	}
	return (err);
}


/*
 *    FREE A MACRO DEFINITION
 *
 *    This routine frees the macro definition given by the identifier id.
 *    It returns the previous definition of id.
 */

static IDENTIFIER
free_macro_defn(IDENTIFIER id)
{
	PPTOKEN *defn;
	IDENTIFIER prev = DEREF_id (id_alias (id));
	if (IS_id_obj_macro (id)) {
		defn = DEREF_pptok (id_obj_macro_defn (id));
		COPY_pptok (id_obj_macro_defn (id), NULL);
	} else {
		defn = DEREF_pptok (id_func_macro_defn (id));
		COPY_pptok (id_func_macro_defn (id), NULL);
	}
	free_tok_list (defn);
	return (prev);
}


/*
 *    READ A DEFINE DIRECTIVE
 *
 *    This routine processes a '#define' directive.  This consists of a macro
 *    identifier, and optional list of macro parameters, and a list of token
 *    comprising the macro definition.  Note that the list of parameters is
 *    built up in the reverse order to that in which they appear in the file
 *    (also see read_macro_args).  The routine returns true if the directive
 *    is a macro definition.
 */

static int
read_define(void)
{
	HASHID macro;
	PPTOKEN *defn;
	int legal = 1;
	IDENTIFIER id;
	IDENTIFIER tok;
	IDENTIFIER prev;
	unsigned prev_def;
	unsigned npars = 0;
	int object_like = 0;
	int va_macro = 0;
	int ret = lex_ignore_token;
	int first_tok = lex_ignore_token;
	LIST (HASHID) pars = NULL_list (HASHID);
	OPTION preproc_strings = option (OPT_preproc_old);
	PPTOKEN *p;

	/* Read the macro identifier */
	int t = read_token ();
	update_column ();
	if (in_preproc_dir) preproc_loc = crt_loc;
	if (t != lex_identifier) {
		report (preproc_loc, ERR_cpp_replace_id (lex_define));
		return (ret);
	}
	macro = token_hashid;
	if (EQ_KEYWORD (macro, lex_defined)) {
		/* Cannot define defined as a macro */
		report (preproc_loc, ERR_cpp_predef_bad (macro, lex_define));
	} else {
		id = DEREF_id (hashid_id (macro));
		if (IS_id_keyword (id) && !preproc_only) {
			/* Warn about redefining keywords */
			report (preproc_loc, ERR_cpp_predef_keyword (macro));
		}
	}
	prev_def = check_macro (macro, 0);
	tok = token_macro;

	/* Check for macro parameters */
	if (peek_char (char_open_round, &legal)) {
		int err = 0;
		int last_tok = lex_open_Hround;
		LIST (HASHID) lp;
		unsigned long par_no = 1;

		/* Scan through definition looking for parameters */
		update_column ();
		if (in_preproc_dir) preproc_loc = crt_loc;
		while (t = read_token (), t != lex_close_Hround) {
			update_column ();
			if (in_preproc_dir) preproc_loc = crt_loc;
			if (t == lex_identifier || t == lex_ellipsis) {
				/* Identifiers are parameter names */
				unsigned long mark;
				HASHID par = token_hashid;
				if (t == lex_ellipsis) {
					if (option (OPT_va_macro) == OPTION_DISALLOW) {
						first_tok = t;
						err = 1;
						break;
					}
					par = KEYWORD (lex_va_Hargs);
					va_macro = 1;
				}
				IGNORE check_macro (par, 0);
				CONS_hashid (par, pars, pars);

				/* Mark name with parameter number */
				mark = DEREF_ulong (hashid_hash (par));
				if (mark >= HASH_SIZE) {
					/* Parameter already marked */
					ERROR e = ERR_cpp_replace_par_dup (par, macro);
					report (preproc_loc, e);
					mark %= HASH_SIZE;
				}
				mark += HASH_SIZE * par_no;
				COPY_ulong (hashid_hash (par), mark);
				if (last_tok != lex_open_Hround && last_tok != lex_comma) {
					err = 1;
				}
				par_no++;
			} else if (t == lex_comma) {
				/* Commas separate parameters */
				if (last_tok != lex_identifier) err = 1;
			} else {
				/* Anything else is an error */
				first_tok = t;
				err = 1;
				break;
			}
			last_tok = t;
		}
		update_column ();
		if (in_preproc_dir) preproc_loc = crt_loc;
		if (err || last_tok == lex_comma) {
			/* Report any errors */
			report (preproc_loc, ERR_cpp_replace_par_bad (macro));
		}

		/* Allow for parameter expansion in strings */
		if (preproc_strings) {
			/* This causes strings not to be recognised */
			set_char_lookup (char_quote, char_illegal);
			set_char_lookup (char_single_quote, char_illegal);
		}

		/* Read the macro definition for function-like macros */
		defn = read_line (first_tok, lex_ignore_token);
		if (defn) defn->pp_space = WHITE_SPACE;

		/* Restore string terminators */
		if (preproc_strings) {
			set_char_lookup (char_quote, char_quote);
			set_char_lookup (char_single_quote, char_single_quote);
		}

		/* Mark the macro parameters in the definition */
		for (p = defn; p != NULL; p = p->next) {
			int tk = p->tok;
			if (tk == lex_identifier) {
				HASHID par = p->pp_data.id.hash;
				unsigned long mark = DEREF_ulong (hashid_hash (par));
				if (mark >= HASH_SIZE) {
					/* Parameters are identified by the parameter number */
					p->tok = lex_macro_Harg;
					p->pp_data.par.hash = par;
					p->pp_data.par.no = (mark / HASH_SIZE);
				}
			}
		}

		/* Check for quoted parameters */
		if (preproc_strings) {
			defn = recognise_strings (defn, macro, 0);
		}

		/* Check for '#' operators */
		for (p = defn; p != NULL; p = p->next) {
			int tk = p->tok;
			if (tk == lex_hash_H2) tk = get_digraph (tk);
			if (tk == lex_hash_H1) {
				/* '#' should be followed by a parameter */
				if (p->next == NULL || p->next->tok != lex_macro_Harg) {
					report (preproc_loc, ERR_cpp_stringize_par (macro));
				} else {
					p->tok = lex_hash_Hop;
				}
			}
		}

		/* Clear the parameter marks */
		for (lp = pars; !IS_NULL_list (lp); lp = TAIL_list (lp)) {
			HASHID par = DEREF_hashid (HEAD_list (lp));
			unsigned long mark = DEREF_ulong (hashid_hash (par));
			mark %= HASH_SIZE;
			COPY_ulong (hashid_hash (par), mark);
			npars++;
		}
		pars = REVERSE_list (pars);

	} else {
		/* Read the macro definition for object-like macros */
		ERROR err = NULL_err;
		if (!(skip_white (0) & WHITE_SPACE)) err = ERR_cpp_space_replace2 ();
		if (!legal && err == NULL_err) err = ERR_cpp_space_replace ();
		defn = read_line (first_tok, lex_ignore_token);
		if (defn != NULL) {
			report (preproc_loc, err);
		} else {
			destroy_error (err, 1);
		}
		object_like = 1;
	}

	/* Complain about '__VA_ARGS__' in object-like or non-varargs macros */
	if (!va_macro && option (OPT_va_macro) == OPTION_ALLOW) {
		for (p = defn; p != NULL; p = p->next)
			if (p->tok == lex_identifier &&
				EQ_KEYWORD (p->pp_data.id.hash, lex_va_Hargs)) {
				report (preproc_loc, ERR_cpp_replace_va_args_bad (macro));
				break;
			}
	}

	/* Check for '##' operators */
	if (defn) {
		int tk = defn->tok;
		if (tk == lex_hash_Hhash_H2) tk = get_digraph (tk);
		if (tk == lex_hash_Hhash_H1) {
			/* Definition can't start with '##' */
			report (preproc_loc, ERR_cpp_concat_place (macro));
		}
		for (p = defn->next; p != NULL; p = p->next) {
			tk = p->tok;
			if (tk == lex_hash_Hhash_H2) tk = get_digraph (tk);
			if (tk == lex_hash_Hhash_H1) {
				if (p->next == NULL) {
					/* Definition can't end with '##' */
					report (preproc_loc, ERR_cpp_concat_place (macro));
				} else {
					p->tok = lex_hash_Hhash_Hop;
				}
			}
		}
	}

	/* Define the macro */
	if (!IS_NULL_exp (crt_hash_cond)) {
		report (preproc_loc, ERR_cpp_cond_if_macro (macro));
	}
	prev = DEREF_id (hashid_id (macro));
	if (object_like) {
		MAKE_id_obj_macro (macro, dspec_defn, NULL_nspace, preproc_loc,
						   defn, id);
	} else {
		IGNORE check_value (OPT_VAL_macro_pars, (ulong) npars);
		MAKE_id_func_macro (macro, dspec_defn, NULL_nspace, preproc_loc,
							defn, pars, npars, va_macro, id);
	}
	COPY_id (id_alias (id), prev);
	if (prev_def & PP_TOKEN) {
		/* Allow for token definitions */
		prev_def &= PP_COND_MASK;
		if (prev_def == PP_UNRESOLVED) {
			/* Can only happen when preprocessing */
			token_macro = id;
			ret = lex_hash_Hop;
		} else {
			int tokdef;
			DECL_SPEC ds = DEREF_dspec (id_storage (id));
			COPY_dspec (id_storage (id), (ds | dspec_temp));
			tokdef = define_token_macro (tok, id);
			COPY_dspec (id_storage (id), ds);
			if (tokdef) {
				IGNORE free_macro_defn (id);
				no_declarations++;
				return (ret);
			}
			prev_def = PP_FALSE;
		}
	}
	COPY_id (hashid_id (macro), id);
	if (do_macro) {
		/* Dump macro definition using current namespace */
		COPY_nspace (id_parent (id), crt_namespace);
		dump_declare (id, &preproc_loc, 1);
		COPY_nspace (id_parent (id), NULL_nspace);
	}

	/* Check consistency of previous definition */
	if (prev_def == PP_TRUE) {
		ERROR err;
		if (option (OPT_macro_nest) == OPTION_DISALLOW) {
			err = check_macro_redef (id, prev);
			prev = free_macro_defn (prev);
		} else {
			PTR (LOCATION) loc = id_loc (prev);
			err = ERR_cpp_replace_redef_nest (prev, loc);
		}
		if (!IS_NULL_err (err)) report (preproc_loc, err);
		COPY_id (id_alias (id), prev);
	} else {
		IGNORE incr_value (OPT_VAL_macro_ids);
	}
	return (ret);
}


/*
 *    READ AN UNDEFINE DIRECTIVE
 *
 *    This routine processes a '#undef' directive.  This just consists of a
 *    macro identifier.  The routine returns true if the macro represents
 *    a token.
 */

static int
read_undef(void)
{
	/* Read the macro identifier */
	unsigned def;
	HASHID macro;
	int ret = lex_ignore_token;
	int t = read_token ();
	update_column ();
	if (in_preproc_dir) preproc_loc = crt_loc;
	if (t != lex_identifier) {
		report (preproc_loc, ERR_cpp_scope_id (lex_undef));
		return (ret);
	}
	macro = token_hashid;
	if (EQ_KEYWORD (macro, lex_defined)) {
		/* Cannot undefine defined */
		report (preproc_loc, ERR_cpp_predef_bad (macro, lex_undef));
	}

	/* Undefine the macro if necessary */
	def = check_macro (macro, 0);
	if (def == PP_TRUE) {
		/* Previously defined as macro */
		IDENTIFIER prev = DEREF_id (hashid_id (macro));
		DECL_SPEC ds = DEREF_dspec (id_storage (prev));
		if ((ds & dspec_builtin) && crt_file_type == 0) {
			report (preproc_loc, ERR_cpp_predef_undef (prev));
		}
		if (do_macro) dump_undefine (prev, &preproc_loc, 1);
		prev = free_macro_defn (prev);
		COPY_id (hashid_id (macro), prev);
		decr_value (OPT_VAL_macro_ids);

	} else if (def & PP_TOKEN) {
		/* Previously defined as token */
		IDENTIFIER prev = token_macro;
		def &= PP_COND_MASK;
		if (def == PP_UNRESOLVED) {
			/* Can only happen when preprocessing */
			ret = lex_hash_Hop;
		} else {
			if (IS_id_function_etc (prev)) {
				do {
					DECL_SPEC ds = DEREF_dspec (id_storage (prev));
					TYPE f = DEREF_type (id_function_etc_form (prev));
					if (!IS_NULL_type (f) && IS_type_token (f)) {
						IDENTIFIER ext = DEREF_id (type_token_tok (f));
						if (!IS_NULL_id (ext) && IS_id_token (ext)) {
							if (do_dump) {
								dump_undefine (prev, &preproc_loc, 1);
							}
							f = NULL_type;
							COPY_type (id_function_etc_form (prev), f);
						}
					}
					ds &= ~dspec_token;
					COPY_dspec (id_storage (prev), ds);
					prev = DEREF_id (id_function_etc_over (prev));
				} while (!IS_NULL_id (prev));
			} else {
				if (do_dump) dump_undefine (prev, &preproc_loc, 1);
				remove_id (prev);
			}
		}

	} else {
		/* Not previously declared */
		report (preproc_loc, ERR_cpp_scope_undef (macro));
	}

	/* Check the rest of the directive */
	if (in_preproc_dir && skip_to_end ()) {
		report (preproc_loc, ERR_cpp_end (lex_undef));
	}
	return (ret);
}


/*
 *    READ A LINE DIRECTIVE
 *
 *    This routine processes a '#line' or '#file' directive (as indicated
 *    by dir).
 */

static void
read_location(int dir)
{
	/* Read the line */
	PPTOKEN *p = read_line (lex_ignore_token, lex_ignore_token);
	unsigned long ln = crt_loc.line;
	string fn = DEREF_string (posn_file (crt_loc.posn));
	unsigned long ln_old = ln;
	string fn_old = fn;

	/* Macro expand the line */
	PPTOKEN *q = expand_tok_list (p);
	q = clean_tok_list (q);
	if (q && q->tok == lex_integer_Hlit && dir == lex_line) {
		/* Process '#line number string-opt' */
		unsigned err = 0;
		PPTOKEN *r = q->next;
		ln = eval_line_digits (q->pp_data.text, &err);
		if (ln != ln_old) crt_line_changed = 1;
		if (err & 2) {
			report (preproc_loc, ERR_cpp_line_float (dir));
		}
		if ((err & 1) || (ln == 0)) {
			report (preproc_loc, ERR_cpp_line_range (dir));
		}
		if (r && r->tok == lex_string_Hlit) {
			fn = r->pp_data.str.start;
			if (!ustreq (fn, fn_old)) {
				crt_file_changed = 1;
				crt_line_changed = 1;
			}
			r = r->next;
		}
		if (r) report (preproc_loc, ERR_cpp_end (dir));
	} else if (q && q->tok == lex_string_Hlit && dir == lex_file) {
		/* Process '#file string' */
		PPTOKEN *r = q->next;
		fn = q->pp_data.str.start;
		if (!ustreq (fn, fn_old)) {
			crt_file_changed = 1;
			crt_line_changed = 1;
		}
		if (r) report (preproc_loc, ERR_cpp_end (dir));
	} else {
		report (preproc_loc, ERR_cpp_line_bad (dir));
	}
	free_tok_list (p);
	free_tok_list (q);
	if (crt_line_changed) {
		PTR (POSITION) posn = crt_loc.posn;
		string d = DEREF_string (posn_dir (posn));
		string input = DEREF_string (posn_input (posn));
		unsigned long off = DEREF_ulong (posn_offset (posn));
		unsigned long date = DEREF_ulong (posn_datestamp (posn));
		PTR (LOCATION) from = DEREF_ptr (posn_from (posn));
		off += (ln - ln_old);
		posn = MAKE_ptr (SIZE_posn);
		MAKE_posn (fn, 0, input, fn, d, off, from, date, posn);
		crt_loc.posn = posn;
	}
	crt_loc.line = ln;
	crt_loc.column = 0;
	return;
}


/*
 *    READ AN ERROR DIRECTIVE
 *
 *    This routine processes a '#error' or '#warning' directive (as indicated
 *    by the error severity level sev).
 */

static void
read_error(int opt)
{
	ERROR err;
	PPTOKEN *p = read_line (lex_ignore_token, lex_ignore_token);
	IGNORE quote_tok_list (p, 0, char_quote);
	err = ERR_cpp_error_msg (token_buff.start);
	if (!IS_NULL_err (err)) {
		err = set_severity (err, opt, 0);
		report (preproc_loc, err);
	}
	free_tok_list (p);
	return;
}


/*
 *    READ AN IDENT DIRECTIVE
 *
 *    This routine processes a '#ident' directive.
 */

void
read_ident(int dir)
{
	int t = read_token ();
	update_column ();
	if (in_preproc_dir) preproc_loc = crt_loc;
	if (t == lex_string_Hlit) {
		string s = token_buff.start;
		unsigned long n = (unsigned long) (token_buff.posn - s);
		compile_comment (s, n);
		if (in_preproc_dir && skip_to_end ()) {
			report (preproc_loc, ERR_cpp_end (dir));
		}
	} else {
		report (preproc_loc, ERR_pragma_cpp_ident (dir));
	}
	return;
}


/*
 *    CREATE AN ASSERTION
 *
 *    This routine looks up the assertion named pred, creating it if it does
 *    not already exist.
 */

IDENTIFIER
make_assert(HASHID pred, int key)
{
	NAMESPACE ns = assert_namespace;
	MEMBER mem = search_member (ns, pred, 1);
	IDENTIFIER id = DEREF_id (member_id (mem));
	if (IS_NULL_id (id)) {
		/* Define assertion if not already defined */
		MAKE_id_predicate (pred, dspec_none, ns, preproc_loc, id);
		COPY_ulong (id_no (id), (unsigned long) key);
		COPY_id (member_id (mem), id);
	}
	return (id);
}


/*
 *    SKIP A PREDICATE
 *
 *    This routine skips a predicate given by the list of preprocessing
 *    tokens p.  It is entered after the initial open bracket and returns
 *    the token after the matching close bracket.
 */

static PPTOKEN *
skip_predicate(PPTOKEN **p, int dir)
{
	PPTOKEN *q = *p;
	PPTOKEN *r = NULL;
	int bracket = 1;
	if (q) q->pp_space = 0;
	while (q) {
		int t = q->tok;
		if (t == lex_open_Hround) {
			/* Increase bracket count */
			bracket++;
		} else if (t == lex_close_Hround) {
			/* Decrease bracket count */
			if (--bracket == 0) {
				if (r) {
					r->next = NULL;
				} else if (dir == lex_define || dir == lex_undef) {
					*p = NULL;
				} else {
					report (preproc_loc, ERR_pragma_assert_empty (dir));
					*p = NULL;
				}
				r = q->next;
				q->next = free_tokens;
				free_tokens = q;
				return (r);
			}
		} else if (t == lex_newline) {
			/* Terminate if a newline is reached */
			report (preproc_loc, ERR_pragma_assert_paren (dir));
			if (r) {
				r->next = NULL;
			} else {
				report (preproc_loc, ERR_pragma_assert_empty (dir));
				*p = NULL;
			}
			return (q);
		}
		r = q;
		q = q->next;
	}
	report (preproc_loc, ERR_pragma_assert_paren (dir));
	return (NULL);
}


/*
 *    CHECK A PREDICATE VALUE
 *
 *    This routine checks whether the value p, or any value if def is true,
 *    has been defined in the predicate pred.
 */

static int
check_assert(HASHID pred, PPTOKEN *p, int def)
{
	IDENTIFIER id = make_assert (pred, lex_unknown);
	int key = (int) DEREF_ulong (id_no (id));
	LIST (PPTOKEN_P) s = DEREF_list (id_predicate_values (id));
	report (preproc_loc, ERR_pragma_assert_pred (pred));
	if (def) {
		/* Check for any predicate */
		if (!IS_NULL_list (s)) return (1);
	} else {
		while (!IS_NULL_list (s)) {
			PPTOKEN *q = DEREF_pptok (HEAD_list (s));
			if (eq_pptok (p, q) == 2) return (1);
			s = TAIL_list (s);
		}
	}
	if (key == lex_include) {
		/* '#include' checks for included files */
		if (def) return (1);
		IGNORE quote_tok_list (p, 0, char_quote);
		return (start_include (token_buff.start, char_quote, INCLUDE_CHECK, 0));
	}
	if (key == lex_keyword) {
		/* '#keyword' checks for keywords */
		if (def) return (1);
		if (p && p->next == NULL) {
			if (p->tok == lex_identifier) {
				IDENTIFIER pid = p->pp_data.id.use;
				if (IS_id_keyword (pid)) return (1);
				if (IS_id_iso_keyword (pid)) return (1);
			}
		}
	}
	if (key == lex_option) {
		/* '#option' checks for options */
		int n;
		ulong sn;
		string sb;
		static STRING str = NULL_str;
		if (def) return (1);
		IGNORE quote_tok_list (p, 0, char_quote);
		sb = token_buff.start;
		sn = (ulong) (token_buff.posn - sb);
		if (IS_NULL_str (str)) {
			MAKE_str_simple (sn, sb, STRING_NONE, str);
		} else {
			COPY_string (str_simple_text (str), sb);
			COPY_ulong (str_simple_len (str), sn);
		}
		n = find_option_no (str, 0);
		if (n != -1 && option (n)) return (1);
	}
	return (0);
}


/*
 *    SET A PREDICATE VALUE
 *
 *    This routine sets the assertion value of the preprocessing tokens p
 *    in the predicate id to be def.
 */

static void
set_assert(IDENTIFIER id, PPTOKEN *p, int def)
{
	LIST (PPTOKEN_P) s;
	PTR (LIST (PPTOKEN_P)) ps = id_predicate_values (id);
	LIST (PPTOKEN_P) r = DEREF_list (ps);
	while (s = DEREF_list (ps), !IS_NULL_list (s)) {
		PPTOKEN *q = DEREF_pptok (HEAD_list (s));
		if (eq_pptok (p, q) == 2) {
			/* Already asserted */
			if (!def) {
				DESTROY_CONS_pptok (destroy, q, s, s);
				COPY_list (ps, s);
				free_tok_list (q);
			}
			free_tok_list (p);
			return;
		}
		ps = PTR_TAIL_list (s);
	}
	if (def) {
		/* Create assertion */
		CONS_pptok (p, r, r);
		COPY_list (id_predicate_values (id), r);
	} else {
		free_tok_list (p);
	}
	return;
}


/*
 *    READ AN ASSERT DIRECTIVE
 *
 *    This routine processes a '#assert' directive.
 */

static void
read_assert(int dir)
{
	/* Read the predicate name */
	int def = 1;
	IDENTIFIER id;
	PPTOKEN *p, *q;
	int t = read_token ();
	update_column ();
	if (in_preproc_dir) preproc_loc = crt_loc;
	if (t != lex_identifier) {
		report (preproc_loc, ERR_pragma_assert_id (dir));
		return;
	}
	id = make_assert (token_hashid, lex_unknown);

	/* Read the predicate token sequence */
	t = read_token ();
	update_column ();
	if (in_preproc_dir) preproc_loc = crt_loc;
	if (t != lex_open_Hround) {
		report (preproc_loc, ERR_pragma_assert_open (dir));
		return;
	}
	p = read_line (lex_ignore_token, lex_ignore_token);
	q = skip_predicate (&p, dir);
	if (q) {
		report (preproc_loc, ERR_cpp_end (dir));
		free_tok_list (q);
	}

	/* Create the assertion */
	if (p) set_assert (id, p, def);
	return;
}


/*
 *    READ AN UNASSERT DIRECTIVE
 *
 *    This routine processes a '#unassert' directive.
 */

static void
read_unassert(int dir)
{
	/* Read the predicate name */
	IDENTIFIER id;
	PPTOKEN *p, *q;
	int t = read_token ();
	update_column ();
	if (in_preproc_dir) preproc_loc = crt_loc;
	if (t != lex_identifier) {
		report (preproc_loc, ERR_pragma_assert_id (dir));
		return;
	}
	id = make_assert (token_hashid, lex_unknown);

	/* Check for simple identifier */
	t = read_token ();
	update_column ();
	if (in_preproc_dir) preproc_loc = crt_loc;
	if (t == lex_newline) {
		p = NULL;
	} else {
		/* Read the predicate token sequence */
		if (t != lex_open_Hround) {
			report (preproc_loc, ERR_pragma_assert_open (dir));
			return;
		}
		p = read_line (lex_ignore_token, lex_ignore_token);
		q = skip_predicate (&p, dir);
		if (q) {
			report (preproc_loc, ERR_cpp_end (dir));
			free_tok_list (q);
		}
	}

	if (p == NULL) {
		/* Unassert all values */
		LIST (PPTOKEN_P) r;
		r = DEREF_list (id_predicate_values (id));
		while (!IS_NULL_list (r)) {
			DESTROY_CONS_pptok (destroy, q, r, r);
			free_tok_list (q);
		}
		COPY_list (id_predicate_values (id), r);
		COPY_ulong (id_no (id), lex_unknown);
	} else {
		/* Destroy the assertion */
		set_assert (id, p, 0);
	}
	return;
}


/*
 *    LOOK UP AN IDENTIFIER IN A PRAGMA WEAK DIRECTIVE
 *
 *    This routine looks up the identifier named nm used in a '#pragma
 *    weak' directive.  If succeed is 1, a dummy identifier will be returned
 *    even if nm was not found.  Otherwise the result should be an external
 *    variable or function.  The null identifier is returned to indicate an
 *    error.
 */

static IDENTIFIER
find_weak_id(HASHID nm, int succeed)
{
	if (!IS_NULL_hashid (nm)) {
		ERROR err;
		IDENTIFIER id = find_id (nm);
		switch (TAG_id (id)) {
		case id_variable_tag : {
			DECL_SPEC ds = DEREF_dspec (id_storage (id));
			if (!(ds & dspec_auto)) {
				ds |= dspec_used;
				COPY_dspec (id_storage (id), ds);
				return (id);
			}
			break;
		}
		case id_function_tag : {
			TYPE t = DEREF_type (id_function_type (id));
			IDENTIFIER over = DEREF_id (id_function_over (id));
			if (IS_NULL_id (over) && IS_type_func (t)) {
				DECL_SPEC ds = DEREF_dspec (id_storage (id));
				ds |= dspec_used;
				COPY_dspec (id_storage (id), ds);
				return (id);
			}
		}
		case id_dummy_tag : {
			if (succeed) return (id);
			break;
		}
		}
		err = ERR_pragma_preserve_undecl (lex_weak, id);
		report (preproc_loc, err);
	}
	return (NULL_id);
}


/*
 *    READ A PRAGMA WEAK DIRECTIVE
 *
 *    This routine processes a '#pragma weak' directive.
 */

void
read_weak(int dir)
{
	IDENTIFIER id = NULL_id;
	IDENTIFIER aid = NULL_id;
	int t = read_token ();
	update_column ();
	if (in_preproc_dir) preproc_loc = crt_loc;
	if (t == lex_identifier) {
		id = find_weak_id (token_hashid, 1);
		t = read_token ();
		update_column ();
		if (in_preproc_dir) preproc_loc = crt_loc;
		if (t == lex_assign) {
			t = read_token ();
			update_column ();
			if (in_preproc_dir) preproc_loc = crt_loc;
			if (t == lex_identifier) {
				aid = find_weak_id (token_hashid, 0);
				t = read_token ();
				update_column ();
				if (in_preproc_dir) preproc_loc = crt_loc;
			}
		}
	} else {
		t = lex_unknown;
	}
	if (!IS_NULL_id (aid)) {
		static LIST (IDENTIFIER) weak_ids = NULL_list (IDENTIFIER);
		LIST (IDENTIFIER) p = weak_ids;
		while (!IS_NULL_list (p)) {
			IDENTIFIER pid = DEREF_id (HEAD_list (p));
			if (EQ_id (id, pid)) {
				report (preproc_loc, ERR_pragma_weak_redef (id));
				id = NULL_id;
				break;
			}
			p = TAIL_list (p);
		}
		if (!IS_NULL_id (id)) {
			CONS_id (id, weak_ids, weak_ids);
		}
	}
	add_weak_dir (id, aid);
	if (t != lex_newline) {
		report (preproc_loc, ERR_cpp_end (dir));
	}
	if (in_preproc_dir) IGNORE skip_to_end ();
	return;
}


/*
 *    READ A PREPROCESSING DIRECTIVE
 *
 *    This routine processes a preprocessing directive up to and including
 *    the terminating newline character.  The act argument indicates
 *    whether the directive is active or is being skipped in a conditional
 *    compilation.  prev gives the previous preprocessing directive value.
 *    The routine returns a lexical token value which is used to
 *    communicate the effect of the directive on the main processor.  Most
 *    directives are only visible to the preprocessor, and return one of
 *    the values lex_ignore_token, lex_end_condition or lex_included (note
 *    that these are all less than zero).  Other directives (for example,
 *    target dependent conditionals) do communicate with the main
 *    processor by returning a valid lexical token value.
 */

int
read_preproc_dir(int act, int prev)
{
	int t;
	ERROR err;
	HASHID dir;
	int pp = lex_ignore_token;
	in_preproc_dir = 1;
	preproc_loc = crt_loc;

	/* Read the token following the '#' */
	t = read_token ();
	update_column ();
	if (in_preproc_dir) preproc_loc = crt_loc;
	if (t != lex_identifier) {
		if (!act) goto end_label;
		if (t == lex_newline || t == lex_eof) {
			/* Warn about empty directives */
			report (preproc_loc, ERR_cpp_null ());
			goto end_label;
		}
		/* Give an error for other directives */
		report (preproc_loc, ERR_cpp_bad ());
		goto end_label;
	}

	/* Analyse the '#identifier' directive */
	dir = token_hashid;
	t = find_hashid (dir);
	switch (t) {

	case lex_define : {
		/* Deal with '#define' */
		if (act) {
			pp = read_define ();
			if (pp >= 0) pp = patch_cond (pp, t);
		}
		goto end_label;
	}

	case lex_else :
	case lex_endif : {
		/* Deal with '#else' and '#endif' */
		if (in_preproc_dir && skip_to_end ()) {
			/* Check end of directive */
			report (preproc_loc, ERR_cpp_cond_endif_end (t));
		}
		pp = read_if (t, PP_FALSE, lex_ignore_token);
		if (act && pp >= 0) pp = patch_cond (pp, t);
		return (pp);
	}

	case lex_elif : {
		/* Deal with '#elif' (expression is read in read_if) */
		report (preproc_loc, ERR_cpp_old (t));
		pp = read_if (t, PP_FALSE, lex_ignore_token);
		if (act && pp >= 0) pp = patch_cond (pp, t);
		return (pp);
	}

	case lex_error : {
		/* Deal with '#error' */
		if (!act) goto end_label;
		report (preproc_loc, ERR_cpp_old (t));
		read_error (OPT_error);
		goto end_label;
	}

	case lex_include : {
		/* Deal with '#include' */
		if (!act) goto end_label;
		pp = read_include (act, t);
		return (pp);
	}

	case lex_if : {
		/* Deal with '#if' */
		unsigned cond = read_if_exp (act, t);
		pp = read_if (t, cond, lex_ignore_token);
		if (act && pp >= 0) pp = patch_cond (pp, t);
		return (pp);
	}

	case lex_ifdef : {
		/* Deal with '#ifdef' */
		unsigned cond = read_if_def (act, t, prev);
		pp = read_if (t, cond, prev);
		if (act && pp >= 0) pp = patch_cond (pp, t);
		return (pp);
	}

	case lex_ifndef : {
		/* Deal with '#ifndef' */
		unsigned cond = read_if_def (act, t, prev);
		pp = read_if (t, negate_cond (cond), prev);
		if (act && pp >= 0) pp = patch_cond (pp, t);
		return (pp);
	}

	case lex_line : {
		/* Deal with '#line' */
		if (act) read_location (t);
		goto end_label;
	}

	case lex_pragma : {
		/* Deal with '#pragma' */
		if (act) {
			int ts = have_type_specifier;
			int td = have_type_declaration;
			int fd = have_func_declarator;
			QUALIFIER cq = crt_id_qualifier;
			in_pragma_dir = 1;
			pp = read_pragma ();
			in_pragma_dir = 0;
			pragma_number = 0;
			crt_id_qualifier = cq;
			have_type_specifier = ts;
			have_type_declaration = td;
			have_func_declarator = fd;
		}
		goto end_label;
	}

	case lex_undef : {
		/* Deal with '#undef' */
		if (act) {
			pp = read_undef ();
			if (pp >= 0) pp = patch_cond (pp, t);
		}
		goto end_label;
	}

	case lex_assert : {
		/* Deal with '#assert' (extension) */
		OPTION opt;
		if (!act) goto end_label;
		opt = option (OPT_ppdir_assert);
		if (opt != OPTION_DISALLOW) {
			if (opt != OPTION_ALLOW) {
				err = ERR_pragma_cpp_known (t);
				err = set_severity (err, OPT_ppdir_assert, 0);
				report (preproc_loc, err);
			}
			if (!option (OPT_ppdir_assert_ignore)) {
				read_assert (t);
			}
			goto end_label;
		}
		break;
	}

	case lex_file : {
		/* Deal with '#file' (extension) */
		OPTION opt;
		if (!act) goto end_label;
		opt = option (OPT_ppdir_file);
		if (opt != OPTION_DISALLOW) {
			if (opt != OPTION_ALLOW) {
				err = ERR_pragma_cpp_known (t);
				err = set_severity (err, OPT_ppdir_file, 0);
				report (preproc_loc, err);
			}
			if (!option (OPT_ppdir_file_ignore)) {
				read_location (t);
			}
			goto end_label;
		}
		break;
	}

	case lex_ident : {
		/* Deal with '#ident' (extension) */
		OPTION opt;
		if (!act) goto end_label;
		opt = option (OPT_ppdir_ident);
		if (opt != OPTION_DISALLOW) {
			if (opt != OPTION_ALLOW) {
				err = ERR_pragma_cpp_known (t);
				err = set_severity (err, OPT_ppdir_ident, 0);
				report (preproc_loc, err);
			}
			if (!option (OPT_ppdir_ident_ignore)) {
				read_ident (t);
			}
			goto end_label;
		}
		break;
	}

	case lex_import :
	case lex_include_Hnext : {
		/* Deal with '#import' and '#include_next' (extension) */
		OPTION opt;
		if (!act) goto end_label;
		opt = option (OPT_ppdir_import);
		if (opt != OPTION_DISALLOW) {
			if (opt != OPTION_ALLOW) {
				err = ERR_pragma_cpp_known (t);
				err = set_severity (err, OPT_ppdir_import, 0);
				report (preproc_loc, err);
			}
			if (!option (OPT_ppdir_import_ignore)) {
				pp = read_include (act, t);
				return (pp);
			}
			goto end_label;
		}
		break;
	}

	case lex_unassert : {
		/* Deal with '#unassert' (extension) */
		OPTION opt;
		if (!act) goto end_label;
		opt = option (OPT_ppdir_unassert);
		if (opt != OPTION_DISALLOW) {
			if (opt != OPTION_ALLOW) {
				err = ERR_pragma_cpp_known (t);
				err = set_severity (err, OPT_ppdir_unassert, 0);
				report (preproc_loc, err);
			}
			if (!option (OPT_ppdir_unassert_ignore)) {
				read_unassert (t);
			}
			goto end_label;
		}
		break;
	}

	case lex_warning : {
		/* Deal with '#warning' (extension) */
		OPTION opt;
		if (!act) goto end_label;
		opt = option (OPT_ppdir_warning);
		if (opt != OPTION_DISALLOW) {
			if (opt != OPTION_ALLOW) {
				err = ERR_pragma_cpp_known (t);
				err = set_severity (err, OPT_ppdir_warning, 0);
				report (preproc_loc, err);
			}
			if (act && !option (OPT_ppdir_warning_ignore)) {
				read_error (OPT_warning);
			}
			goto end_label;
		}
		break;
	}

	case lex_weak : {
		/* Deal with '#weak' (extension) */
		OPTION opt;
		if (!act) goto end_label;
		opt = option (OPT_ppdir_weak);
		if (opt != OPTION_DISALLOW) {
			if (opt != OPTION_ALLOW) {
				err = ERR_pragma_cpp_known (t);
				err = set_severity (err, OPT_ppdir_weak, 0);
				report (preproc_loc, err);
			}
			if (!option (OPT_ppdir_weak_ignore)) {
				read_weak (t);
			}
			goto end_label;
		}
		break;
	}
	}

	/* Unknown directives */
	report (preproc_loc, ERR_cpp_unknown (dir));
	end_label : {
		if (in_preproc_dir) IGNORE skip_to_end ();
		return (pp);
	}
}


/*
 *    PRINT DEPENDENCIES FOR MAKE
 *
 *    Adds the file fn to the current dependency line.  If output is not a
 *    null pointer, a new dependency list is begun on the next line.  For
 *    the target, the file extension is replaced with inclusion_obj_suffix.
 *    Lines longer than 72 characters are wrapped.
 */

void
print_dependency(const char *fn, FILE *output)
{
	static FILE *f = NULL;
	static int col = 0;

	if (output != NULL) {
		/* Search the filename part and replace the extension. */
		const char *dot;
		const char *slash = strrchr (fn, char_slash);
		const char *file = fn;
		if (slash != NULL) file = slash + 1;
		dot = strrchr (file, char_dot);
		if (dot == NULL) dot = file + strlen (file);
		if (f != NULL) putc ('\n', f);
		f = output;
		col = fprintf (f, "%.*s%s:", (int)(dot - file), file,
			inclusion_obj_suffix);
	}
	if (col + strlen (fn) > 72) {
		fputs (" \\\n ", f);
		col = 2;
	}
	col += fprintf (f, " %s", fn);
}


/*
 *    PREPROCESS A FILE
 *
 *    This routine gives the preprocessor entry point for the compiler.  Each
 *    token is read, preprocessed, and printed.  The white space in the output
 *    does not necessarily bear any resemblance to that in the input.
 */

void
preprocess_file(void)
{
	int t;
	FILE *f;
	string fn;
	BUFFER *bf;
	unsigned long ws = 0;
	unsigned long ln = 0;
	int force_space = preproc_space;
	static BUFFER preproc_buff = { NULL, NULL, NULL, NULL };

	/* Initialise input file */
	init_lex ();
	fn = DEREF_string (posn_file (preproc_loc.posn));

	/* Open output file */
	if (!open_output (OUTPUT_PREPROC, text_mode)) {
		string nm = output_name [OUTPUT_PREPROC];
		fail (ERR_fail_output (nm));
		tenapp_exit ();
		return;
	}
	f = output_file [OUTPUT_PREPROC];
	bf = clear_buffer (&preproc_buff, f);
	if (inclusion_dependencies == DEP_NONE) {
		fprintf_v (f, "#line 1 \"%s\"", strlit (fn));
	} else {
		print_dependency (strlit (fn), f);
	}
	crt_file_changed = 1;

	/* Scan through preprocessing tokens */
	while (t = expand_preproc (EXPAND_NORMAL), t != lex_eof) {
		/* Allow for skipped files */
		if (crt_file_type) continue;
		/* The actual printing is done via start_include() */
		if (inclusion_dependencies != DEP_NONE) continue;

		/* Replace keywords by underlying identifier */
		if (t >= FIRST_KEYWORD && t <= LAST_KEYWORD) {
			crt_token->tok = lex_identifier;
		}
		if (t == lex_builtin_Hfile) {
			fputc_v ('\n', f);
			ln++;
			crt_line_changed = 1;
			crt_spaces = 0;
			continue;
		}

		/* Print any required '#line' directives */
		if (crt_line_changed) {
			int ch = crt_file_changed;
			unsigned long n = crt_loc.line;
			unsigned long sp = crt_spaces;
			unsigned long tab = tab_width;
			output_buffer (bf, 0);
			if (ch) {
				string fm = DEREF_string (posn_file (crt_loc.posn));
				if (ch > 1 || !ustreq (fn, fm)) {
					char *s = strlit (fm);
					fprintf_v (f, "\n\n#line %lu \"%s\"\n", n, s);
					fn = fm;
					ln = n;
				}
				crt_file_changed = 0;
			}
			if (n != ln) {
				if (n > ln && n <= ln + 10) {
					for (; ln < n; ln++) fputc_v ('\n', f);
				} else {
					/* Force '#line' for more than 10 blank lines */
					fprintf_v (f, "\n\n#line %lu\n", n);
				}
				ln = n;
			}
			crt_line_changed = 0;

			/* Print indentation */
			ws = sp;
			for (; sp >= tab; sp -= tab) bfputc (bf, '\t');
			for (; sp; sp--) bfputc (bf, ' ');

			/* Allow for hash symbols */
			if (t == lex_hash_H1) crt_token->tok = lex_hash_Hhash_H1;
			if (t == lex_hash_H2) crt_token->tok = lex_hash_Hhash_H2;

		} else {
			unsigned long sp = crt_spaces;
			if (sp != ws || force_space) {
				/* Print space */
				ws = sp;
				bfputc (bf, ' ');
			}
		}

		/* Print the token name */
		IGNORE print_pptok (crt_token, bf, 0);
	}
	bfputc (bf, '\n');
	output_buffer (bf, 0);
	close_output (OUTPUT_PREPROC);
	return;
}


syntax highlighted by Code2HTML, v. 0.9.1