/*
 * Copyright (c) 1997, 1999 The University of Utah and
 * the Computer Systems Laboratory at the University of Utah (CSL).
 *
 * This file is part of Flick, the Flexible IDL Compiler Kit.
 *
 * Flick is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Flick is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Flick; see the file COPYING.  If not, write to
 * the Free Software Foundation, 59 Temple Place #330, Boston, MA 02111, USA.
 */

#include <string.h>
#include <stdlib.h>

#include <mom/compiler.h>


/*****************************************************************************/

/*
 * `FLAG_VALUE_SEQ_GROW_SIZE' is the number many new slots that we allocate
 * when we have to grow the set of values associated with a flag.
 */
#define FLAG_VALUE_SEQ_GROW_SIZE (4)

void grow_flag_value_seq(flag_value_seq *fvs, int needed)
{
	if( needed > fvs->max ) {
		fvs->max += needed + FLAG_VALUE_SEQ_GROW_SIZE;
		fvs->values = (flag_value *)
			mustrealloc(fvs->values,
				    fvs->max * sizeof(flag_value));
	}
}

/*
 * Add a value to the sequence of values associated with a flag.
 *
 * This function does *not* signal an error if the flag becomes associated with
 * too many values (i.e., more than `this_flag->max_occur'); that semantic
 * check is performed by the main `parse_args' function.
 */
static void
flag_add_value(flags_in *this_flag,
	       flag_value_seq *this_flag_seq, 
	       flag_value *value)
{
	unsigned int need_len;
	
	/*
	 * Figure out how many slots we now need in `this_flag_seq->values'.
	 */
	switch (this_flag->max_occur) {
	default:
	case FLAG_UNLIMITED_LIST:
		/*
		 * In these cases, we extend the list of values for this flag.
		 */
		need_len = this_flag_seq->len + 1;
		break;
	case FLAG_UNLIMITED_USE_LAST:
	case FLAG_UNLIMITED_OR:
		/*
		 * In these cases, we logically combine the new value with any
		 * existing values.
		 */
		need_len = 1;
		break;
	}
	
	/*
	 * Make sure we have space in `this_flag_seq' for the new value.
	 */
	grow_flag_value_seq(this_flag_seq, need_len);
	
	/*
	 * Add `value' to `this_flag_seq->values'.
	 */
	switch (this_flag->max_occur) {
	default:
	case FLAG_UNLIMITED_LIST:
		/* General case: just collect the value. */
		this_flag_seq->values[(this_flag_seq->len)++] = *value;
		break;
		
	case FLAG_UNLIMITED_USE_LAST:
		/* Set or replace `this_flag_seq->values[0]'. */
		this_flag_seq->values[0] = *value;
		this_flag_seq->len = 1;
		break;
		
	case FLAG_UNLIMITED_OR:
		/* Logically `or' the new value with any existing value. */
		if (this_flag->kind != fk_FLAG)
			panic(("In `flag_add_value', cannot use "
			       "`FLAG_UNLIMITED_OR' with a non-boolean "
			       "command line option."));
		
		if (this_flag_seq->len >= 1)
			this_flag_seq->values[0].flag
				= (this_flag_seq->values[0].flag
				   || value->flag);
		else
			this_flag_seq->values[0].flag = value->flag;
		this_flag_seq->len = 1;
		break;
	}
}

/*
 * Parse a single argument (the switch and its data, if any) from the command
 * line.  Return 1 if the `next' command line word was consumed; 0 if only the
 * current command line word was consumed; or -1 if there was a parsing error.
 */
static int
parse_arg(/* const */ char *	rest, /* rest of the current argument */
	  /* const */ char *	next, /* the next argument (may be 0) */
	  flags_in *		this_flag,
	  flag_value_seq *	this_flag_seq,
	  flags_out *		res)
{
	char *		arg_data;
	int		arg_data_consumed;
	flag_value	value;
	int		parse_error;
	int		i;
	
	/*****/
	
	if (rest[0]) {
		arg_data = rest;
		arg_data_consumed = 0;
	} else {
		arg_data = next;
		arg_data_consumed = (this_flag->kind != fk_FLAG);
	}
	parse_error = 0;
	
	switch (this_flag->kind) {
	case fk_FLAG:
		if (arg_data) {
			if (!strcmp(arg_data, "1")
			    || !strcmp(arg_data, "y")
			    || !strcmp(arg_data, "yes")) {
				value.flag = 1;
				flag_add_value(this_flag,
					       this_flag_seq,
					       &value);
				arg_data_consumed = (arg_data == next);
				
			} else if (!strcmp(arg_data, "0")
				   || !strcmp(arg_data, "n")
				   || !strcmp(arg_data, "no")) {
				value.flag = 0;
				flag_add_value(this_flag,
					       this_flag_seq,
					       &value);
				arg_data_consumed = (arg_data == next);
				
			} else if (rest[0] == 0) {
				/* Parse `-x' as setting the flag. */
				value.flag = 1;
				flag_add_value(this_flag,
					       this_flag_seq,
					       &value);
				arg_data_consumed = 0;
				
			} else {
				parse_error = 1;
			}
			
		} else {
			/* Parse `-x' as setting the flag. */
			value.flag = 1;
			flag_add_value(this_flag,
				       this_flag_seq,
				       &value);
			arg_data_consumed = 0;
		}
		break;
		
	case fk_STRING:
		if (arg_data) {
			value.string = arg_data;
			flag_add_value(this_flag, this_flag_seq, &value);
		} else {
			parse_error = 1;
		}
		break;
		
	case fk_NUMBER:
		if (arg_data) {
			value.number = 0;
			for (i = 0; arg_data[i]; ++i) {
				if ((arg_data[i] < '0')
				    || (arg_data[i] > '9'))
					break;
				value.number = value.number * 10
					       + (arg_data[i] - '0');
			}
			if (!arg_data[i]) {
				flag_add_value(this_flag,
					       this_flag_seq,
					       &value);
			} else {
				parse_error = 1;
			}
			
		} else {
			parse_error = 1;
		}
		break;
	}
	
	if (parse_error) {
		res->error = 1;
		return -1;
	}
	
	return arg_data_consumed;
}

/*
 * Parse the `argv' command line according to the flags described by `flags',
 * and return a `flags' out structure describing the parse.
 */
flags_out
parse_args(int argc, char *argv[], int flag_count, flags_in *flags)
{
	flags_out res;
	int i;
	
	/*****/
	
	/*
	 * Initialize `res', which will be our return value.
	 */
	
	res.progname = argv[0];
	
	res.error = 0;
	res.other_count = 0;
	res.other = 0;
	
	res.flag_seqs = (flag_value_seq *)
			mustmalloc(sizeof(flag_value_seq) * flag_count);
	for (i = 0; i < flag_count; ++i) {
		int seq_max;
		
		res.flag_seqs[i].len = 0;
		
		seq_max = flags[i].max_occur;
		/*
		 * Special case: if this flag may appear any number of times,
		 * only preallocate one `flag_value' in our value vector.
		 */
		if (FLAG_UNLIMITED_P(seq_max))
			seq_max = 1;
		
		res.flag_seqs[i].max = seq_max;
		res.flag_seqs[i].values = (flag_value *)
					  mustmalloc(sizeof(flag_value)
						     * seq_max);
	}
	
	/*
	 * Parse the command line.
	 */
	
	for (i = 1; i < argc; ++i) {
		if (argv[i][0] != '-') {
			/*
			 * It's not a valid option --- add it to the list of
			 * other arguments.
			 */
			res.other = (char **)
				    mustrealloc(res.other,
						(++res.other_count
						 * sizeof(char *)));
			res.other[res.other_count - 1] = argv[i];
			
		} else if (argv[i][1] != '-') {
			/*
			 * It's a short form (`-') option.  Short form options
			 * may be concatenated with their values (e.g., `-s1')
			 * or they may precede their values (e.g., `-s 1').
			 */
			int j, OK = 0;
			
			for (j = 0; j < flag_count && !OK; j++) {
				if (flags[j].sng
				    && (argv[i][1] == flags[j].sng)) {
					/*
					 * We found the flag, parse its args.
					 */
					int add =
						parse_arg(
							&(argv[i][2]),
							argv[i+1],
							&(flags[j]),
							&(res.flag_seqs[j]),
							&res);
					
					if (add >= 0) {
						i += add;
						OK = 1;
					}
					break;
				}
			}
			if (!OK) {
				res.other = (char **)
					    mustrealloc(res.other,
							(++res.other_count
							 * sizeof(char *)));
				res.other[res.other_count - 1] = argv[i];
				res.error = 1;
			}
			
		} else {
			/*
			 * It's a long form (`--') option.  A long form option
			 * may be concatenated with its value if the value is
			 * preceded by `=' (e.g., `--this=that').  Long form
			 * options may also may precede their values (e.g.,
			 * `--this that').
			 *
			 * Concatenation requires `=' so that we can identify
			 * the end of the option name.  Otherwise, we couldn't
			 * allow string-valued long-form options to share a
			 * common prefix.  For example, we couldn't have both
			 * `--client' and `--client_type'.
			 */
			int j, OK = 0;
			
			for (j = 0; j < flag_count && !OK; j++) {
				int len;
				
				if (!flags[j].dbl)
					continue;
				
				len = strlen(flags[j].dbl);
				
				if (!strncmp(flags[j].dbl, &(argv[i][2]),
					     len)
				    && ((argv[i][2+len] == 0)
					|| (argv[i][2+len] == '='))) {
					/*
					 * We found the flag, parse its args.
					 */
					int add =
						parse_arg(
							(argv[i][2+len] ?
							 /* Strip `='. */
							 &(argv[i][2+len+1]) :
							 /* Otherwise... */
							 &(argv[i][2+len])),
							argv[i+1],
							&(flags[j]),
							&(res.flag_seqs[j]),
							&res);
					
					if (add >= 0) {
						i += add;
						OK = 1;
					}
					break;
				}
			}
			if (!OK) {
				res.other = (char **)
					    mustrealloc(res.other,
							(++res.other_count
							 * sizeof(char *)));
				res.other[res.other_count - 1] = argv[i];
				res.error = 1;
			}
		}
	}
	
	/*
	 * Set the default values of flags that were not specified on the
	 * command line.
	 */
	for (i = 0; i < flag_count; i++) {
		if (res.flag_seqs[i].len == 0) {
			flag_add_value(&(flags[i]),
				       &(res.flag_seqs[i]),
				       &(flags[i].dfault));
		}
	}
	
	/*
	 * Signal an error if any flag was specified more than its specified
	 * maximum number of times.
	 */
	for (i = 0; i < flag_count; i++) {
		if (!FLAG_UNLIMITED_P(flags[i].max_occur)
		    && (res.flag_seqs[i].len
			> (unsigned int) flags[i].max_occur)) {
			fprintf(stderr,
				"%s: too many occurrences of ",
				res.progname);
			if (flags[i].sng)
				fprintf(stderr,
					"`-%c'%s",
					flags[i].sng,
					(flags[i].dbl ? " or " : ""));
			if (flags[i].dbl)
				fprintf(stderr, "`--%s' ", flags[i].dbl);
			fprintf(stderr, "(max. %d).\n", flags[i].max_occur);
			
			res.error = 1;
		}
	}
	
	return res;
}

/*****************************************************************************/

/*
 * Print a program usage description to `stderr'.
 */
void
print_args_usage(const char *progname,
		 int flag_count, flags_in *flags,
		 const char *cl_extra, const char *extra)
{
	int i;
	
	fprintf(stderr, "Usage: %s <options> %s\n\n", progname, cl_extra);
	
	for (i = 0; i < flag_count; i++) {
		/* Print the short and long forms of the flag. */
		if (flags[i].sng)
			fprintf(stderr, "-%c", flags[i].sng);
		if (flags[i].dbl)
			fprintf(stderr, "%s--%s",
				(flags[i].sng ? " / " : ""),
				flags[i].dbl);
		
		/* Print the type and explanation. */
		fprintf(stderr, "%s:\n\t%s.\n",
			((flags[i].kind == fk_FLAG) ? "" :
			 (flags[i].kind == fk_NUMBER) ? " <number>" :
			 (flags[i].kind == fk_STRING) ? " <string>" :
			 " <unknown type>"),
			(flags[i].explain ?
			 flags[i].explain :
			 "(No documentation available)"));
		if (flags[i].max_occur != 1) {
			if (FLAG_UNLIMITED_P(flags[i].max_occur)) {
				fprintf(stderr,
					"\tMay be repeated; ");
				switch (flags[i].max_occur) {
				case FLAG_UNLIMITED_LIST:
					fprintf(stderr,
						"values are collected");
					break;
				case FLAG_UNLIMITED_USE_LAST:
					fprintf(stderr,
						"last value is used");
					break;
				case FLAG_UNLIMITED_OR:
					fprintf(stderr,
						("flag is true if set by any "
						 "occurrence"));
					break;
				default:
					fprintf(stderr,
						"(bad collection strategy)");
					break;
				}
				fprintf(stderr, ".\n");
			} else
				fprintf(stderr,
					"\tMay be repeated up to %d times.\n",
					flags[i].max_occur);
		}
	}
	if (extra)
		fprintf(stderr, "\n%s\n", extra);
}


/*****************************************************************************/

/*
 * Fill `in' with the descriptions of the flags that are common to all Flick
 * programs.
 */
void
set_def_flags(flags_in *in)
{
	in[0].sng = 'v';
	in[0].dbl = "version";
	in[0].kind = fk_FLAG;
	in[0].max_occur = FLAG_UNLIMITED_OR;
	in[0].dfault.flag = 0;
	in[0].explain = "Print out the version information for this program";
	
	in[1].sng = 'u';
	in[1].dbl = "usage";
	in[1].kind = fk_FLAG;
	in[1].max_occur = FLAG_UNLIMITED_OR;
	in[1].dfault.flag = 0;
	in[1].explain = "Print out this message";
	
	in[OUTPUT_FILE_FLAG].sng = 'o';
	in[OUTPUT_FILE_FLAG].dbl = "output";
	in[OUTPUT_FILE_FLAG].kind = fk_STRING;
	in[OUTPUT_FILE_FLAG].max_occur = 1;
	in[OUTPUT_FILE_FLAG].dfault.string = 0;
	in[OUTPUT_FILE_FLAG].explain = "Optional output file name";
	
	in[3].sng = '?';
	in[3].dbl = "help";
	in[3].kind = fk_FLAG;
	in[3].max_occur = FLAG_UNLIMITED_OR;
	in[3].dfault.flag = 0;
	in[3].explain = "Print out this message";
}

/*
 * Handle the standard Flick common line options.
 */
void
std_handler(flags_out out, int flag_count, flags_in *in,
	    const char *cl_info, const char *info)
{
	int print_version;
	int print_usage;
	int quit = 0;
	
	/* `-v' occurrences are `or'ed by the parser. */
	print_version = out.flag_seqs[0].values[0].flag;
	/* `-u' and `-?' occurrences are also `or'ed by the parser. */
	print_usage = (out.flag_seqs[1].values[0].flag
		       || out.flag_seqs[3].values[0].flag);
	
	/* If requested, print the version. */
	if (print_version) {
		fprintf(stderr, "%s version %s\n",
			out.progname, FLICK_VERSION);
		quit = 1;
	}
	
	/* If requested, or if a parsing error occurred, print usage. */
	if (out.error || print_usage) {
		print_args_usage(out.progname, flag_count, in, cl_info, info);
		quit = 1;
	}
	
	if (quit)
		exit(out.error);
}


/*****************************************************************************/

/*
 * Print a flag description to `stderr'.  This is an auxiliary function for
 * `print_args_flags', below.
 */
static void
print_args_flag_desc(flags_in *flag)
{
	/* Print the short and long forms of the flag. */
	if (flag->sng)
		fprintf(stderr, "-%c", flag->sng);
	if (flag->dbl)
		fprintf(stderr, "%s--%s",
			(flag->sng ? " / " : ""),
			flag->dbl);
	
	/* Print the flag type. */
	fprintf(stderr, " <");
	switch (flag->kind) {
	case fk_FLAG:
		fprintf(stderr, "flag");
		break;
	case fk_STRING:
		fprintf(stderr, "string");
		break;
	case fk_NUMBER:
		fprintf(stderr, "number");
		break;
	default:
		fprintf(stderr, "unknown type");
		break;
	}

	/* Print the maximum number of occurrences. */
	fprintf(stderr, ", ");
	if (FLAG_UNLIMITED_P(flag->max_occur)) {
		fprintf(stderr, "unlimited occurrences, ");
		switch (flag->max_occur) {
		case FLAG_UNLIMITED_LIST:
			fprintf(stderr, "values are collected");
			break;
		case FLAG_UNLIMITED_USE_LAST:
			fprintf(stderr, "last value is used");
			break;
		case FLAG_UNLIMITED_OR:
			fprintf(stderr, "true if set by any occurrence");
			break;
		default:
			fprintf(stderr, "(bad collection strategy)");
			break;
		}
		
	} else {
		fprintf(stderr,
			"at most %d occurence%s",
			flag->max_occur,
			((flag->max_occur == 1) ? "" : "s"));
	}
	fprintf(stderr, ">:\n");
	
	/* Print the explanation. */
	fprintf(stderr,
		"\t%s.\n",
		(flag->explain ?
		 flag->explain :
		 "(No documentation available)"));
	
	/* Print the default value. */
	fprintf(stderr, "\tdefault: ");
	switch (flag->kind) {
	case fk_FLAG:
		fprintf(stderr, (flag->dfault.flag ? "true" : "false"));
		break;
	case fk_STRING:
		if (flag->dfault.string)
			fprintf(stderr, "\"%s\"", flag->dfault.string);
		else
			fprintf(stderr, "(null)");
		break;
	case fk_NUMBER:
		fprintf(stderr, "%d", flag->dfault.number);
		break;
	default:
		fprintf(stderr, "(unknown)");
		break;
	}
	fprintf(stderr, "\n");
}

/*
 * Print a flag's values to `stderr'.  This is an auxiliary function for
 * `print_args_flags', below.
 */
static void
print_args_flag_values(flags_in *flag, flag_value_seq *flag_values)
{
	unsigned int i;
	
	if (flag_values->len <= 0) {
		fprintf(stderr, "\tno values!\n");
		return;
	}
	
	for (i = 0; i < flag_values->len; ++i) {
		if ((flag->max_occur == 1) && (flag_values->len == 1))
			/* Common case: one value allowed, one specified. */
			fprintf(stderr, "\tvalue: ");
		else
			/* General case. */
			fprintf(stderr, "\tvalue %u: ", i);
		
		switch (flag->kind) {
		case fk_FLAG:
			fprintf(stderr,
				(flag_values->values[i].flag ?
				 "true" : "false"));
			break;
			
		case fk_STRING:
			if (flag_values->values[i].string)
				fprintf(stderr,
					"\"%s\"",
					flag_values->values[i].string);
			else
				fprintf(stderr, "(null)");
			break;
		case fk_NUMBER:
			fprintf(stderr, "%d", flag_values->values[i].number);
			break;
		default:
			fprintf(stderr, "(unknown)");
			break;
		}
		fprintf(stderr, "\n");
	}
}

/*
 * A debugging routine to print the parsed command line.
 */
void
print_args_flags(flags_out res, int flag_count, flags_in *flags)
{
	int i;
	
	char **name, **value, **dfault;
	
	name = (char **) mustcalloc(sizeof(char *) * flag_count);
	value = (char **) mustcalloc(sizeof(char *) * flag_count);
	dfault = (char **) mustcalloc(sizeof(char *) * flag_count);
	
	fprintf(stderr, "Flags for %s:\n", res.progname);
	
	for (i = 0; i < flag_count; i++) {
		print_args_flag_desc(&(flags[i]));
		print_args_flag_values(&(flags[i]), &(res.flag_seqs[i]));
	}
	
	if (res.other_count)
		fprintf(stderr, "\nAdditional command line arguments:\n");
	for (i = 0; i < res.other_count; ++i)
		fprintf(stderr, "%s\n", res.other[i]);
}


/*****************************************************************************/

/* End of file. */



syntax highlighted by Code2HTML, v. 0.9.1