// // aegis - project change supervisor // Copyright (C) 1991-1995, 1997-1999, 2001-2006, 2008, 2012, 2013 Peter Miller // // This program 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 3 of the License, or (at // your option) any later version. // // This program 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 this program. If not, see . // #include #include #include #include #include #include #include #include #include #include #include #include static arglex_table_ty table[] = { { "-", arglex_token_stdio, }, { "-Help", arglex_token_help, }, { "-VERSion", arglex_token_version, }, { "-TRAce", arglex_token_trace, }, { "-Page_Length", arglex_token_page_length, }, { "-Page_Width", arglex_token_page_width, }, ARGLEX_END_MARKER }; typedef const char **argv_t; static int argc; static argv_t argv; arglex_value_ty arglex_value; int arglex_token; static arglex_table_ty *utable; static const char *partial; static int strings_only_mode; static int incomplete = -1; static int is_synthetic; // // NAME // arglex_init // // SYNOPSIS // void arglex_init(int ac, char **av, arglex_table-t *tp); // // DESCRIPTION // The arglex_init function is used to initialize the // command line processing. // // ARGUMENTS // ac - aergument count, from main // av - argument values, from main // tp - pointer to table of options // // CAVEAT // Must be called before the arglex() function. // void arglex_init(int ac, char **av, arglex_table_ty *tp) { progname_set(av[0]); argc = ac - 1; argv = (argv_t)(av + 1); // gcc incorrectly whines about incompatible // pointer types in assignment utable = tp; } // // NAME // is_a_number // // SYNOPSIS // int is_a_number(char *s); // // DESCRIPTION // The is_a_number function is used to determine if the // argument is a number. // // The value is placed in arglex_value.alv_number as // a side effect. // // Negative and positive signs are accepted. // The C conventions for decimal, octal and hexadecimal are understood. // // There may be no white space anywhere in the string, // and the string must end after the last digit. // Trailing garbage will be interpreted to mean it is not a string. // // ARGUMENTS // s - string to be tested and evaluated // // RETURNS // int; zero if not a number, // non-zero if is a number. // static int is_a_number(const char *s) { long n; int sign; n = 0; switch (*s) { case '-': ++s; sign = -1; break; case '+': ++s; sign = 1; break; default: sign = 1; break; } switch (*s) { case '0': if ((s[1] == 'x' || s[1] == 'X') && s[2]) { s += 2; for (;;) { switch (*s) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': n = n * 16 + *s++ - '0'; continue; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': n = n * 16 + *s++ - 'A' + 10; continue; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': n = n * 16 + *s++ - 'a' + 10; continue; } break; } } else { for (;;) { switch (*s) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': n = n * 8 + *s++ - '0'; continue; } break; } } break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': for (;;) { switch (*s) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': n = n * 10 + *s++ - '0'; continue; } break; } break; default: return 0; } if (*s) return 0; arglex_value.alv_number = n * sign; return 1; } // // NAME // arglex // // SYNOPSIS // int arglex(void); // // DESCRIPTION // The arglex function is used to perfom lexical analysis // on the command line arguments. // // Unrecognised options are returned as arglex_token_option // for anything starting with a '-', or // arglex_token_string otherwise. // // RETURNS // The next token in the token stream. // When the end is reached, arglex_token_eoln is returned forever. // // CAVEAT // Must call arglex_init befor this function is called. // int arglex(void) { arglex_table_ty *tp; int j; arglex_table_ty *hit[20]; int nhit; static const char *pushback; const char *arg; string_ty *s1; string_ty *s2; int is_inco; trace(("arglex()\n{\n")); if (pushback) { // // the second half of a "-foo=bar" style argument. // arg = pushback; is_inco = (incomplete == 0); pushback = 0; } else { get_another: if (argc <= 0) { arglex_token = arglex_token_eoln; arg = ""; goto done; } arg = argv[0]; is_inco = (incomplete == 0); argc--; argv++; incomplete--; // // The "--" option means the rest of the arguments on // the command line are only strings. // if (strings_only_mode) { arglex_token = ( is_inco ? arglex_token_string_incomplete : arglex_token_string ); goto done; } if (arg[0] == '-' && arg[1] == '-' && !arg[2]) { strings_only_mode = 1; goto get_another; } // // See if it looks like a GNU "-foo=bar" option. // Split it at the '=' to make it something the // rest of the code understands. // if (arg[0] == '-' && arg[1] != '=') { const char *eqp = strchr(arg, '='); if (eqp) { pushback = eqp + 1; if (is_inco) { incomplete = 0; is_inco = 0; } // // Crop the argument, so that we can figure out that the // left hand side means. It is often a memory leak. // arg = mem_copy_string(arg, eqp - arg); } } // // Turn the GNU-style leading "--" // into "-" if necessary. // if (arg[0] == '-' && arg[1] == '-' && arg[2] && !is_a_number(arg + 1)) ++arg; } // // see if it is a number // if (is_a_number(arg)) { arglex_token = (is_inco ? arglex_token_number_incomplete : arglex_token_number); goto done; } // // scan the tables to see what it matches // nhit = 0; partial = 0; if (!is_inco) { for (tp = table; tp->t_name; tp++) { if (arglex_compare(tp->t_name, arg, &partial)) hit[nhit++] = tp; } if (utable) { for (tp = utable; tp->t_name; tp++) { if (arglex_compare(tp->t_name, arg, &partial)) hit[nhit++] = tp; } } } // // deal with unknown or ambiguous options // switch (nhit) { case 0: // // not found in the tables // if (*arg == '-') { arglex_token = ( is_inco ? arglex_token_option_incomplete : arglex_token_option ); } else { arglex_token = ( is_inco ? arglex_token_string_incomplete : arglex_token_string ); } break; case 1: one: arglex_token = hit[0]->t_token; if (partial) arg = partial; // const-ness hack else arg = hit[0]->t_name; break; default: // // not an error if they are all the same // e.g. due to cultural spelling differences // with the same abbreviation. // for (j = 1; j < nhit; ++j) if (hit[0]->t_token != hit[j]->t_token) break; if (j >= nhit) goto one; if (is_synthetic) { arglex_token = arglex_token_option; goto done; } // // build a list of the names // and complain that it is ambiguous // s1 = str_from_c(hit[0]->t_name); for (j = 1; j < nhit; ++j) { s2 = str_format("%s, %s", s1->str_text, hit[j]->t_name); str_free(s1); s1 = s2; } fatal_raw("option \"%s\" ambiguous (%s)", arg, s1->str_text); } // // here for all exits // done: arglex_value.alv_string = arg; trace(("return %d; /* %s */\n", arglex_token, arglex_value.alv_string)); trace(("}\n")); return arglex_token; } // // NAME // arglex_prefetch // // SYNOPSIS // int arglex_prefetch(int *list, int list_len); // // DESCRIPTION // The arglex_prefetch function is used to perfom lexical analysis // on the command line arguments, much like the arglex function. // However, it is given a list of tokens to look for on the command // line, and such arguments are matched and extracted, which may // be used to relax command line argument ordering restrictions. // // RETURNS // One of the tokens in the list, or // ARGLEX_PREFETCH_FAIL if none of the are available. // // CAVEAT // Must call arglex_init before this function is called. // int arglex_prefetch(int *list, int list_len) { int j; trace(("arglex_prefetch()\n{\n")); if (strings_only_mode) goto fail; for (j = 0; j < argc; ++j) { const char *actual; int k; if (j == incomplete) continue; // // The "--" option means the rest of the arguments on // the command line are only strings. // actual = argv[j]; if (actual[0] == '-' && actual[1] == '-' && !actual[2]) goto fail; // // see if it is a number // if (is_a_number(actual)) continue; // // Turn the GNU-style leading "--" // into "-" if necessary. // if ( actual[0] == '-' && actual[1] == '-' && actual[2] && !is_a_number(actual + 1) ) ++actual; for (k = 0; k < list_len; ++k) { int token; const char *formal; token = list[k]; formal = arglex_token_name(token); if (arglex_compare(formal, actual, &partial)) { int m; // // Shuffle everything down to fill in // the hole. // for (m = j + 1; m < argc; ++m) argv[m - 1] = argv[m]; --argc; if (j < incomplete) --incomplete; // // Fill in the answer as it would be // returned form arglex() // arglex_value.alv_string = actual; arglex_token = token; trace(("return %d; /* %s */\n", arglex_token, arglex_value.alv_string)); trace(("}\n")); return arglex_token; } } } fail: trace(("return FAIL;\n")); trace(("}\n")); return ARGLEX_PREFETCH_FAIL; } const char * arglex_token_name(int n) { arglex_table_ty *tp; switch (n) { case arglex_token_eoln: return "end of command line"; case arglex_token_number: return "number"; case arglex_token_option: return "option"; case arglex_token_stdio: return "standard input or output"; case arglex_token_string: return "string"; default: break; } for (tp = table; tp->t_name; tp++) { if (tp->t_token == n) return tp->t_name; } if (utable) { for (tp = utable; tp->t_name; tp++) { if (tp->t_token == n) return tp->t_name; } } assert(!"unknown command line token"); return "unknown command line token"; } arglex_table_ty * arglex_table_catenate(arglex_table_ty *tp1, arglex_table_ty *tp2) { size_t len1; size_t len2; size_t len; arglex_table_ty *tp; static arglex_table_ty zero = ARGLEX_END_MARKER; for (len1 = 0; tp1[len1].t_name; ++len1) ; for (len2 = 0; tp2[len2].t_name; ++len2) ; len = len1 + len2; tp = (arglex_table_ty *)mem_alloc((len + 1) * sizeof(arglex_table_ty)); memcpy(tp, tp1, len1 * sizeof(arglex_table_ty)); memcpy(tp + len1, tp2, len2 * sizeof(arglex_table_ty)); tp[len] = zero; return tp; } void arglex_dispatch(const arglex_dispatch_ty *choices, unsigned choices_size, void (*otherwise)(void)) { trace(("arglex_dispatch()\n{\n")); int *tmp = (int *)mem_alloc(choices_size * sizeof(tmp[0])); for (int priority = 0;; ++priority) { unsigned tmp_len = 0; for (unsigned j = 0; j < choices_size; ++j) { const arglex_dispatch_ty *cp = choices + j; if (cp->priority == priority) tmp[tmp_len++] = cp->token; } if (tmp_len == 0) break; int tok = arglex_prefetch(tmp, tmp_len); for (unsigned j = 0; j < choices_size; ++j) { const arglex_dispatch_ty *cp = choices + j; if (tok == cp->token) { mem_free(tmp); if (cp->func) cp->func(); trace(("}\n")); return; } } } mem_free(tmp); if (otherwise) otherwise(); trace(("}\n")); } void arglex_synthetic(int ac, char **av, int inco) { argc = ac - 1; argv = (argv_t)(av + 1); // gcc incorrectly whines about incompatible // pointer types in assignment is_synthetic = 1; incomplete = inco - 1; } void arglex_retable(arglex_table_ty *tp) { utable = tp; } int arglex_get_string(void) { int result; int hold; hold = strings_only_mode; strings_only_mode = 1; result = arglex(); strings_only_mode = hold; return result; } nstring arglex_abbreviate(const char *s) { nstring_accumulator result; for (;;) { unsigned char c = *s++; switch (c) { case '\0': return result.mkstr(); case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '_': break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': c = tolower(c); result.push_back(c); break; default: result.push_back(c); break; } } } void arglex_test_ambiguous(void) { arglex_table_ty *tbl = (utable ? arglex_table_catenate(table, utable) : table); int number_of_errors = 0; for (const arglex_table_ty *tp1 = tbl; tp1->t_name; ++tp1) { nstring abbrev = arglex_abbreviate(tp1->t_name); for (const arglex_table_ty *tp2 = tbl; tp2->t_name; ++tp2) { if ( tp1->t_token != tp2->t_token && arglex_compare(tp2->t_name, abbrev.c_str(), 0) ) { error_raw ( "option %s is ambiguous (%s vs %s)", abbrev.c_str(), tp1->t_name, tp2->t_name ); ++number_of_errors; } } } if (number_of_errors > 0) { fatal_raw ( "found %d fatal error%s", number_of_errors, (number_of_errors == 1 ? "" : "s") ); } if (tbl != table) mem_free(tbl); } // vim: set ts=8 sw=4 et :