/* cmdline.c -- command line parsing routines */
/*
 * This module is designed to allow various modules to scan (and rescan)
 * the command line for applicable arguments.  The goal is to hide as
 * much information about switches and their names as possible so that
 * switches become more consistent across applications and so that the
 * author of an application need not do a lot of work to provide numerous
 * options.  Instead, each module scans the command line for its own
 * arguments.
 *
 * Command lines are of the following form:
 *	command -s1 -s2 opt2 -s3 arg1 arg2 -s4 opt4 arg3
 * Note that there are three kinds of command line parameters:
 * (1) A Switch is a "-" followed by a name, e.g. "-s1"
 * (2) An Option is a Switch followed by a space and name, e.g. "-s2 opt2"
 * (3) An Argument is a name by itself, e.g. "arg1"
 * Note also that a switch followed by an argument looks just like an
 * option, so a list of valid option names is necessary to disambiguate.
 *
 * A main program that uses cmdline.c should do the following:
 *	(1) create an array of pointers to strings (char *names[]) that
 *		contains every possible option name
 *	(2) create another array of pointers to strings that contains
 *		every possible switch name
 *	(2) call cl_init(switches, nsw, options, nopt, argv, argc)
 * cl_init will report an error (to stderr) if it finds any illegal
 * switch or option names.
 *
 * Afterward, switches, options, and arguments can be accessed by
 * calling cl_switch, cl_option, and cl_arg.  If cl_switch or cl_option
 * is called with a switch name that was not mentioned in the call to 
 * cl_init, an error will result.  This indicates that the application
 * author omitted a valid switch or option name when calling cl_init.
 * This is an error because the full set of names is needed for error
 * checking and to distinguish arguments from options.
 *
 * cl_nswitch and cl_noption are similar to cl_switch and cl_option,
 * except they each take a list of equivalent switch or option names.  
 * This makes it simple to allow both verbose (-debug) and terse (-d) names.
 */

/*****************************************************************************
*	    Change Log
*  Date	    | Change
*-----------+-----------------------------------------------------------------
* 13-Jun-86 | Created Change Log
*  6-Aug-86 | Modified for Lattice 3.0 -- use "void" to type some routines
*****************************************************************************/

#include "cext.h"
#include "stdio.h"
#include "ctype.h"
#include "cmdline.h"

private char **voptions;	/* valid options */
private int noptions;		/* number of options */
private char **vswitches;	/* valid switches */
private int nswitches;		/* number of switches */
private char **argv;		/* command line argument vector */
private int argc;		/* length of argv */

private int cl_rdy = false;	/* set to true when initialized */

/*****************************************************************************
*	Routines local to this module
*****************************************************************************/
private	void	check_names();
private int	find_match();
private int	find_string();
private	void	ready_check();

/****************************************************************
*			check_names
* Inputs:
*	char *names[]:	array of alternative switch or option names
*	int nnames:	number of alternative switch or option names
*	char *valid[]:	array of valid names
*	int nvalid:	number of valid names
* Effect:
*	Checks that all names are in validnames.  If not, print
*	an error message.
*****************************************************************/

private void check_names(names, nnames, valid, nvalid)
    char *names[];
    int nnames;
    char *valid[];
    int nvalid;
{
    int i;	/* loop counters */
    for (i = 0; i < nnames; i++) {
	if (find_string(names[i], valid, nvalid) >= nvalid) {
	    fprintf(stderr, "internal error detected by cmdline module:\n");
	    fprintf(stderr, "\t'%s' should be in valid lists\n", names[i]);
	}
    }
}

/****************************************************************
*			cl_arg
* Inputs:
*	n: the index of the arg needed
* Results:
*	pointer to the nth arg, or NULL if none exists
*	arg 0 is the command name
*****************************************************************/

char *cl_arg(n)
    int n;
{
    int i = 1;
    if (n <= 0) return argv[0];
    while (i < argc) {
	if (*argv[i] == '-') {
	    if (find_string(argv[i], voptions, noptions) < noptions)
		i += 2; /* skip name and option */
	    else i += 1; /* skip over switch name */
	} else if (n == 1) {
	    return argv[i];
	} else { /* skip over argument */
	    n--;
	    i++;
	}
    }
    return NULL;
}

/*****************************************************************************
*			cl_init
* Inputs:
*	char *switches[]:	array of switch names
*	int nsw:		number of switch names
*	char *options[]:	array of option names
*	int nopt:		number of option names
*	char *av:		array of command line fields (argv)
*	int ac:			number of command line fields (argc)
* Effect:
*	Checks that all command line entries are valid.
*	Saves info for use by other routines.
* Returns:
*	True if syntax checks OK, otherwise false
*****************************************************************************/

boolean cl_init(switches, nsw, options, nopt, av, ac)
    char *switches[];
    int nsw;
    char *options[];
    int nopt;
    char *av[];
    int ac;
{
    int i;	/* index into argv */
    boolean result = true;

    vswitches = switches;	nswitches = nsw;
    voptions = options;		noptions = nopt;
    argv = av;			argc = ac;

    for (i = 1; i < argc; i++) {  /* case fold lower */
	int j;
	for (j = 0; j < strlen(argv[i]); j++)
	    if (isupper(argv[i][j])) 
		argv[i][j] = tolower(argv[i][j]);
    }

    /* check command line syntax: */
    i = 1;
    while (i < argc) {
	if (*argv[i] == '-') {
	    if (find_string(argv[i], voptions, noptions) < noptions) {
		i += 1; /* skip name and option */
		if (i < argc && *argv[i] == '-') {
		    fprintf(stderr, "missing argument after %s\n", argv[i-1]);
		    result = false;
		    i += 1;
		}
	    } else if (find_string(argv[i], vswitches, nswitches) < 
		       nswitches) {
		i += 1; /* skip over switch name */
	    } else {
		fprintf(stderr, "invalid switch: %s\n", argv[i]);
		i += 1;
		result = false;
	    }
	} else i++; /* skip over argument */
    }
    cl_rdy = true;
    return result;
}

/****************************************************************
*			cl_noption
* Inputs:
*	char *names[]:	array of alternative switch names
*	int nnames:	number of alternative switch names
* Result:
*	returns pointer to  if one exists, otherwise null
* Effect:
*	looks for pattern in command line of the form "-n s",
*	where n is a member of names.  Returns pointer to s.
* Implementation:
*	find the option name, then
*	see if the switch is followed by a string that does
*	not start with "-"
*****************************************************************/

char *cl_noption(names, nnames)
    char *names[];
    int nnames;
{
    int i;	/* index of switch */

    ready_check();
    check_names(names, nnames, voptions, noptions);
    i = find_match(names, nnames) + 1; /* point at the option */
    if (i < argc) { /* make sure option exists */
	if (*(argv[i]) != '-') return argv[i];
    }
    return NULL;
}

/*****************************************************************
*			cl_nswitch
* Inputs:
*	char *names[]:	array of alternative switch names
*	int nnames:	number of alternative switch names
* Effect:
*	Checks that names is valid.
*	Finds a pattern in command line of the form "-n", where
*	n is a member of names.
* Result:
*	returns pointer to command line switch if one exists,
*	otherwise null
*****************************************************************/

char *cl_nswitch(names, nnames)
    char *names[];
    int nnames;
{
    int i;	/* index of switch */

    ready_check();
    check_names(names, nnames, vswitches, nswitches);
    i = find_match(names, nnames);
    if (i < argc) return argv[i];
    /* else */ return NULL;
}

/****************************************************************
*			cl_option
* Inputs:
*	char *name:	option name
* Outputs:
*	returns char *: the option string if found, otherwise null
****************************************************************/

char *cl_option(name)
    char *name;
{
    char *names[1];	/* array to hold name */

    names[0] = name;
    return cl_noption(names, 1);
}

/****************************************************************
*			cl_switch
* Inputs:
*	char *name:	switch name
* Outputs:
*	boolean:	true if switch found
****************************************************************/

boolean cl_switch(name)
    char *name;
{
    char *names[1];	/* array to hold name */

    names[0] = name;
    return cl_nswitch(names, 1) != NULL;
}

/****************************************************************
*			find_match
* Inputs:
*	char *names[]:	array of alternative switch or option names
*	int nnames:	number of alternative switch or option names
* Effect:
*	Looks for command line switch that matches one of names.
* Returns:
*	Index of switch if found, argc if not found.
*****************************************************************/

private int find_match(names, nnames)
    char *names[];
    int nnames;
{
    int j;	/* loop counter */
    for (j = 0; j < argc; j++) {
	if (find_string(argv[j], names, nnames) < nnames) return j;
    }
    return argc;
}

/****************************************************************
*			find_string
* Inputs:
*	char *s:	string to find
*	char *names[]:	array of strings
*	int nnames:	number of strings
* Effect:
*	Looks for s in names
* Returns:
*	Index of s in names if found, nnames if not found
*****************************************************************/

private int find_string(s, names, nnames)
    char *s;
    char *names[];
    int nnames;
{
    int i; /* loop counter */
    for (i = 0; i < nnames; i++) {
	if (strcmp(s, names[i]) == 0) {
	    return i;
	}
    }
    return nnames;
}

/****************************************************************
*			ready_check
* Effect:
*	Halt program if cl_rdy is not true.
*****************************************************************/
private void ready_check()
{
    if (!cl_rdy) {
	fprintf(stderr,
	    "Internal error: cl_init was not called, see cmdline.c\n");
	exit(1);
    }
}
