/*
 * Copyright (c) 1991-2009 by Wayne C. Gramlich.
 * All rights reserved.
 */

/*
 * This file is responsible for parsing and checking command line arguments
 * for the STIPPLE compiler.
 */

#ifndef ERROR_EXPORTS_H
#include "error_exports.h"
#endif

#ifndef HEAP_EXPORTS_H
#include "heap_exports.h"
#endif

#ifndef FLAGS_DEFS_H
#include "flags_defs.h"
#endif

#ifndef LIBC_EXPORTS_H
#include "libc_exports.h"
#endif

#ifndef LINT_H
#include "lint.h"
#endif

#ifndef STR_EXPORTS_H
#include "str_exports.h"
#endif

#ifndef UNIX_ASSERT_H
#include "unix_assert.h"
#endif

#ifndef UNIX_STDLIB_H
#include "unix_stdlib.h"
#endif

#ifndef UNIX_UNISTD_H
#include "unix_unistd.h"
#endif

#ifndef VECTOR_EXPORTS_H
#include "vector_exports.h"
#endif

/* This file contains code for processing compiler flag information: */

LOCAL void		flags_check(Flags);
LOCAL Str		flags_check_helper(Flags, Str, Str, Str,
					   int, int, int);
LOCAL Flags		flags_create(Heap);
LOCAL void		flags_file_full_fill(Flags_file, Heap);
LOCAL Flags_file	flags_file_parse(Str, Heap);
LOCAL void		flags_parse(Flags, int, Str *, Heap);

/*
 * flags_check(flags)
 *	This routine will check "flags" for being consistent.  All errors
 *	are fatal.
 */
void
flags_check(
	Flags		flags)
{
	Heap		heap;
	Flags_file	gen_file;
	Str		option;

	switch (vec_size(Flags_file, flags->in_files)) {
	    case 0:
		error_fatal("No .sts file specified");
		break;
	    case 1:
		break;
	    default:
		if (!flags->gen_interface) {
			error_fatal("Only the -i option takes"
				    " multiple .sts files");
		}
	}
	flags->in_file = vec_fetch(Flags_file, flags->in_files, 0);

	heap = flags->heap;
	gen_file = heap_allocate(heap, Flags_file);
	gen_file->base = strprintf(heap, "generate_%s", flags->in_file->base);
	gen_file->path = "/tmp";
	gen_file->suffix = ".sts";
	flags_file_full_fill(gen_file, heap);
	flags->gen_file = gen_file;

	option = (Str)0;
	option = flags_check_helper(flags, option, "-S", ".s", 1, 1,
				    flags->gen_asm);
	option = flags_check_helper(flags, option, "-C", ".c", 1, 1,
				    flags->gen_c);
	option = flags_check_helper(flags, option, "-i", ".sti", 0, 0,
				    flags->gen_interface);
	option = flags_check_helper(flags, option, "-T", ".txt", 1, 0,
				    flags->gen_text);
	option = flags_check_helper(flags, option, "-c", ".o", 0, 1,
				    flags->gen_object);
	option = flags_check_helper(flags, option, "-P", ".stp", 1, 0,
				    flags->gen_parse);
	option = flags_check_helper(flags, option, "-x", ".stc", 1, 0,
				    flags->gen_calls);
	if (option == (Str)0) {
		flags->gen_object = 1;
	}
}

/*
 * flags_check_helper(flags, previous, current,
 *		      suffix, list, debug_ok, enabled)
 */
Str
flags_check_helper(
	Flags		flags,
	Str		previous,
	Str		current,
	Str		suffix,
	int		list,
	int		debug_ok,
	int		enabled)
{
	Flags_file	c_file;
	Flags_file	gen_file;
	int		gen_file_update;
	Heap		heap;
	Flags_file	in_file;
	Str		out_dir;
	Flags_file	out_file;

	if (!enabled) {
		return previous;
	}
	if (previous != (Str)0) {
		error_fatal("%s option is incompatible with %s\n", current);
	}
	gen_file_update = 1;
	in_file = flags->in_file;
	if (flags->out_file == (Flags_file)0) {
		heap = flags->heap;
		out_dir = flags->out_dir;

		/* Determine the output file name: */
		if (out_dir != (Str)0) {
			out_file = heap_allocate(heap, Flags_file);
			out_file->path = out_dir;
			out_file->base = in_file->base;
			out_file->suffix = suffix;
		} else if (list) {
			out_file = heap_allocate(heap, Flags_file);
			out_file->path = "/dev";
			out_file->base = "tty";
			out_file->suffix = "";
			gen_file_update = 0;
		} else {
			in_file = vec_fetch(Flags_file, flags->in_files, 0);
			out_file = heap_allocate(heap, Flags_file);
			out_file->path = in_file->path;
			out_file->base = in_file->base;
			out_file->suffix = suffix;
		}
		flags_file_full_fill(out_file, heap);
		flags->out_file = out_file;

		if (gen_file_update) {
			gen_file = heap_allocate(heap, Flags_file);
			*gen_file = *out_file;
			gen_file->suffix = ".sts";
			gen_file->base = strprintf(heap, "%s_generate",
						   gen_file->base);
			flags_file_full_fill(gen_file, heap);
			flags->gen_file = gen_file;
		}

		/* Determine the temporary C file name: */
		c_file = heap_allocate(heap, Flags_file);
		c_file->path = (out_dir == (Str)0) ? "." : out_dir;
		c_file->base = in_file->base;
		c_file->suffix = ".c";
		flags_file_full_fill(c_file, heap);
		flags->c_file = c_file;
	} else if (!strequal(flags->out_file->suffix, suffix)) {
		error_fatal(
		    "Output file should have suffix of %s instead of %s\n",
		    suffix, flags->out_file->suffix);
	}
	if (flags->debug && !debug_ok) {
		error_fatal("-g option is not permitted with %s option\n",
			    current);
	}
	return current;
}

/*
 * flags_create(heap)
 *	This routine will create and return an empty Flags object from "heap".
 */

Flags
flags_create(
	Heap		heap)
{
	Flags		flags;

	flags = heap_allocate(heap, Flags);
	flags->c_file = (Flags_file)0;
	flags->compiler = "cc";
	flags->debug = 0;
	flags->debug_c = 0;
	flags->dump_tables = 0;
	flags->heap = heap;
	flags->gen_asm = 0;
	flags->gen_c = 0;
	flags->gen_file = (Flags_file)0;
	flags->gen_object = 0;
	flags->gen_parse = 0;
	flags->gen_patch = 0;
	flags->gen_text = 0;
	flags->import_dirs = vec_create(Str, heap);
	flags->import_dump = 0;
	flags->in_files = vec_create(Flags_file, heap);
	flags->linkage = 1;		/* Alwasy leave on for now */
	flags->opt_base_types = 1;	/* Always leave on for now */
	flags->opt_inter = 0;
	flags->opt_level = 0;
	flags->opt_pic = 0;
	flags->options = vec_create(Str, heap);
	flags->out_file = (Flags_file)0;
	flags->out_dir = (Str)0;
	flags->package_name = "";
	flags->profile = 0;
	flags->verbose = 0;
	return flags;
}

/*
 * flags_file_parse(file_name, heap)
 *	This routine will parse "file_name" and return an associated
 *	Flags_file object allocated from "heap".
 */
Flags_file
flags_file_parse(
	Str		file_name,
	Heap		heap)
{
	Str		base;
	Str		suffix;
	Flags_file	flags_file;
	Str		path;
	char		full_path[2000];
	Str		slash_pointer;
	int		size;

	flags_file = heap_allocate(heap, Flags_file);

	/* Parse the path: */
	slash_pointer = strrchr(file_name, '/');
	if (slash_pointer == (Str)0) {
		path = ".";
		flags_file->path = path;
		slash_pointer = file_name;
	} else {
		size = slash_pointer - file_name;
		path = (Str)heap_alloc(heap, size + 1);
		(void)strncpy(path, file_name, size);
		path[size] = '\0';
		flags_file->path = path;
		slash_pointer++;
	}
	assert(realpath(path, full_path) != (Str)0);
	path = strdupl(full_path, heap);
	flags_file->path = path;

	/* Extract the suffix: */
	suffix = strrchr(file_name, '.');
	if ((suffix == (Str)0) || (suffix < slash_pointer)) {
		error_fatal("No suffix specified for file %s\n", file_name);
	}
	flags_file->suffix = strdupl(suffix, heap);

	/* Extract the base name: */
	size = suffix - slash_pointer;
	base = (Str)heap_alloc(heap, size + 1);
	(void)strncpy(base, slash_pointer, size);
	base[size] = '\0';
	flags_file->base = base;

	flags_file_full_fill(flags_file, heap);
	return flags_file;
}

/*
 * flags_file_full_fill(flags_file, heap)
 *	This routine will fill in the full field of "flags_file".
 */
void
flags_file_full_fill(
	Flags_file	flags_file,
	Heap		heap)
{
	flags_file->full = strprintf(heap, "%s/%s%s", flags_file->path,
				     flags_file->base, flags_file->suffix);
}

/*
 * flags_parse(flags, argc, argv, heap)
 *	This routine will parse the "argc" options in "argv".  "argv[0]"
 *	should point to the first option, not the compiler name.  All
 *	errors are fatal.
 */
void
flags_parse(
	Flags		flags,
	int		argc,
	Str		*argv,
	Heap		heap)
{
	Str		argument;
	int		index;
	Str		option;

	for (index = 0; index < argc; index++) {
		option = argv[index];
		if (option[0] != '-') {
			vec_append(Flags_file, flags->in_files,
				   flags_file_parse(option, heap));
			continue;
		}
		switch (option[1]) {
		    case 'C':
			flags->gen_c = 1;
			continue;
		    case 'c':
			flags->gen_object = 1;
			continue;
		    case 'd':
			flags->dump_tables = 1;
			continue;
		    case 'E':
			break;
		    case 'G':
			flags->debug_c = 1;
			continue;
		    case 'g':
			flags->debug = 1;
			flags->linkage = 1;
			continue;
		    case 'I':
			break;
		    case 'i':
			flags->gen_interface = 1;
			continue;
		    case 'O':
			break;
		    case 'o':
			break;
		    case 'P':
			flags->gen_parse = 1;
			continue;
		    case 'p':
			break;
		    case 'S':
			flags->gen_asm = 1;
			continue;
		    case 'T':
			flags->gen_text = 1;
			continue;
		    case 'v':
			flags->verbose = 1;
			continue;
		    case 'w':
			break;
		    case 'W':
			/* Windows cross compile mode: */
			flags->cross_compile = 1;
		  	continue;
		    case 'X':
			break;
		    case 'x':
			flags->gen_calls = 1;
			continue;
		    case 'z':
			flags->profile = 1;
			continue;
		    default:
			error_fatal("Bad command line option: '%s'\n",
				    option);
		}

		/* Parse next option: */
		index++;
		if (index >= argc) {
			error_fatal("Missing option after %s", option);
		}
		argument = argv[index];
		switch (option[1]) {
		    case 'E':
		      {
			char		out_dir[2000];

			if (flags->out_dir != (Str)0) {
				error_fatal("Duplicate -O options");
			}
			assert(realpath(argument, out_dir) != (Str)0);
			flags->out_dir = strdupl(out_dir, heap);
			break;
		      }
		    case 'I':
			vec_append(Str, flags->import_dirs, argument);
			break;
		    case 'O':
		      {
			int	error;
			char	opt;
			Str	opts;
			int	index;
			int	size;

			error = 0;
			opts = argument;
			size = strlen(opts);
			for (index = 0; index < size; index++) {
				opt = opts[index];
				switch (opt) {
				    case '1':
				    case '2':
				    case '3':
				    case '4':
					error = flags->opt_level;
					flags->opt_level = opt - '0';
					break;
				    case 'b':
					error = flags->opt_base_types;
					flags->opt_base_types = 1;
					break;
				    case 'i':
					error = flags->opt_inter;
					flags->opt_inter = 1;
					break;
				    case 'p':
					error = flags->opt_pic;
					flags->opt_pic = 1;
					break;
				    default:
					error_fatal("-O %c is illegal", opt);
				}
				if (error) {
					error_fatal("Duplicate -O %c", opt);
				}
			}
			break;
		      }
		    case 'o':
			if (flags->out_file != (Flags_file)0) {
				error_fatal("Duplicate -o options");
			}
			flags->out_file = flags_file_parse(argument, heap);
			break;
		    case 'p':
			vec_append(Str, flags->options, argument);
			break;
		    case 'w':
			flags->compiler = argument;
			break;
		    case 'X':
			if (strequal(argument, "dump_imports")) {
				flags->import_dump = 1;
			} else {
				error_fatal("Bad -X %s option", argument);
			}
			break;
		    default:
			assert_fail();
		}
	}
}

/*
 * flags_process(argc, argv, heap)
 *	This routine will process the "argc" options in "argv" and return
 *	a corresponding Flags object allocated from "heap".
 */
Flags
flags_process(
	int		argc,
	Str		*argv,
	Heap		heap)
{
	Flags		flags;

	flags = flags_create(heap);
	flags_parse(flags, argc, argv, heap);
	flags_check(flags);
	return flags;
}
