/*
 * 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/tools/tcc/compile.c,v 1.10 2005/10/17 08:21:48 stefanf Exp $
 */


#include "config.h"
#include "msgcat.h"

#include "filename.h"
#include "list.h"
#include "archive.h"
#include "environ.h"
#include "flags.h"
#include "compile.h"
#include "execute.h"
#include "options.h"
#include "stages.h"
#include "startup.h"
#include "suffix.h"
#include "utility.h"

static filename *apply_unjoin (filename *, int);


/*
 *    CHECK IF ONE FILE IS THE C SPEC OF THE OTHER
 *
 *    This macro checks if q is the C or C++ spec file corresponding to p.
 *    q must be of type C_SPEC and have the same uniq value as p.
 */

#define spec_of(p, q)\
	((q) && ((q)->type == C_SPEC || (q)->type == CPP_SPEC) \
			 && (q)->uniq == (p)->uniq)


/*
 *    APPLY THE SYSTEM COMPILER
 *
 *    This routine applies the system C compiler to the files, input,
 *    returning the result.
 */

static filename *
apply_cc(filename *input)
{
	filename *p = input;
	filename *output = null;
	while (p != null) {
		filename *ps = null;
		filename *pn = p->next;
		p->next = null;
		switch (p->type) {

		case C_SOURCE : {
			/* C source */
			if (make_preproc || keeps [PREPROC_C]) {
				p = do_cc (p, C_SOURCE, PREPROC_C);
				if (stops [PREPROC_C]) break;
			}
			goto preproc_c_lab;
		}

		case PREPROC_C :
			preproc_c_lab : {
				/* Preprocessed C source */
				if (use_system_cc == 1) {
					ps = do_produce (p);
					if (ps == null) {
						p = null;
						break;
					} else {
						ps = ps->next;
					}
				}
				if (stops [DEP_TDF]) {
					if (ps != null) p = null;
					break;
				} else if (stops [AS_SOURCE]) {
					p = do_cc (p, C_SOURCE, AS_SOURCE);
					break;
				} else if (keeps [AS_SOURCE]) {
					p = do_cc (p, C_SOURCE, AS_SOURCE);
				} else {
					p = do_cc (p, C_SOURCE, binary_obj_type);
					break;
				}
				goto as_source_lab;
			}

		case CPP_SOURCE : {
			/* C++ source */
			if (make_preproc || keeps [PREPROC_CPP]) {
				p = do_cc (p, CPP_SOURCE, PREPROC_CPP);
				if (stops [PREPROC_CPP]) break;
			}
			goto preproc_cpp_lab;
		}

		case PREPROC_CPP :
			preproc_cpp_lab : {
				/* Preprocessed C++ source */
				if (use_system_cc == 1) {
					ps = do_cpp_produce (p);
					if (ps == null) {
						p = null;
						break;
					} else {
						ps = ps->next;
					}
				}
				if (stops [DEP_TDF]) {
					if (ps != null) p = null;
					break;
				} else if (stops [AS_SOURCE]) {
					p = do_cc (p, CPP_SOURCE, AS_SOURCE);
					break;
				} else if (keeps [AS_SOURCE]) {
					p = do_cc (p, CPP_SOURCE, AS_SOURCE);
				} else {
					p = do_cc (p, CPP_SOURCE, binary_obj_type);
					break;
				}
				goto as_source_lab;
			}

		case AS_SOURCE :
			as_source_lab : {
				/* Assembly source file */
				if (!stops [AS_SOURCE]) p = do_cc (p, AS_SOURCE, BINARY_OBJ);
				break;
			}

		case PREPROC_AS : {
			/* Preprocessed assembly source */
			p = do_cc (p, PREPROC_AS, AS_SOURCE);
			goto as_source_lab;
		}

		case BINARY_OBJ : {
			/* Binary object file */
			break;
		}

		case C_SPEC : {
			/* C spec file */
			if (!allow_specs) {
				MSG_is_a_C_spec_file (p->name);
				p->type = DEFAULT_TYPE;
			} else {
				if (p->storage == INPUT_FILE &&
					keeps_aux [BINARY_OBJ] == 1 &&
					!stops [AS_SOURCE]) {
					p = do_build_file (p, BINARY_OBJ);
				}
			}
			break;
		}

		case CPP_SPEC : {
			/* C++ spec file */
			if (!allow_specs || !allow_cpp) {
				MSG_is_a_cpp_spec_file (p->name);
				p->type = DEFAULT_TYPE;
			} else {
				if (p->storage == INPUT_FILE &&
					keeps_aux [BINARY_OBJ] == 1 &&
					!stops [AS_SOURCE]) {
					p = do_build_file (p, BINARY_OBJ);
				}
			}
			break;
		}

		default : {
			/* Other file types give an error */
			MSG_TDF_file_not_recognised_in_cc_mode (p->name);
			p->type = DEFAULT_TYPE;
			break;
		}
		}
		if (p && p->storage != INPUT_FILE && p->aux == null) {
			boolean have_spec = 0;
			filename *p_archive = p;
			int t = p->type;
			if (t == BINARY_OBJ && keeps [t] && binary_obj_type != t) {
				if (spec_of (p_archive, ps)) {
					p_archive->next = ps;
					p_archive = p_archive->next;
					ps = ps ->next;
					p_archive->next = null;
					have_spec = 1;
				}
				if (have_spec) {
					p = do_build_file (p, BINARY_OBJ);
				}
			}
		}
		output = add_filename (output, p);
		if (ps) output = add_filename (output, ps);
		p = pn;
	}
	return (output);
}


/*
 *    APPLY THE TDF LINKER
 *
 *    This routine applies the TDF linker to p, returning the result.
 */

static filename *
apply_tdf_link(filename *p)
{
	static boolean tried = 0;
	if (p == null || stops [INDEP_TDF]) return (p);
	if (tokdef_name && tokdef_output == null && !tried) {
		filename *q;
		if (!allow_notation) read_env (TNC_ENV);
		q = find_filename (tokdef_name, PRETTY_TDF);
		q = do_notation (q);
		if (q) tokdef_output = q->name;
		tried = 1;
	}
	return (do_tdf_link (p));
}


/*
 *    APPLY THE MAIN COMPILATION STAGES
 *
 *    This routine applies the main compilation phases to the input files
 *    input, and returns the corresponding output files.  If produce is
 *    true then the compilation stops at the target independent TDF stage.
 *    This, and all the routines in this module, needs to be kept in step
 *    with Table 1.
 */

static filename *
apply_compile(filename *input, int produce)
{
	filename *p = input;
	filename *output = null;
	if (use_system_cc) return (apply_cc (input));
	while (p != null) {
		filename *pc = p;
		filename *pn = p->next;
		p->next = null;
		switch (p->type) {

		case C_SOURCE : {
			/* C source */
			if (keeps [PREPROC_C]) {
				p = do_preproc (p, PREPROC_C);
			} else {
				p = do_produce (p);
			}
			if (allow_specs && stops [C_SPEC]) p = null;
			if (p != pc) p = apply_compile (p, produce);
			break;
		}

		case PREPROC_C : {
			/* Preprocessed C source */
			if (stops [PREPROC_C]) break;
			p = do_produce (p);
			if (allow_specs && stops [C_SPEC]) p = null;
			if (p != pc) p = apply_compile (p, produce);
			break;
		}

		case CPP_SOURCE : {
			/* C++ source */
			if (allow_cpp) {
				if (keeps [PREPROC_CPP]) {
					p = do_cpp_preproc (p);
				} else {
					p = do_cpp_produce (p);
				}
				if (allow_specs && stops [CPP_SPEC]) p = null;
				if (p != pc) p = apply_compile (p, produce);
			} else {
				MSG_is_a_cpp_source_file (p->name);
				p->type = DEFAULT_TYPE;
			}
			break;
		}

		case PREPROC_CPP : {
			/* Preprocessed C++ source */
			if (stops [PREPROC_CPP]) break;
			if (allow_cpp) {
				p = do_cpp_produce (p);
				if (allow_specs && stops [CPP_SPEC]) p = null;
				if (p != pc) p = apply_compile (p, produce);
			} else {
				MSG_is_a_preprocessed_cpp_source_file (p->name);
				p->type = DEFAULT_TYPE;
			}
			break;
		}

		case INDEP_TDF : {
			/* Target independent TDF */
			if (!produce) {
				p = apply_tdf_link (p);
				if (p != pc) p = apply_compile (p, produce);
			}
			break;
		}

		case DEP_TDF : {
			/* Target dependent TDF */
			if (!produce) {
				p = do_translate (p);
				if (p != pc) p = apply_compile (p, produce);
			}
			break;
		}

		case AS_SOURCE : {
			/* Assembly source */
			if (!produce) {
				p = do_assemble (p);
				if (p != pc) p = apply_compile (p, produce);
			}
			break;
		}

		case PREPROC_AS : {
			/* Preprocessed assembly source */
			if (!produce) {
				p = do_preproc (p, AS_SOURCE);
				p = apply_compile (p, produce);
			}
			break;
		}

		case PRETTY_TDF : {
			/* TDF notation source */
			if (allow_notation) {
				p = do_notation (p);
				if (p != pc) p = apply_compile (p, produce);
			} else {
				MSG_is_a_tdf_notation_source_file (p->name);
				p->type = DEFAULT_TYPE;
			}
			break;
		}

		case PL_TDF : {
			/* PL_TDF source */
			if (allow_pl_tdf) {
				p = do_pl_tdf (p);
				if (p != pc) p = apply_compile (p, produce);
			} else {
				MSG_is_a_pl_tdf_source_file (p->name);
				p->type = DEFAULT_TYPE;
			}
			break;
		}

		case C_SPEC : {
			/* C spec file */
			if (!allow_specs) {
				MSG_is_a_C_spec_file (p->name);
				p->type = DEFAULT_TYPE;
			} else {
				if (p->storage == INPUT_FILE &&
					keeps_aux [BINARY_OBJ] == 1 &&
					!stops [AS_SOURCE]) {
					p = do_build_file (p, BINARY_OBJ);
				}
			}
			break;
		}

		case CPP_SPEC : {
			/* C++ spec file */
			if (!allow_specs || !allow_cpp) {
				MSG_is_a_cpp_spec_file (p->name);
				p->type = DEFAULT_TYPE;
			} else {
				if (p->storage == INPUT_FILE &&
					keeps_aux [BINARY_OBJ] == 1 &&
					!stops [AS_SOURCE]) {
					p = do_build_file (p, BINARY_OBJ);
				}
			}
			break;
		}
		}
		if (p && p->storage != INPUT_FILE && p->aux == null) {
			boolean have_spec = 0;
			filename *p_archive = p;
			if (spec_of (p_archive, p_archive->next)) {
				p_archive = p_archive->next;
				have_spec = 1;
			} else if (spec_of (p_archive, pn)) {
				p_archive->next = pn;
				p_archive = p_archive->next;
				pn = pn->next;
				p_archive->next = null;
				have_spec = 1;
			}
			if (have_spec) {
				int t = p->type;
				if (t == INDEP_TDF && checker && !stops [t]) {
					if (stops [BINARY_OBJ] ||
						keeps_aux [BINARY_OBJ] == 1) {
						p = do_build_file (p->next, BINARY_OBJ);
					}
				}
				if (t == BINARY_OBJ && keeps [t]) {
					p = do_build_file (p, BINARY_OBJ);
				}
			}
		}
		output = add_filename (output, p);
		p = pn;
	}
	return (output);
}


/*
 *    FILTER OUT LIST OF BINARY OBJECT FILES
 *
 *    This routine picks out the TDF capsules from the input files, input,
 *    compiles them to binary object files, and returns them.
 */

static filename *
filter_ofiles(filename *input)
{
	filename *p = input;
	filename *links = null;
	while (p != null) {
		filename *pn = p->next;
		p->next = null;
		p->aux = pn;
		if (p->type == INDEP_TDF) {
			filename *q = apply_compile (p, 0);
			if (q && q->type == BINARY_OBJ) {
				links = add_filename (links, q);
			}
		}
		p = pn;
	}
	return (links);
}


/*
 *    APPLY THE SYSTEM LINKER
 *
 *    This routine applies the system linker to the input files, input,
 *    and returns the corresponding output files.
 */

static filename *
apply_link(filename *input)
{
	filename *p = input;
	int spec_out = C_SPEC_2;
	filename *links = null, *links_out = null;
	filename *specs = null, *specs_out;
	filename *others = null;
	while (p != null) {
		filename *pn = p->next;
		p->next = null;
		p->aux = pn;
		if (p->type == BINARY_OBJ) {
			links = add_filename (links, p);
		} else if (p->type == C_SPEC) {
			specs = add_filename (specs, p);
		} else if (p->type == CPP_SPEC) {
			specs = add_filename (specs, p);
			spec_out = CPP_SPEC_2;
		} else {
			others = add_filename (others, p);
		}
		p = pn;
	}
	last_return = 0;
	keeps [binary_obj_type] = 0;
	specs_out = do_link_specs (specs, spec_out);
	links = add_filename (links, filter_ofiles (specs_out));
	if (use_dynlink != 0 && use_system_cc == 0) links = do_dynlink (links);
	if (links) links_out = do_link (links);
	if (specs_out && links_out) specs_out->uniq = links_out->uniq;
	p = add_filename (links_out, specs_out);
	p = add_filename (p, others);
	if (last_return) {
		/* If the linking failed, keep the .o files */
		filename *q = input;
		boolean b = keeps [BINARY_OBJ];
		keeps [BINARY_OBJ] = 1;
		p = null;
		while (q != null) {
			filename *qa = q->aux;
			q->next = null;
			if (q->type == C_SPEC || q->type == CPP_SPEC) {
				q = null;
			} else if (q->type == BINARY_OBJ) {
				if (q->storage == INPUT_FILE || b) {
					q = null;
				} else if (allow_specs) {
					if (link_specs) q = do_keep (q);
					if (spec_of (q, qa)) {
						q->next = qa;
						qa->next = null;
						qa = qa->aux;
						if (link_specs) q->next = do_keep (q->next);
					}
					q = do_build_file (q, BINARY_OBJ);
				} else {
					q = do_keep (q);
				}
			}
			p = add_filename (p, q);
			q = qa;
		}
	}
	return (p);
}


/*
 *    SPLIT ALL TDF ARCHIVES
 *
 *    This routine splits any TDF archives in the input files, input,
 *    and returns the corresponding output files.
 */

static filename *
apply_split_arch(filename *input)
{
	filename *p = input;
	filename *output = null;
	archive_type = TDF_ARCHIVE;
	while (p != null) {
		filename *pn = p->next;
		p->next = null;
		if (p->type == TDF_ARCHIVE) {
			if (use_system_cc) {
				MSG_is_a_tdf_archive (p->name);
				p->type = DEFAULT_TYPE;
			} else {
				p = do_split_arch (p);
			}
		}
		output = add_filename (output, p);
		p = pn;
	}
	return (output);
}


/*
 *    BUILD A TDF ARCHIVE
 *
 *    This routine creates a TDF archive from the input files, input,
 *    and returns the corresponding output files.
 */

static filename *
apply_build_arch(filename *input)
{
	filename *p = input;
	filename *links = null;
	filename *specs = null;
	filename *others = null;
	int spec_out = C_SPEC_1;
	while (p != null) {
		filename *pn = p->next;
		p->next = null;
		if (p->type == INDEP_TDF) {
			links = add_filename (links, p);
		} else if (p->type == C_SPEC) {
			specs = add_filename (specs, p);
		} else if (p->type == CPP_SPEC) {
			specs = add_filename (specs, p);
			spec_out = CPP_SPEC_1;
		} else {
			others = add_filename (others, p);
		}
		p = pn;
	}
	specs = do_link_specs (specs, spec_out);
	links = do_build_arch (links);
	if (specs && links) specs->uniq = links->uniq;
	p = add_filename (links, specs);
	p = add_filename (p, others);
	return (p);
}


/*
 *    BUILD A TDF COMPLEX
 *
 *    This routine creates a complex TDF capsule from the input files,
 *    input, and returns the corresponding output files.
 */

static filename *
apply_build(filename *input)
{
	filename *p = input;
	filename *links = null;
	filename *specs = null;
	filename *others = null;
	int spec_out = C_SPEC_1;
	while (p != null) {
		filename *pn = p->next;
		p->next = null;
		if (p->type == INDEP_TDF) {
			links = add_filename (links, p);
		} else if (p->type == C_SPEC) {
			specs = add_filename (specs, p);
		} else if (p->type == CPP_SPEC) {
			specs = add_filename (specs, p);
			spec_out = CPP_SPEC_1;
		} else {
			others = add_filename (others, p);
		}
		p = pn;
	}
	specs = do_link_specs (specs, spec_out);
	links = do_tdf_build (links);
	if (specs && links) specs->uniq = links->uniq;
	p = add_filename (links, specs);
	p = add_filename (p, others);
	return (p);
}


/*
 *    APPLY THE PREPROCESSOR
 *
 *    This routine applies the preprocessor to the input files, input,
 *    and returns the corresponding output files.
 */

static filename *
apply_preproc(filename *input)
{
	filename *p = input;
	filename *output = null;
	if (use_system_cc) return (apply_cc (input));
	while (p != null) {
		filename *pn = p->next;
		p->next = null;
		if (p->type == C_SOURCE) {
			p = do_preproc (p, PREPROC_C);
		} else if (p->type == CPP_SOURCE) {
			p = do_cpp_preproc (p);
		}
		output = add_filename (output, p);
		p = pn;
	}
	return (output);
}


/*
 *    APPLY THE TDF PRETTY PRINTER
 *
 *    This routine applies the TDF pretty printer to the input files,
 *    input, and returns the corresponding output files.
 */

static filename *
apply_pretty(filename *input)
{
	filename *p = input;
	filename *output = null;
	while (p != null) {
		filename *pn = p->next;
		p->next = null;
		if (make_tspec) {
			if (p->type == C_SPEC) p = do_pretty (p);
		} else if (p->type == INDEP_TDF) {
			if (make_pretty == 2) p = apply_tdf_link (p);
			p = do_pretty (p);
		} else if (p->type == DEP_TDF) {
			p = do_pretty (p);
		}
		output = add_filename (output, p);
		p = pn;
	}
	return (output);
}


/*
 *    APPLY THE ARCHIVER TO UNJOIN C SPEC FILES
 *
 *    This routine scans input for input archives of type t.  It splits these
 *    into their component files.  For internal archives the contents are
 *    in the aux field.
 */

static filename *
apply_unjoin(filename *input, int t)
{
	filename *p = input;
	filename *output = null;
	archive_type = t;
	while (p != null) {
		filename *pn = p->next;
		p->next = null;
		if (p->type == t) {
			if (p->storage == INPUT_FILE) {
				if (is_archive (p->name)) p = do_split_arch (p);
			} else if (p->aux != null) {
				p = p->aux;
			}
		}
		output = add_filename (output, p);
		p = pn;
	}
	return (output);
}


/*
 *    APPLY ALL THE COMPILATION STAGES
 *
 *    This routine applies all the compilation stages to the input files,
 *    input, and returns the corresponding output files.
 */

filename *
apply_all(filename *input)
{
	filename *p = input;

	/* Set up file types */
	if (allow_specs) binary_obj_type = BINARY_OBJ_AUX;

	/* Preprocessing is a special case */
	if (make_preproc) return (apply_preproc (p));

	/* Any TDF archives are split immediately */
	p = apply_split_arch (input);

	/* Deal with building TDF archive case */
	if (make_archive) {
		p = apply_compile (p, 1);
		if (make_complex) p = apply_build (p);
		return (apply_build_arch (p));
	}

	/* Deal with pretty printing case */
	if (make_pretty) {
		p = apply_compile (p, 1);
		if (make_complex) p = apply_build (p);
		return (apply_pretty (p));
	}

	/* Deal with building TDF complex */
	if (make_complex) {
		p = apply_compile (p, 1);
		p = apply_build (p);
	}

	/* Main compilation phases */
	p = apply_compile (p, (int) checker);
	if (allow_specs && !stops [BINARY_OBJ]) {
		p = apply_unjoin (p, BINARY_OBJ);
	}
	p = apply_link (p);
	return (p);
}
