/* cmdline.c -- command line parsing routines */ /* Copyright 1989 Carnegie Mellon University */ /* * 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. * * Long names are good for readability, but single character abbreviations * are nice for experienced users. cmdline.c allows single character * abbreviations provided that they are unambiguous. These are * recognized with no extra work by the programmer. If an * isolated '?' is encountered in the command line, then all of * the options and switches are printed as help and for debugging. * * Given that we must tell this module about option names and switch * names, how should we do it? We can't wait until modules are * initialized, since often modules want to read the command line * at initialization time. In the original implementation, the * main program was supposed to provide names for the whole program, * but this violates modularity: when an option is added to a module, * the main program has to be modified too. This is a real pain when * different machines support different options and you want to have * a single machine-independent main program. The solution is to * have the main program import strings describing the options and * switches used by each module. These are passed into cmdline.c * before initialization of other modules is begun. * * A main program that uses cmdline.c should do the following: * call cl_syntax(s) for each module's option/switch string. * The string s should have the following format: * "opt1description;opt2description;...;switch1description;..." * where opt1 and opt2 are option names (without the preceding "-"), and * switch1 is a switch name. The and indicate whether the * name is an option or a switch. The descriptions are arbitrary strings * (without semicolons) that are printed out for the user when "?" * is typed on the command line. * * After calling cl_syntax, main() should call * cl_init(argv, argc) * cl_init will report an error (to STDERR) if it finds any illegal * switch or option names in argv, and help will be printed if "?" * is found in argv. If cl_init returns false, then the user has been * given an error message or help, and main should probably exit. * * 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. * */ /***************************************************************************** * Change Log * Date | Change *-----------+----------------------------------------------------------------- * 13-Jun-86 | Created Change Log * 6-Aug-86 | Modified for Lattice 3.0 -- use "void" to type some routines * 20-Sep-89 | Redesigned the interface, adding cl_syntax call. * 2-Apr-91 | JDW : further changes * 27-Dec-93 | "@file" as first arg reads command line args from file * 11-Mar-94 | PLu: Add private to cl_search() definition. * 28-Apr-03 | DM: true->TRUE, false->FALSE *****************************************************************************/ /* stdlib.h not on PMAX */ #ifndef mips #include "stdlib.h" #endif #include "stdio.h" #include "cext.h" #include "userio.h" #include "cmdline.h" #include "ctype.h" #include "string.h" #define syntax_max 10 /* allow for 10 syntax strings */ private char *syntax[syntax_max]; private int n_syntax = 0; /* number of strings so far */ private char **argv; /* command line argument vector */ private int argc; /* length of argv */ private boolean cl_rdy = FALSE; /* set to TRUE when initialized */ #define cl_OPT 1 #define cl_SW 2 #define cl_INIT 3 #define cl_ARG 4 /***************************************************************************** * Routines local to this module *****************************************************************************/ private char *cl_search(); private int find_string(); private void indirect_command(char *filename, char *oldarg0); private void ready_check(); /**************************************************************** * 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; { return (n <= 0 ? argv[0] : cl_search((char *)NULL, cl_ARG, n)); } /* cl_help -- print help from syntax strings */ /**/ void cl_help() { register int i, j; int count = 0; /* see if there are any switches or flags */ for (i = 0; i < n_syntax; i++) { register char *ptr = syntax[i]; register char c = *ptr++; while (c != EOS) { while (c != EOS && !(isalnum(c))) c = *ptr++; if (c != EOS) { count++; gprintf(TRANS, "-"); j = 1; while (c != EOS && c != '<') { gprintf(TRANS, "%c", c); c = *ptr++; j++; } if (c != EOS) { c = *ptr++; if (c == 'o') { gprintf(TRANS, " xxx"); j += 4; } } /* attempt to tab */ do { gprintf(TRANS, " "); } while (j++ < 16); while (c != EOS && c != '>') c = *ptr++; if (c != EOS) c = *ptr++; while (c != EOS && c != ';') { gprintf(TRANS, "%c", c); c = *ptr++; } gprintf(TRANS, "\n"); } } } if (!count) gprintf(TRANS, "No switches or options exist.\n"); } /***************************************************************************** * 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(av, ac) char *av[]; int ac; { argv = av; argc = ac; /* check for help request */ if (argc == 2 && strcmp(argv[1], "?") == 0) { cl_help(); return FALSE; /* avoid cl_search which would complain about "?" */ } /* check for indirection */ if (argc == 2 && *(argv[1]) == '@') { /* read new args from file */ indirect_command(av[1] + 1, av[0]); } /* check command line syntax: */ cl_rdy = TRUE; return (cl_rdy = (cl_search("true", cl_INIT, 0) != NULL)); } /**************************************************************** * cl_int_option * Inputs: * char *name: name of option * long default: default value for option * Result: * returns long encoding of the option, deflt if none * Implementation: * call cl_option and sscanf result *****************************************************************/ long cl_int_option(name, deflt) char *name; long deflt; { char *opt = cl_option(name); if (opt) { if (sscanf(opt, "%ld", &deflt) != 1) { gprintf(TRANS, "Warning: option %s %s not an integer, ignored\n", name, opt); } } return deflt; } /**************************************************************** * cl_search * Inputs: * char *name: name of field, must be non-null if opt_sw == cl_INIT * int opt_sw: option, switch, init, or arg * int n: argument number (if opt_sw is cl_ARG) * Result: * returns pointer to option value/switch if one exists, otherwise null * Implementation: * parse the command line until name or arg is found * see if the option is followed by a string that does * not start with "-" *****************************************************************/ private char *cl_search(name, opt_sw, n) char *name; int opt_sw; int n; /* if opt_sw is cl_ARG, n > 0 tells which one */ { register int i = 1; /* index into command line */ boolean abbr; boolean result = TRUE; ready_check(); /* parse command line: */ while (i < argc) { register char *arg = argv[i]; /* arguments that start with '-' should be quoted and quotes must be removed by the application */ if (*arg == '-') { int arg_type = find_string(arg + 1, &abbr); if (arg_type == cl_OPT) { i += 1; /* skip name and option */ /* don't look for '-' because the option might be a * negative number */ if (i >= argc /* || *arg == '-' */) { if (opt_sw == cl_INIT) { gprintf(ERROR, "missing argument after %s\n", arg); result = FALSE; } } else if (opt_sw == cl_OPT && (strcmp(arg + 1, name) == 0 || (abbr && *(arg + 1) == name[0]))) { return argv[i]; } } else if (arg_type == cl_SW) { if (opt_sw == cl_SW && (strcmp(arg + 1, name) == 0 || (abbr && *(arg + 1) == name[0]))) return arg; } else if (opt_sw == cl_INIT) { gprintf(ERROR, "invalid switch: %s\n", arg); result = FALSE; } } else if (opt_sw == cl_ARG) { if (n == 1) return arg; n--; } i++; /* skip to next field */ } if (opt_sw == cl_INIT) { /* return name or NULL to represent TRUE or FALSE */ return (result ? name : NULL); } 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; { return cl_search(name, cl_OPT, 0); } /**************************************************************** * cl_switch * Inputs: * char *name: switch name * Outputs: * boolean: TRUE if switch found ****************************************************************/ boolean cl_switch(name) char *name; { return (boolean)(cl_search(name, cl_SW, 0) != NULL); } /* cl_syntax -- install a string specifying options and switches */ /**/ boolean cl_syntax(char *s) { if (n_syntax < syntax_max) { syntax[n_syntax++] = s; return TRUE; } else { gprintf(ERROR, "cl_syntax: out of room\n"); return FALSE; } } /**************************************************************** * find_string * Inputs: * char *s: string to find, terminated by any non-alphanumeric * boolean *abbr: set TRUE if s is an abbreviation, otherwise false * Effect: * Looks for s in syntax strings * Returns: * 0 = FALSE = not found, 1 = cl_OPT = option, 2 = cl_SW = switch *****************************************************************/ private int find_string(s, abbr) char *s; boolean *abbr; { int found_it = FALSE; int i; *abbr = FALSE; for (i = 0; i < n_syntax; i++) { /* loop through strings */ register char *syntax_ptr = syntax[i]; while (*syntax_ptr != EOS) { register char *s_ptr = s; while (*syntax_ptr != EOS && !(isalnum(*syntax_ptr))) syntax_ptr++; while (*s_ptr != EOS && (*s_ptr++ == *syntax_ptr)) syntax_ptr++; /* only increment if there's a match */ if (!(isalnum(*s_ptr)) && *syntax_ptr == '<') { syntax_ptr++; /* advance to the type field */ if (*syntax_ptr == 's') return cl_SW; if (*syntax_ptr != 'o') gprintf(ERROR, "(internal error) bad cl_syntax string: %s\n", syntax[i]); return cl_OPT; } /* no match, so go to next */ while (*syntax_ptr != ';' && *syntax_ptr != EOS) syntax_ptr++; if (*syntax_ptr == ';') syntax_ptr++; } } /* no match, maybe there is a single character match */ if (s[0] == EOS || s[1] != EOS) return FALSE; for (i = 0; i < n_syntax; i++) { /* loop through strings */ char *syntax_ptr = syntax[i]; while (*syntax_ptr != EOS) { while (*syntax_ptr != EOS && !(isalnum(*syntax_ptr))) syntax_ptr++; if (s[0] == *syntax_ptr) { if (found_it) return FALSE; /* ambiguous */ /* else, find the type */ while (*syntax_ptr != '<' && *syntax_ptr != EOS) syntax_ptr++; syntax_ptr++; if (*syntax_ptr == 's') found_it = cl_SW; else if (*syntax_ptr == 'o') found_it = cl_OPT; else return FALSE; /* error in string syntax */ } /* no match, so go to next */ while (*syntax_ptr != ';' && *syntax_ptr != EOS) syntax_ptr++; if (*syntax_ptr == ';') syntax_ptr++; } } if (found_it) *abbr = TRUE; return found_it; } /* get_arg -- get an argument from a file */ /**/ boolean get_arg(file, arg) FILE *file; char *arg; { int c; while ((c = getc(file)) != EOF && isspace(c)) ; if (c == EOF) return FALSE; ungetc(c, file); while ((c = getc(file)) != EOF && !isspace(c)) { *arg++ = c; } *arg = 0; return TRUE; } /* indirect_command -- get argv, argc from a file */ /**/ private void indirect_command(filename, oldarg0) char *filename; char *oldarg0; { FILE *argfile = fopen(filename, "r"); if (!argfile) { argv = (char **) malloc(sizeof(char *)); argv[0] = oldarg0; argc = 1; } else { int i = 1; char arg[100]; while (get_arg(argfile, arg)) i++; fclose(argfile); argfile = fopen(filename, "r"); argv = (char **) malloc(sizeof(char *) * i); argv[0] = oldarg0; argc = i; i = 1; while (get_arg(argfile, arg)) { argv[i] = (char *) malloc(strlen(arg) + 1); strcpy(argv[i], arg); i++; } } } /**************************************************************** * ready_check * Effect: * Halt program if cl_rdy is not true. *****************************************************************/ private void ready_check() { if (!cl_rdy) { gprintf(ERROR, "Internal error: cl_init was not called, see cmdline.c\n"); EXIT(1); } }