/* -*- related-file-name: "../include/lcdf/clp.h" -*- */ /* clp.c - Complete source code for CLP. * This file is part of CLP, the command line parser package. * * Copyright (c) 1997-2005 Eddie Kohler, kohler@cs.ucla.edu * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, subject to the conditions * listed in the Click LICENSE file, which is available in full at * http://www.pdos.lcs.mit.edu/click/license.html. The conditions include: you * must preserve this copyright notice, and you cannot mention the copyright * holders in advertising related to the Software without their permission. * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This * notice is a summary of the Click LICENSE file; the license in that file is * legally binding. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include /* By default, assume we have strtoul. */ #if !defined(HAVE_STRTOUL) && !defined(HAVE_CONFIG_H) # define HAVE_STRTOUL 1 #endif #ifdef __cplusplus extern "C" { #endif /* Option types for Clp_SetOptionChar */ #define Clp_DoubledLong (Clp_LongImplicit * 2) #define Clp_AnyArgument (Clp_Mandatory | Clp_Optional) #define MAX_AMBIGUOUS_VALUES 4 typedef struct { Clp_ArgParseFunc func; int flags; void *thunk; } Clp_ArgType; typedef struct { int pos; int neg; } Clp_LongMinMatch; struct Clp_Internal { Clp_Option *opt; Clp_LongMinMatch *long_min_match; int nopt; Clp_ArgType *argtype; int nargtype; const char * const *argv; int argc; unsigned char option_class[256]; int both_short_and_long; char option_chars[3]; const char *text; const char *program_name; void (*error_handler)(const char *); int is_short; int whole_negated; /* true if negated by an option character */ int could_be_short; int option_processing; int ambiguous; int ambiguous_values[MAX_AMBIGUOUS_VALUES]; Clp_Option *current_option; int current_short; int negated_by_no; }; struct Clp_ParserState { const char * const *argv; int argc; char option_chars[3]; const char *text; int is_short; int whole_negated; }; typedef struct Clp_StringList { Clp_Option *items; Clp_LongMinMatch *long_min_match; int nitems; int allow_int; int nitems_invalid_report; } Clp_StringList; #define TEST(o, f) (((o)->flags & (f)) != 0) static int calculate_long_min_match(int, Clp_Option *, int, int, int); static int parse_string(Clp_Parser *, const char *, int, void *); static int parse_int(Clp_Parser *, const char *, int, void *); static int parse_bool(Clp_Parser *, const char *, int, void *); static int parse_double(Clp_Parser *, const char *, int, void *); static int parse_string_list(Clp_Parser *, const char *, int, void *); static int ambiguity_error(Clp_Parser *, int, int *, Clp_Option *, const char *, const char *, ...); /******* * Clp_NewParser, etc. **/ static void check_duplicated_short_options(Clp_Parser *clp, int nopt, Clp_Option *opt, int negated) { int i; int check[256]; for (i = 0; i < 256; i++) check[i] = -1; for (i = 0; i < nopt; i++) if (opt[i].short_name > 0 && opt[i].short_name < 256 && (negated ? TEST(&opt[i], Clp_Negate) : !TEST(&opt[i], Clp_OnlyNegated))) { int sh = opt[i].short_name; if (check[sh] >= 0 && check[sh] != opt[i].option_id) Clp_OptionError(clp, "CLP internal error: more than 1 option has short name '%c'", sh); check[sh] = opt[i].option_id; } } Clp_Parser * Clp_NewParser(int argc, const char * const *argv, int nopt, Clp_Option *opt) /* Creates and returns a new Clp_Parser using the options in 'opt', or 0 on memory allocation failure */ { int i; Clp_Parser *clp = (Clp_Parser *)malloc(sizeof(Clp_Parser)); Clp_Internal *cli = (Clp_Internal *)malloc(sizeof(Clp_Internal)); Clp_LongMinMatch *lmm = (Clp_LongMinMatch *)malloc(sizeof(Clp_LongMinMatch) * nopt); if (!clp || !cli || !lmm) goto failed; clp->internal = cli; cli->long_min_match = lmm; /* Assign arguments (need to do this now so we can call Clp_OptionError) */ cli->argc = argc; cli->argv = argv; { const char *slash = strrchr(argv[0], '/'); cli->program_name = slash ? slash + 1 : argv[0]; } cli->error_handler = 0; /* Get rid of negative option_ids, which are internal to CLP */ for (i = 0; i < nopt; i++) if (opt[i].option_id < 0) { Clp_OptionError(clp, "CLP internal error: option %d has negative option_id", i); opt[i] = opt[nopt - 1]; nopt--; i--; } /* Massage the options to make them usable */ for (i = 0; i < nopt; i++) { /* Enforce invariants */ if (opt[i].arg_type <= 0) opt[i].flags &= ~Clp_AnyArgument; if (opt[i].arg_type > 0 && !TEST(&opt[i], Clp_Optional)) opt[i].flags |= Clp_Mandatory; /* Nonexistent short options have character 256. We know this won't equal any character in an argument, even if characters are signed */ if (opt[i].short_name <= 0 || opt[i].short_name > 255) opt[i].short_name = 256; /* Options that start with 'no-' should be changed to OnlyNegated */ if (opt[i].long_name && strncmp(opt[i].long_name, "no-", 3) == 0) { opt[i].long_name += 3; opt[i].flags |= Clp_Negate | Clp_OnlyNegated; } } /* Check for duplicated short options */ check_duplicated_short_options(clp, nopt, opt, 0); check_duplicated_short_options(clp, nopt, opt, 1); /* Calculate long options' minimum unambiguous length */ for (i = 0; i < nopt; i++) if (opt[i].long_name && !TEST(&opt[i], Clp_OnlyNegated)) lmm[i].pos = calculate_long_min_match (nopt, opt, i, Clp_OnlyNegated, 0); for (i = 0; i < nopt; i++) if (opt[i].long_name && TEST(&opt[i], Clp_Negate)) lmm[i].neg = calculate_long_min_match (nopt, opt, i, Clp_Negate, Clp_Negate); /* Set up clp->internal */ cli->opt = opt; cli->nopt = nopt; cli->argtype = (Clp_ArgType *)malloc(sizeof(Clp_ArgType) * 5); if (!cli->argtype) goto failed; cli->nargtype = 5; for (i = 0; i < 256; i++) cli->option_class[i] = 0; cli->option_class[(unsigned char) '-'] = Clp_Short; cli->both_short_and_long = 0; cli->is_short = 0; cli->whole_negated = 0; cli->option_processing = 1; cli->current_option = 0; /* Add default type parsers */ Clp_AddType(clp, Clp_ArgString, 0, parse_string, 0); Clp_AddType(clp, Clp_ArgStringNotOption, Clp_DisallowOptions, parse_string, 0); Clp_AddType(clp, Clp_ArgInt, 0, parse_int, 0); Clp_AddType(clp, Clp_ArgUnsigned, 0, parse_int, (void *)cli); Clp_AddType(clp, Clp_ArgBool, 0, parse_bool, 0); Clp_AddType(clp, Clp_ArgDouble, 0, parse_double, 0); return clp; failed: /* It should be OK to free(0), but on some ancient systems, it isn't */ if (cli && cli->argtype) free(cli->argtype); if (cli) free(cli); if (clp) free(clp); if (lmm) free(lmm); return 0; } void Clp_DeleteParser(Clp_Parser *clp) /* Destroys the Clp_Parser 'clp' and any associated memory allocated by CLP */ { int i; Clp_Internal *cli; if (!clp) return; cli = clp->internal; /* get rid of any string list types */ for (i = 0; i < cli->nargtype; i++) if (cli->argtype[i].func == parse_string_list) { Clp_StringList *clsl = (Clp_StringList *)cli->argtype[i].thunk; free(clsl->items); free(clsl->long_min_match); free(clsl); } free(cli->argtype); free(cli->long_min_match); free(cli); free(clp); } int Clp_SetOptionProcessing(Clp_Parser *clp, int option_processing) /* Sets whether command line arguments are parsed (looking for options) at all. Each parser starts out with OptionProcessing true. Returns old value. */ { Clp_Internal *cli = clp->internal; int old = cli->option_processing; cli->option_processing = option_processing; return old; } Clp_ErrorHandler Clp_SetErrorHandler(Clp_Parser *clp, void (*error_handler)(const char *)) /* Sets a hook function to be called before Clp_OptionError prints anything. 0 means nothing will be called. */ { Clp_Internal *cli = clp->internal; Clp_ErrorHandler old = cli->error_handler; cli->error_handler = error_handler; return old; } int Clp_SetOptionChar(Clp_Parser *clp, int c, int option_type) /* Determines how clp will deal with short options. option_type must be a sum of: 0 == Clp_NotOption 'c' isn't an option. Clp_Short 'c' introduces a list of short options. Clp_Long 'c' introduces a long option. Clp_ShortNegated 'c' introduces a negated list of short options. Clp_LongNegated 'c' introduces a negated long option. Clp_LongImplicit 'c' introduces a long option, and *is part of the long option itself*. Some values are not allowed (Clp_Long | Clp_LongNegated isn't allowed, for instance). c=0 means ALL characters are that type. Returns 0 on failure (you gave an illegal option_type), 1 on success. */ { int i; Clp_Internal *cli = clp->internal; if (option_type < 0 || option_type >= 2*Clp_LongImplicit || ((option_type & Clp_Short) && (option_type & Clp_ShortNegated)) || ((option_type & Clp_Long) && (option_type & Clp_LongNegated)) || ((option_type & Clp_LongImplicit) && (option_type & (Clp_Short | Clp_ShortNegated | Clp_Long | Clp_LongNegated)))) return 0; if (c == 0) for (i = 1; i < 256; i++) cli->option_class[i] = option_type; else cli->option_class[(unsigned char) c] = option_type; /* If an option character can introduce either short or long options, then we need to fix up the long_min_match values. We may have set the long_min_match for option 'abcde' to 1, if no other option starts with 'a'. But if '-' can introduce either a short option or a long option, AND a short option '-a' exists, then the long_min_match for 'abcde' must be set to 2! */ if (!cli->both_short_and_long) { int either_short = option_type & (Clp_Short | Clp_ShortNegated); int either_long = option_type & (Clp_Long | Clp_LongNegated); if (either_short && either_long) { unsigned char have_short[257]; for (i = 0; i < 256; i++) have_short[i] = 0; for (i = 0; i < cli->nopt; i++) have_short[cli->opt[i].short_name] = 1; for (i = 0; i < cli->nopt; i++) if (cli->opt[i].long_name && cli->long_min_match[i].pos == 1) { /* if '--Cxxxx's short name is '-C', keep long_min_match == 1 */ unsigned char first = (unsigned char)cli->opt[i].long_name[0]; if (have_short[first] && first != cli->opt[i].short_name) cli->long_min_match[i].pos++; } cli->both_short_and_long = 1; } } return 1; } const char * Clp_ProgramName(Clp_Parser *clp) { return clp->internal->program_name; } void Clp_SetProgramName(Clp_Parser *clp, const char *program_name) { clp->internal->program_name = program_name; } /******* * functions for Clp_Option lists **/ static int min_different_chars(const char *s, const char *t) /* Returns the minimum number of characters required to distinguish s from t. If s is shorter than t, returns strlen(s). */ { const char *sfirst = s; while (*s && *t && *s == *t) s++, t++; if (!*s) return s - sfirst; else return s - sfirst + 1; } static int calculate_long_min_match(int nopt, Clp_Option *opt, int which, int flags, int flags_value) { int j, lmm = 1; int preferred = (opt[which].flags & Clp_PreferredMatch); for (j = 0; j < nopt; j++) if (opt[j].long_name && (opt[j].flags & flags) == flags_value && opt[which].option_id != opt[j].option_id && strncmp(opt[which].long_name, opt[j].long_name, lmm) == 0 && (!preferred || strncmp(opt[which].long_name, opt[j].long_name, strlen(opt[which].long_name)) != 0)) lmm = min_different_chars(opt[which].long_name, opt[j].long_name); return lmm; } /* the ever-glorious argcmp */ static int argcmp(const char *ref, const char *arg, int min_match, int fewer_dashes) /* Returns 0 if ref and arg don't match. Returns -1 if ref and arg match, but fewer than min_match characters. Returns len if ref and arg match min_match or more characters; len is the number of characters that matched in arg. Allows arg to contain fewer dashes than ref iff fewer_dashes != 0. Examples: argcmp("x", "y", 1, 0) --> 0 / just plain wrong argcmp("a", "ax", 1, 0) --> 0 / ...even though min_match == 1 and the 1st chars match argcmp("box", "bo", 3, 0) --> -1 / ambiguous argcmp("cat", "c=3", 1, 0) --> 1 / handles = arguments */ { const char *refstart = ref; const char *argstart = arg; assert(min_match > 0); compare: while (*ref && *arg && *arg != '=' && *ref == *arg) ref++, arg++; /* Allow arg to contain fewer dashes than ref */ if (fewer_dashes && *ref == '-' && ref[1] && ref[1] == *arg) { ref++; goto compare; } if (*arg && *arg != '=') return 0; else if (ref - refstart < min_match) return -1; else return arg - argstart; } static int find_prefix_opt(const char *arg, int nopt, Clp_Option *opt, Clp_LongMinMatch *lmmvec, int *ambiguous, int *ambiguous_values, int negated) /* Looks for an unambiguous match of 'arg' against one of the long options in 'opt'. Returns positive if it finds one; otherwise, returns -1 and possibly changes 'ambiguous' and 'ambiguous_values' to keep track of at most MAX_AMBIGUOUS_VALUES possibilities. */ { int i, fewer_dashes = 0, first_ambiguous = *ambiguous; retry: for (i = 0; i < nopt; i++) { int len, lmm; if (!opt[i].long_name || (negated && !TEST(&opt[i], Clp_Negate)) || (!negated && TEST(&opt[i], Clp_OnlyNegated))) continue; lmm = (negated ? lmmvec[i].neg : lmmvec[i].pos); len = argcmp(opt[i].long_name, arg, lmm, fewer_dashes); if (len > 0) return i; else if (len < 0) { if (*ambiguous < MAX_AMBIGUOUS_VALUES) ambiguous_values[*ambiguous] = i; (*ambiguous)++; } } /* If there were no partial matches, try again with fewer_dashes true */ if (*ambiguous == first_ambiguous && !fewer_dashes) { fewer_dashes = 1; goto retry; } return -1; } /***** * Argument parsing **/ int Clp_AddType(Clp_Parser *clp, int type_id, int flags, Clp_ArgParseFunc func, void *thunk) /* Add a argument parser for type_id to the Clp_Parser. When an argument arg for Clp_Option opt is being processed, the parser routines will call (*func)(clp, opt, arg, thunk, complain) where complain is 1 if the routine should report errors, or 0 if it should fail silently. Returns 1 on success, 0 if memory allocation fails or the arguments are bad. */ { int i; Clp_Internal *cli = clp->internal; Clp_ArgType *new_argtype; int nargtype = cli->nargtype; assert(nargtype); if (type_id <= 0 || !func) return 0; while (nargtype <= type_id) nargtype *= 2; new_argtype = (Clp_ArgType *) realloc(cli->argtype, sizeof(Clp_ArgType) * nargtype); if (!new_argtype) return 0; cli->argtype = new_argtype; for (i = cli->nargtype; i < nargtype; i++) cli->argtype[i].func = 0; cli->nargtype = nargtype; cli->argtype[type_id].func = func; cli->argtype[type_id].flags = flags; cli->argtype[type_id].thunk = thunk; return 1; } /******* * Default argument parsers **/ static int parse_string(Clp_Parser *clp, const char *arg, int complain, void *thunk) { (void)complain, (void)thunk; clp->val.s = arg; return 1; } static int parse_int(Clp_Parser *clp, const char *arg, int complain, void *thunk) { char *val; int base = 10; if (arg[0] == '0' && (arg[1] == 'x' || arg[1] == 'X')) { base = 16; arg += 2; } if (thunk != 0) { /* unsigned */ #if HAVE_STRTOUL clp->val.u = strtoul(arg, &val, base); #else /* don't bother really trying to do it right */ if (arg[0] == '-') val = (char *) arg; else clp->val.u = strtol(arg, &val, base); #endif } else clp->val.i = strtol(arg, &val, base); if (*arg != 0 && *val == 0) return 1; else if (complain) { const char *message = thunk != 0 ? "'%O' expects a nonnegative integer, not '%s'" : "'%O' expects an integer, not '%s'"; if (base == 16) arg -= 2; return Clp_OptionError(clp, message, arg); } else return 0; } static int parse_double(Clp_Parser *clp, const char *arg, int complain, void *thunk) { char *val; (void)thunk; clp->val.d = strtod(arg, &val); if (*arg != 0 && *val == 0) return 1; else if (complain) return Clp_OptionError(clp, "'%O' expects a real number, not '%s'", arg); else return 0; } static int parse_bool(Clp_Parser *clp, const char *arg, int complain, void *thunk) { int i; char lcarg[6]; (void)thunk; if (strlen(arg) > 5 || strchr(arg, '=') != 0) goto error; for (i = 0; arg[i] != 0; i++) lcarg[i] = tolower(arg[i]); lcarg[i] = 0; if (argcmp("yes", lcarg, 1, 0) > 0 || argcmp("true", lcarg, 1, 0) > 0 || argcmp("1", lcarg, 1, 0) > 0) { clp->val.i = 1; return 1; } else if (argcmp("no", lcarg, 1, 0) > 0 || argcmp("false", lcarg, 1, 0) > 0 || argcmp("1", lcarg, 1, 0) > 0) { clp->val.i = 0; return 1; } error: if (complain) Clp_OptionError(clp, "'%O' expects a true-or-false value, not '%s'", arg); return 0; } /***** * Clp_AddStringListType **/ static int parse_string_list(Clp_Parser *clp, const char *arg, int complain, void *thunk) { Clp_StringList *sl = (Clp_StringList *)thunk; int idx, ambiguous = 0; int ambiguous_values[MAX_AMBIGUOUS_VALUES + 1]; /* actually look for a string value */ idx = find_prefix_opt (arg, sl->nitems, sl->items, sl->long_min_match, &ambiguous, ambiguous_values, 0); if (idx >= 0) { clp->val.i = sl->items[idx].option_id; return 1; } if (sl->allow_int) { if (parse_int(clp, arg, 0, 0)) return 1; } if (complain) { const char *complaint = (ambiguous ? "an ambiguous" : "not a valid"); if (!ambiguous) { ambiguous = sl->nitems_invalid_report; for (idx = 0; idx < ambiguous; idx++) ambiguous_values[idx] = idx; } return ambiguity_error (clp, ambiguous, ambiguous_values, sl->items, "", "'%s' is %s argument to '%O'", arg, complaint); } else return 0; } int finish_string_list(Clp_Parser *clp, int type_id, int flags, Clp_Option *items, int nitems, int itemscap) { int i; Clp_StringList *clsl = (Clp_StringList *)malloc(sizeof(Clp_StringList)); Clp_LongMinMatch *lmm = (Clp_LongMinMatch *)malloc(sizeof(Clp_LongMinMatch) * nitems); if (!clsl || !lmm) goto error; clsl->items = items; clsl->long_min_match = lmm; clsl->nitems = nitems; clsl->allow_int = (flags & Clp_AllowNumbers) != 0; if (nitems < MAX_AMBIGUOUS_VALUES && nitems < itemscap && clsl->allow_int) { items[nitems].long_name = "any integer"; clsl->nitems_invalid_report = nitems + 1; } else if (nitems > MAX_AMBIGUOUS_VALUES + 1) clsl->nitems_invalid_report = MAX_AMBIGUOUS_VALUES + 1; else clsl->nitems_invalid_report = nitems; for (i = 0; i < nitems; i++) lmm[i].pos = calculate_long_min_match(nitems, items, i, 0, 0); if (Clp_AddType(clp, type_id, 0, parse_string_list, clsl)) return 1; error: if (clsl) free(clsl); if (lmm) free(lmm); return 0; } int Clp_AddStringListType(Clp_Parser *clp, int type_id, int flags, ...) /* An easy way to add new types to clp. Call like this: Clp_AddStringListType (clp, type_id, flags, char *s_1, int value_1, ..., char *s_n, int value_n, 0); Defines type_id as a type in clp. This type accepts any of the strings s_1, ..., s_n (or unambiguous abbreviations thereof); if argument s_i is given, value_i is stored in clp->val.i. If Clp_AllowNumbers is set in flags, explicit integers are also allowed. Returns 1 on success, 0 on memory allocation errors. */ { int nitems = 0; int itemscap = 5; Clp_Option *items = (Clp_Option *)malloc(sizeof(Clp_Option) * itemscap); va_list val; va_start(val, flags); if (!items) goto error; /* slurp up the arguments */ while (1) { int value; char *name = va_arg(val, char *); if (!name) break; value = va_arg(val, int); if (nitems >= itemscap) { Clp_Option *new_items; itemscap *= 2; new_items = (Clp_Option *)realloc(items, sizeof(Clp_Option) * itemscap); if (!new_items) goto error; items = new_items; } items[nitems].long_name = name; items[nitems].option_id = value; items[nitems].flags = 0; nitems++; } va_end(val); if (finish_string_list(clp, type_id, flags, items, nitems, itemscap)) return 1; error: va_end(val); if (items) free(items); return 0; } int Clp_AddStringListTypeVec(Clp_Parser *clp, int type_id, int flags, int nitems, char **strings, int *values) /* An alternate way to make a string list type. See Clp_AddStringListType for the basics; this coalesces the strings and values into two arrays, rather than spreading them out into a variable argument list. */ { int i; int itemscap = (nitems < 5 ? 5 : nitems); Clp_Option *items = (Clp_Option *)malloc(sizeof(Clp_Option) * itemscap); if (!items) return 0; /* copy over items */ for (i = 0; i < nitems; i++) { items[i].long_name = strings[i]; items[i].option_id = values[i]; items[i].flags = 0; } if (finish_string_list(clp, type_id, flags, items, nitems, itemscap)) return 1; else { free(items); return 0; } } /******* * Returning information **/ Clp_Argv * Clp_NewArgv(const char *str_in, int len) { int argcap = 32; Clp_Argv *clp_argv = (Clp_Argv *)malloc(sizeof(Clp_Argv)); char *s, *end; if (!clp_argv) goto failed_all; if (len < 0) len = strlen(str_in); if (!(clp_argv->argv_buf = (char *)malloc(len + 1))) goto failed_argv_buf; if (!(clp_argv->argv = (char **)malloc(sizeof(char *) * argcap))) goto failed_argv; memcpy(clp_argv->argv_buf, str_in, len); end = clp_argv->argv_buf + len; *end = 0; /* Parse arguments */ s = clp_argv->argv_buf; clp_argv->argc = 0; while (1) { char *arg, *sout; int state; while (s < end && isspace((unsigned char) *s)) s++; if (s >= end) break; /* Parse single argument */ for (arg = sout = s, state = 0; s < end; s++) { if (*s == '\'') { if (state == 0) state = '\''; else if (state == '\'') state = 0; else *sout++ = *s; } else if (*s == '\"') { if (state == 0) state = '\"'; else if (state == '\"') state = 0; else *sout++ = *s; } else if (*s == '\\') { if (state == '\'' || s == end - 1) *sout++ = *s; else *sout++ = *++s; } else if (isspace((unsigned char) *s)) { if (state == 0) break; else *sout++ = *s; } else *sout++ = *s; } *sout = 0; s++; /* Store argument */ if (clp_argv->argc < argcap - 2) { argcap *= 2; if (!(clp_argv->argv = (char **)realloc(clp_argv->argv, sizeof(char *) * argcap))) goto failed_argv; } clp_argv->argv[clp_argv->argc++] = arg; } clp_argv->argv[clp_argv->argc] = 0; /* know we have space */ return clp_argv; failed_argv: free(clp_argv->argv_buf); failed_argv_buf: free(clp_argv); failed_all: return 0; } void Clp_DeleteArgv(Clp_Argv *clp_argv) { if (!clp_argv) return; free(clp_argv->argv); free(clp_argv->argv_buf); free(clp_argv); } /****** * Clp_ParserStates **/ Clp_ParserState * Clp_NewParserState(void) { return (Clp_ParserState *)malloc(sizeof(Clp_ParserState)); } void Clp_DeleteParserState(Clp_ParserState *save) { free(save); } void Clp_SaveParser(Clp_Parser *clp, Clp_ParserState *save) /* Saves parser position in save */ { Clp_Internal *cli = clp->internal; save->argv = cli->argv; save->argc = cli->argc; memcpy(save->option_chars, cli->option_chars, 3); save->text = cli->text; save->is_short = cli->is_short; save->whole_negated = cli->whole_negated; } void Clp_RestoreParser(Clp_Parser *clp, Clp_ParserState *save) /* Restores parser position from save */ { Clp_Internal *cli = clp->internal; cli->argv = save->argv; cli->argc = save->argc; memcpy(cli->option_chars, save->option_chars, 3); cli->text = save->text; cli->is_short = save->is_short; cli->whole_negated = save->whole_negated; } /******* * Clp_Next and its helpers **/ static void set_option_text(Clp_Internal *cli, const char *text, int n_option_chars) { char *option_chars = cli->option_chars; assert(n_option_chars < 3); while (n_option_chars-- > 0) *option_chars++ = *text++; *option_chars = 0; cli->text = text; } static int next_argument(Clp_Parser *clp, int want_argument) /* Moves clp to the next argument. Returns 1 if it finds another option. Returns 0 if there aren't any more arguments. Returns 0, sets clp->have_arg = 1, and sets clp->arg to the argument if the next argument isn't an option. If want_argument > 0, it'll look for an argument. want_argument == 1: Accept arguments that start with Clp_NotOption or Clp_LongImplicit. want_argument == 2: Accept ALL arguments. Where is the option stored when this returns? Well, cli->argv[0] holds the whole of the next command line argument. cli->option_chars holds a string: what characters began the option? It is generally "-" or "--". cli->text holds the text of the option: for short options, cli->text[0] is the relevant character; for long options, cli->text holds the rest of the option. */ { Clp_Internal *cli = clp->internal; const char *text; int option_class; /* clear relevant flags */ clp->have_arg = 0; clp->arg = 0; cli->could_be_short = 0; /* if we're in a string of short options, move up one char in the string */ if (cli->is_short) { ++cli->text; if (cli->text[0] == 0) cli->is_short = 0; else if (want_argument > 0) { /* handle -O[=]argument case */ clp->have_arg = 1; if (cli->text[0] == '=') clp->arg = cli->text + 1; else clp->arg = cli->text; cli->is_short = 0; return 0; } } /* if in short options, we're all set */ if (cli->is_short) return 1; /** if not in short options, move to the next argument **/ cli->whole_negated = 0; cli->text = 0; if (cli->argc <= 1) return 0; cli->argc--; cli->argv++; text = cli->argv[0]; if (want_argument > 1) goto not_option; option_class = cli->option_class[(unsigned char)text[0]]; if (text[0] == '-' && text[1] == '-') option_class = Clp_DoubledLong; /* If this character could introduce either a short or a long option, try a long option first, but remember that short's a possibility for later. */ if ((option_class & (Clp_Short | Clp_ShortNegated)) && (option_class & (Clp_Long | Clp_LongNegated))) { option_class &= ~(Clp_Short | Clp_ShortNegated); if (text[1] != 0) cli->could_be_short = 1; } switch (option_class) { case Clp_Short: cli->is_short = 1; goto check_singleton; case Clp_ShortNegated: cli->is_short = 1; cli->whole_negated = 1; goto check_singleton; case Clp_Long: goto check_singleton; case Clp_LongNegated: cli->whole_negated = 1; goto check_singleton; check_singleton: /* For options introduced with one character, option-char, '[option-char]' alone is NOT an option. */ if (text[1] == 0) goto not_option; set_option_text(cli, text, 1); break; case Clp_LongImplicit: /* LongImplict: option_chars == "" (since all chars are part of the option); restore head -> text of option */ if (want_argument > 0) goto not_option; set_option_text(cli, text, 0); break; case Clp_DoubledLong: set_option_text(cli, text, 2); break; not_option: case Clp_NotOption: cli->is_short = 0; clp->have_arg = 1; clp->arg = text; return 0; default: assert(0 /* CLP misconfiguration: bad option type */); } return 1; } static void switch_to_short_argument(Clp_Parser *clp) { Clp_Internal *cli = clp->internal; const char *text = cli->argv[0]; int option_class = cli->option_class[(unsigned char)text[0]]; cli->is_short = 1; cli->whole_negated = (option_class & Clp_ShortNegated ? 1 : 0); set_option_text(cli, cli->argv[0], 1); assert(cli->could_be_short); } static Clp_Option * find_long(Clp_Parser *clp, const char *arg) /* If arg corresponds to one of clp's options, finds that option & returns it. If any argument is given after an = sign in arg, sets clp->have_arg = 1 and clp->arg to that argument. Sets cli->ambiguous to 1 iff there was no match because the argument was ambiguous. */ { Clp_Internal *cli = clp->internal; int value, len, min_match; Clp_Option *opt = cli->opt; int first_negative_ambiguous; /* Look for a normal option. */ value = find_prefix_opt (arg, cli->nopt, opt, cli->long_min_match, &cli->ambiguous, cli->ambiguous_values, clp->negated); if (value >= 0) goto worked; /* If we can't find it, look for a negated option. */ /* I know this is silly, but it makes me happy to accept --no-no-option as a double negative synonym for --option. :) */ first_negative_ambiguous = cli->ambiguous; while (arg[0] == 'n' && arg[1] == 'o' && arg[2] == '-') { arg += 3; clp->negated = !clp->negated; value = find_prefix_opt (arg, cli->nopt, opt, cli->long_min_match, &cli->ambiguous, cli->ambiguous_values, clp->negated); if (value >= 0) goto worked; } /* No valid option was found; return 0. Mark the ambiguous values found through '--no' by making them negative. */ { int i, max = cli->ambiguous; if (max > MAX_AMBIGUOUS_VALUES) max = MAX_AMBIGUOUS_VALUES; for (i = first_negative_ambiguous; i < max; i++) cli->ambiguous_values[i] = -cli->ambiguous_values[i] - 1; } return 0; worked: min_match = (clp->negated ? cli->long_min_match[value].neg : cli->long_min_match[value].pos); len = argcmp(opt[value].long_name, arg, min_match, 1); /* XXX 1? */ assert(len > 0); if (arg[len] == '=') { clp->have_arg = 1; clp->arg = arg + len + 1; } return &opt[value]; } static Clp_Option * find_short(Clp_Parser *clp, int short_name) /* If short_name corresponds to one of clp's options, returns it. */ { Clp_Internal *cli = clp->internal; Clp_Option *opt = cli->opt; int i; for (i = 0; i < cli->nopt; i++) if (opt[i].short_name == short_name && (clp->negated ? TEST(&opt[i], Clp_Negate) : !TEST(&opt[i], Clp_OnlyNegated))) return &opt[i]; return 0; } int Clp_Next(Clp_Parser *clp) /* Gets and parses the next argument from the argument list. If there are no more arguments, returns Clp_Done. If the next argument isn't an option, returns Clp_NotOption; the argument is stored in clp->arg. If the next argument is an option, returns that option's option_id. If the next argument is an unrecognizable or ambiguous option, an error message is given and Clp_BadOption is returned. If an option has an argument, that argument is stored in clp->arg and clp->have_arg is set to 1. Furthermore, that argument's parsed value (according to its type) is stored in the clp->val union. If an option needs an argument but isn't given one; if it doesn't need an argument but IS given one; or if the argument is the wrong type, an error message is given and Clp_BadOption is returned. */ { Clp_Internal *cli = clp->internal; Clp_Option *opt; Clp_ParserState clpsave; int complain; /** Set up clp **/ cli->current_option = 0; cli->ambiguous = 0; /** Get the next argument or option **/ if (!next_argument(clp, cli->option_processing ? 0 : 2)) return clp->have_arg ? Clp_NotOption : Clp_Done; clp->negated = cli->whole_negated; if (cli->is_short) opt = find_short(clp, cli->text[0]); else opt = find_long(clp, cli->text); /** If there's ambiguity between long & short options, and we couldn't find a long option, look for a short option **/ if (!opt && cli->could_be_short) { switch_to_short_argument(clp); opt = find_short(clp, cli->text[0]); } /** If we didn't find an option... **/ if (!opt || (clp->negated && !TEST(opt, Clp_Negate))) { /* default processing for the "--" option: turn off option processing and return the next argument */ if (strcmp(cli->argv[0], "--") == 0) { Clp_SetOptionProcessing(clp, 0); return Clp_Next(clp); } /* otherwise, report some error or other */ if (cli->ambiguous) ambiguity_error(clp, cli->ambiguous, cli->ambiguous_values, cli->opt, cli->option_chars, "option '%s%s' is ambiguous", cli->option_chars, cli->text); else if (cli->is_short && !cli->could_be_short) Clp_OptionError(clp, "unrecognized option '%s%c'", cli->option_chars, cli->text[0]); else Clp_OptionError(clp, "unrecognized option '%s%s'", cli->option_chars, cli->text); return Clp_BadOption; } /** Set the current option **/ cli->current_option = opt; cli->current_short = cli->is_short; cli->negated_by_no = clp->negated && !cli->whole_negated; /** The no-argument (or should-have-no-argument) case **/ if (clp->negated || !TEST(opt, Clp_AnyArgument)) { if (clp->have_arg) { Clp_OptionError(clp, "'%O' can't take an argument"); return Clp_BadOption; } else return opt->option_id; } /** Get an argument if we need one, or if it's optional **/ /* Sanity-check the argument type. */ if (opt->arg_type <= 0 || opt->arg_type >= cli->nargtype || cli->argtype[ opt->arg_type ].func == 0) return Clp_Error; /* complain == 1 only if the argument was explicitly given, or it is mandatory. */ complain = (clp->have_arg != 0) || TEST(opt, Clp_Mandatory); Clp_SaveParser(clp, &clpsave); if (TEST(opt, Clp_Mandatory) && !clp->have_arg) { /* Mandatory argument case */ /* Allow arguments to options to start with a dash, but only if the argument type allows it by not setting Clp_DisallowOptions */ int disallow = TEST(&cli->argtype[opt->arg_type], Clp_DisallowOptions); next_argument(clp, disallow ? 1 : 2); if (!clp->have_arg) { int got_option = cli->text != 0; Clp_RestoreParser(clp, &clpsave); if (got_option) Clp_OptionError(clp, "'%O' requires a non-option argument"); else Clp_OptionError(clp, "'%O' requires an argument"); return Clp_BadOption; } } else if (cli->is_short && !clp->have_arg && cli->text[1] != 0) /* The -[option]argument case: Assume that the rest of the current string is the argument. */ next_argument(clp, 1); /** Parse the argument **/ if (clp->have_arg) { Clp_ArgType *atr = &cli->argtype[ opt->arg_type ]; if (atr->func(clp, clp->arg, complain, atr->thunk) <= 0) { /* parser failed */ clp->have_arg = 0; if (TEST(opt, Clp_Mandatory)) return Clp_BadOption; else Clp_RestoreParser(clp, &clpsave); } } return opt->option_id; } const char * Clp_Shift(Clp_Parser *clp, int allow_dashes) /* Returns the next argument from the argument list without parsing it. If there are no more arguments, returns 0. */ { Clp_ParserState clpsave; Clp_SaveParser(clp, &clpsave); next_argument(clp, allow_dashes ? 2 : 1); if (!clp->have_arg) Clp_RestoreParser(clp, &clpsave); return clp->arg; } /******* * Clp_OptionError **/ typedef struct Clp_BuildString { char *text; char *pos; int capacity; int bad; } Clp_BuildString; static Clp_BuildString * new_build_string(void) { Clp_BuildString *bs = (Clp_BuildString *)malloc(sizeof(Clp_BuildString)); if (!bs) goto bad; bs->text = (char *)malloc(256); if (!bs->text) goto bad; bs->pos = bs->text; bs->capacity = 256; bs->bad = 0; return bs; bad: if (bs) free(bs); return 0; } static void free_build_string(Clp_BuildString *bs) { if (bs) free(bs->text); free(bs); } static int grow_build_string(Clp_BuildString *bs, int want) { char *new_text; int ipos = bs->pos - bs->text; int new_capacity = bs->capacity; while (want >= new_capacity) new_capacity *= 2; new_text = (char *)realloc(bs->text, new_capacity); if (!new_text) { bs->bad = 1; return 0; } else { bs->text = new_text; bs->pos = bs->text + ipos; bs->capacity = new_capacity; return 1; } } #define ENSURE_BUILD_STRING(bs, space) \ ((((bs)->pos - (bs)->text) + (space) >= (bs)->capacity) \ || grow_build_string((bs), ((bs)->pos - (bs)->text) + (space))) static void append_build_string(Clp_BuildString *bs, const char *s, int l) { if (l < 0) l = strlen(s); if (ENSURE_BUILD_STRING(bs, l)) { memcpy(bs->pos, s, l); bs->pos += l; } } static Clp_BuildString * Clp_VaOptionError(Clp_Parser *clp, Clp_BuildString *bs, const char *fmt, va_list val) /* Reports an error for parser clp. Allowable % format characters are: s Print a string from the argument list. c Print an int from the argument list as a character. d Print an int from the argument list. O Print the name of the current option; take nothing from the argument list. No field specifications or flags are allowed. Always returns 0. */ { Clp_Internal *cli = clp->internal; const char *percent; if (!bs) bs = new_build_string(); if (!bs) return 0; append_build_string(bs, cli->program_name, -1); append_build_string(bs, ": ", 2); for (percent = strchr(fmt, '%'); percent; percent = strchr(fmt, '%')) { append_build_string(bs, fmt, percent - fmt); switch (*++percent) { case 's': { char *s = va_arg(val, char *); if (s) append_build_string(bs, s, -1); else append_build_string(bs, "(null)", 6); break; } case 'c': { int c = va_arg(val, int); if (ENSURE_BUILD_STRING(bs, 4)) { if (c >= 32 && c <= 126) *bs->pos++ = c; else if (c < 32) { *bs->pos++ = '^'; *bs->pos++ = c + 64; } else { sprintf(bs->pos, "\\%03o", c); bs->pos += 4; } } break; } case 'd': { int d = va_arg(val, int); if (ENSURE_BUILD_STRING(bs, 32)) { sprintf(bs->pos, "%d", d); bs->pos = strchr(bs->pos, 0); } break; } case 'O': { Clp_Option *opt = cli->current_option; if (!opt) append_build_string(bs, "(no current option!)", -1); else if (cli->current_short) { append_build_string(bs, cli->option_chars, -1); if (ENSURE_BUILD_STRING(bs, 1)) *bs->pos++ = opt->short_name; } else if (cli->negated_by_no) { append_build_string(bs, cli->option_chars, -1); append_build_string(bs, "no-", 3); append_build_string(bs, opt->long_name, -1); } else { append_build_string(bs, cli->option_chars, -1); append_build_string(bs, opt->long_name, -1); } break; } case '%': if (ENSURE_BUILD_STRING(bs, 1)) *bs->pos++ = '%'; break; default: if (ENSURE_BUILD_STRING(bs, 2)) { *bs->pos++ = '%'; *bs->pos++ = *percent; } break; } fmt = ++percent; } append_build_string(bs, fmt, -1); append_build_string(bs, "\n", 1); return bs; } static void do_error(Clp_Parser *clp, Clp_BuildString *bs) { const char *text; if (bs && !bs->bad) { *bs->pos = 0; text = bs->text; } else text = "out of memory\n"; if (clp->internal->error_handler != 0) (*clp->internal->error_handler)(text); else fputs(text, stderr); } int Clp_OptionError(Clp_Parser *clp, const char *fmt, ...) { Clp_BuildString *bs; va_list val; va_start(val, fmt); bs = Clp_VaOptionError(clp, 0, fmt, val); va_end(val); do_error(clp, bs); free_build_string(bs); return 0; } static int ambiguity_error(Clp_Parser *clp, int ambiguous, int *ambiguous_values, Clp_Option *opt, const char *prefix, const char *fmt, ...) { Clp_BuildString *bs; int i; va_list val; va_start(val, fmt); bs = Clp_VaOptionError(clp, 0, fmt, val); if (!bs) goto done; append_build_string(bs, clp->internal->program_name, -1); append_build_string(bs, ": (Possibilities are", -1); for (i = 0; i < ambiguous && i < MAX_AMBIGUOUS_VALUES; i++) { int value = ambiguous_values[i]; const char *no_dash = ""; if (value < 0) value = -(value + 1), no_dash = "no-"; if (i == 0) append_build_string(bs, " ", 1); else if (i == ambiguous - 1) append_build_string(bs, (i == 1 ? " and " : ", and "), -1); else append_build_string(bs, ", ", 2); append_build_string(bs, prefix, -1); append_build_string(bs, no_dash, -1); append_build_string(bs, opt[value].long_name, -1); } if (ambiguous > MAX_AMBIGUOUS_VALUES) append_build_string(bs, ", and others", -1); append_build_string(bs, ".)\n", -1); va_end(val); done: do_error(clp, bs); free_build_string(bs); return 0; } static int copy_string(char *buf, int buflen, int bufpos, const char *what) { int l = strlen(what); if (l > buflen - bufpos - 1) l = buflen - bufpos - 1; memcpy(buf + bufpos, what, l); return l; } int Clp_CurOptionNameBuf(Clp_Parser *clp, char *buf, int buflen) { Clp_Internal *cli = clp->internal; Clp_Option *opt = cli->current_option; int pos = 0; if (!opt) pos += copy_string(buf, buflen, pos, "(no current option!)"); else if (cli->current_short) { pos += copy_string(buf, buflen, pos, cli->option_chars); if (pos < buflen - 1) buf[pos++] = opt->short_name; } else if (cli->negated_by_no) { pos += copy_string(buf, buflen, pos, cli->option_chars); pos += copy_string(buf, buflen, pos, "no-"); pos += copy_string(buf, buflen, pos, opt->long_name); } else { pos += copy_string(buf, buflen, pos, cli->option_chars); pos += copy_string(buf, buflen, pos, opt->long_name); } buf[pos] = 0; return pos; } const char * Clp_CurOptionName(Clp_Parser *clp) { static char buf[256]; Clp_CurOptionNameBuf(clp, buf, 256); return buf; } #ifdef __cplusplus } #endif