00001 /* 00002 File: ArgParse.cc 00003 00004 Function: Argument parsing package 00005 00006 Author(s): Paul Heckbert 00007 Andrew Willmott: converted to C++, 00008 modifications to fit in with CL. 00009 00010 Notes: 00011 */ 00012 00013 /* 00014 * arg_parse: Command line argument parser. 00015 * 00016 * notable features: 00017 * arbitrary order of flag arguments 00018 * automatic argument conversion and type checking 00019 * multiple-character flag names 00020 * required, optional, and flag arguments 00021 * automatic usage message 00022 * subroutine call for exotic options (variable number of parameters) 00023 * modularized parsers encourage standardized options 00024 * expression evaluation 00025 * works either from argv or in interactive mode, 00026 * as a primitive language parser and interpreter 00027 * concise specification 00028 * easy to use 00029 * 00030 * Paul Heckbert ph@cs.cmu.edu 00031 * 00032 * 19 April 1988 - written at UC Berkeley 00033 * converted to ANSI C, ajw 1999 00034 * 00035 * simpler version written at Pacific Data Images, Aug 1985. 00036 * Ideas borrowed from Ned Greene's ARGS at New York Inst. of Tech. 00037 * and Alvy Ray Smith's AARG at Pixar. 00038 */ 00039 00040 #include "cl/ArgParse.h" 00041 #include "cl/Expr.h" 00042 00043 #include <stdarg.h> 00044 #include <ctype.h> 00045 #include <string.h> 00046 #include <stdlib.h> 00047 00048 00049 inline bool str_eq(const char *a, const char *b) 00050 { 00051 return (strcmp(a, b) == 0); 00052 }; 00053 00054 00055 #define CHECKTYPE(form, keyword) \ 00056 if (form->type!=0) \ 00057 { \ 00058 fprintf(stderr, "arg: %s doesn't belong in %s paramlist\n", \ 00059 keyword, form->format); \ 00060 return 0; \ 00061 } \ 00062 else 00063 00064 /* recognize a valid numeric constant or expression by its first char: */ 00065 #define NUMERIC(s) (isdigit(*(s)) || \ 00066 *(s)=='.' || *(s)=='-' || *(s)=='+' || *(s)=='(') 00067 00068 ArgForm *arg_to_form1(va_list ap); 00069 ArgForm *arg_find_flag(char *arg, ArgForm *form); 00070 ArgForm *arg_find_reg(); 00071 00072 int arg_debug = 0; /* debugging level; 0=none */ 00073 int arg_doccol = 24; /* column at which to print doc string*/ 00074 int arg_warning = 1; /* print warnings about repeated flags? */ 00075 00076 static ArgForm *regf; /* advancing form ptr used by arg_find_reg */ 00077 00078 va_list arg_doc_parse(ArgForm *f, va_list ap); 00079 int arg_format(ArgForm *f); 00080 void arg_init(ArgForm *form); 00081 int arg_done(); 00082 int arg_parse_form1(int ac, char **av, ArgForm *form); 00083 int arg_do(int ac, char **av, ArgForm *f); 00084 void av_print(char *str, int ac, char **av); 00085 00086 typedef enum {SPACE, TOKEN, END} Token_type; 00087 00088 static void space(FILE *fp, int c, int c1); 00089 static int checkstr(char *s, char *name, char *prev); 00090 static Token_type token_char(FILE *fp, char *p); 00091 static int scan(int narg, char **arg, ArgForm *f); 00092 static int nargs(int ac, char **av, ArgForm *f, int *skip); 00093 00094 /* 00095 * arg_parse(ac, av, varargs_list) 00096 * Parse the arguments in av according to the varargs list, which contains 00097 * format strings, parameter and subroutine ptrs, and other stuff. 00098 * The varargs list must be terminated by a 0. 00099 * Returns an error code. 00100 */ 00101 00102 int arg_parse(int ac, char **av, ...) 00103 { 00104 int ret; 00105 va_list ap; 00106 ArgForm *form; 00107 00108 va_start(ap, av); 00109 if (ac < 1 || ac > ARG_NARGMAX) 00110 { 00111 fprintf(stderr, 00112 "arg_parse: first arg to arg_parse (%d) doesn't look like argc\n", 00113 ac); 00114 return ARG_BADCALL; 00115 } 00116 00117 /* convert varargs to formlist */ 00118 form = arg_to_form1(ap); 00119 if (!form) 00120 return ARG_BADCALL; 00121 00122 /* parse args according to form */ 00123 if (ac == 2 && str_eq(av[1], "-stdin")) /* no args, run interactive version */ 00124 ret = arg_parse_stream(stdin, form); 00125 else /* args supplied, parse av */ 00126 ret = arg_parse_argv(ac, av, form); 00127 return ret; 00128 } 00129 00130 /*----------------------------------------------------------------------*/ 00131 00132 /* 00133 * arg_to_form: convert varargs to formlist 00134 * not called by arg_parse, but sometimes called from outside to build sublists 00135 */ 00136 00137 ArgForm *arg_to_form(int dummy, ...) 00138 { 00139 va_list ap; 00140 00141 va_start(ap, dummy); 00142 return arg_to_form1(ap); 00143 } 00144 00145 /* 00146 * arg_to_form1: convert varargs to formlist recursively. 00147 * assumes va_start has already been called 00148 * calls va_end when done to clean up 00149 * returns 0 on error. 00150 */ 00151 00152 ArgForm *arg_to_form1(va_list ap) 00153 { 00154 char *s, *prevs; 00155 int pi, t; 00156 ArgForm *form, *prevform, *rootform; 00157 00158 /* 00159 * varargs syntax is: 00160 * formatstr [KEYWORD val] paramptr* docstr docargptr* 00161 * where there are as many paramptrs as %'s in the format string 00162 * and as many docargptrs as %'s in the doc string 00163 */ 00164 rootform = 0; 00165 prevs = ""; 00166 for (prevform = 0; (s = va_arg(ap, char *)) != 0; prevform = form) 00167 { 00168 00169 /* first, read the format string */ 00170 if (checkstr(s, "format string", prevs)) return 0; 00171 form = new ArgForm; 00172 form->next = 0; 00173 form->format = s; 00174 form->flag = 0; 00175 form->type = 0; 00176 form->param = 0; 00177 form->parammask = 0; 00178 form->subr = 0; 00179 form->sublist = 0; 00180 if (prevform) prevform->next = form; 00181 else rootform = form; 00182 00183 /* parse format to create flag and code strings, compute #params */ 00184 t = arg_format(form); 00185 if (t) return 0; 00186 00187 /* next, read the parameters and keywords */ 00188 pi = 0; 00189 if (form->nparam > 0) 00190 { 00191 form->type = form->flag[0] == '-' ? ARG_PARAMFLAG : ARG_REGULAR; 00192 form->param = new (int*)[form->nparam]; 00193 } 00194 for (; (s = va_arg(ap, char *)) != 0; ) 00195 { 00196 /* note that we continue (not break) in all cases except one */ 00197 switch ((int)s) 00198 { 00199 case ARG_FLAGNEXT: /* ptr to flag vbl */ 00200 CHECKTYPE(form, "FLAG"); 00201 form->type = ARG_SIMPFLAG; 00202 form->param = new (int *); 00203 *form->param = va_arg(ap, int *); 00204 continue; 00205 case ARG_SUBRNEXT: /* ptr to action subr */ 00206 CHECKTYPE(form, "SUBR"); 00207 form->type = ARG_SUBRFLAG; 00208 form->subr = (int (*)(int argc, char * argv[]))va_arg(ap, int *); 00209 /* append dots to end of format string */ 00210 s = new char[strlen(form->format) + 5]; 00211 sprintf(s, "%s ...", form->format); 00212 form->format = s; 00213 continue; 00214 case ARG_LISTNEXT: /* ptr to sub-formlist */ 00215 CHECKTYPE(form, "SUBLIST"); 00216 form->type = ARG_SUBLISTFLAG; 00217 form->sublist = va_arg(ap, ArgForm *); 00218 continue; 00219 default: /* ptr to param */ 00220 if (pi >= form->nparam) break; 00221 form->param[pi++] = (int *)s; 00222 continue; 00223 } 00224 break; /* end of params/keywords */ 00225 } 00226 00227 if (!form->flag[0] && form->type == ARG_SUBLISTFLAG) 00228 { 00229 fprintf(stderr, "arg: sublist must be given a flag name\n"); 00230 return 0; 00231 } 00232 if (!form->type) /* just a doc string */ 00233 form->type = ARG_NOP; 00234 /* finally, read the doc string */ 00235 if (checkstr(s, "doc string", form->format)) return 0; 00236 form->doc = prevs = s; 00237 00238 /* skip over doc args */ 00239 ap = arg_doc_parse(form, ap); 00240 } 00241 va_end(ap); 00242 return rootform; 00243 } 00244 00245 /* 00246 * arg_format: parse the format string to create flag string, 00247 * code string, parammask, and count the number of params. 00248 * e.g.: format="-size %d %F" => flag="-size", code="dF", nparam=2 00249 */ 00250 00251 int arg_format(ArgForm *f) 00252 { 00253 char *s, *c; 00254 int n, np; 00255 00256 if (f->format[0] == '-') 00257 { /* flag string present */ 00258 /* find the end of the flag string, put flag string in f->flag */ 00259 for (s = &f->format[1]; *s && *s != ' ' && *s != '%' && *s != '['; s++); 00260 n = s - f->format; 00261 f->flag = new char[n + 1]; 00262 bcopy(f->format, f->flag, n); 00263 f->flag[n] = 0; 00264 } 00265 else 00266 { 00267 s = f->format; /* no flag string: probably a reg arg */ 00268 f->flag = ""; /* or maybe a flagless subrflag */ 00269 } 00270 00271 /* extract scanf codes from remainder of format string, put in f->code */ 00272 n = (f->format + strlen(f->format) - s) / 2; /* overestimate # of % codes */ 00273 f->code = new char[n + 1]; 00274 for (c = f->code, np = 0; ; np++, s++) 00275 { 00276 for (; *s == ' ' || *s == '['; s++) 00277 if (*s == '[') f->parammask |= 1 << np; 00278 if (!*s || *s == ']') break; 00279 if (*s != '%' || !s[1]) 00280 { 00281 fprintf(stderr, "arg: bad format string (%s)\n", f->format); 00282 return ARG_BADCALL; 00283 } 00284 *c++ = *++s; 00285 } 00286 for (; *s; s++) 00287 if (*s != ' ' && *s != ']') 00288 { 00289 fprintf(stderr, "bad format (%s), nothing allowed after ']'s\n", 00290 f->format); 00291 return ARG_BADCALL; 00292 } 00293 f->parammask |= 1 << np; 00294 if (np >= 8*sizeof(int)) 00295 { 00296 fprintf(stderr, "out of bits in parammask! too many params to %s\n", 00297 f->flag); 00298 return ARG_BADCALL; 00299 } 00300 00301 /* number of parameters to flag = number of '%'s in format string */ 00302 f->nparam = np; 00303 *c = 0; 00304 if (c - f->code != f->nparam) fprintf(stderr, "OUCH!\n"); 00305 return 0; 00306 } 00307 00308 /* 00309 * arg_doc_parse: Find the '%' format codes in f->doc and increment varargs 00310 * ptr ap over the doc string parameters. Updates f->doc to be the formatted 00311 * documentation string and returns the new ap. 00312 */ 00313 00314 va_list arg_doc_parse(ArgForm *f, va_list ap) 00315 { 00316 char *s, buf[256]; 00317 int size, gotparam; 00318 va_list ap0; 00319 00320 ap0 = ap; 00321 gotparam = 0; 00322 for (s = f->doc; *s; s++) 00323 { 00324 for (; *s; s++) /* search for next format code */ 00325 if (s[0] == '%') 00326 { 00327 if (s[1] == '%') 00328 s++; /* skip over %% */ 00329 else 00330 break; 00331 } 00332 if (!*s) break; 00333 /* skip over numerical parameters */ 00334 for (s++; *s && *s == '-' || *s > '0' && *s <= '9' || *s == '.'; s++); 00335 /* now *s points to format code */ 00336 switch (*s) 00337 { 00338 case 'h': 00339 size = 0; 00340 s++; 00341 break; /* half */ 00342 case 'l': 00343 size = 2; 00344 s++; 00345 break; /* long */ 00346 default : 00347 size = 1; 00348 break; /* normal size */ 00349 } 00350 if (!*s) 00351 { 00352 fprintf(stderr, "arg: premature end of string in (%s)\n", f->doc); 00353 break; 00354 } 00355 gotparam = 1; 00356 /* 00357 * simulate printf's knowledge of type sizes 00358 * (it's too bad we have to do this) 00359 */ 00360 switch (*s) 00361 { 00362 case 'd': 00363 case 'D': 00364 case 'o': 00365 case 'O': 00366 case 'x': 00367 case 'X': 00368 case 'c': 00369 if (size == 2 || *s >= 'A' && *s <= 'Z') va_arg(ap, long); 00370 else va_arg(ap, int); 00371 break; 00372 case 'e': 00373 case 'f': 00374 case 'g': 00375 /* note: float args are converted to doubles by MOST compilers*/ 00376 va_arg(ap, double); 00377 break; 00378 case 's': 00379 va_arg(ap, char *); 00380 break; 00381 default: 00382 fprintf(stderr, "arg: unknown format code %%%c in %s\n", 00383 *s, f->doc); 00384 va_arg(ap, int); 00385 break; 00386 } 00387 } 00388 if (gotparam) 00389 { /* there are doc parameters, format a new doc string */ 00390 vsprintf(buf, f->doc, ap0); 00391 f->doc = new char[sizeof(buf) + 1]; 00392 strcpy(f->doc, buf); 00393 } 00394 00395 return ap; /* varargs ptr past end of doc params */ 00396 } 00397 00398 /*----------------------------------------------------------------------*/ 00399 00400 #define LINEMAX 256 00401 #define ACMAX 128 00402 00403 /* 00404 * arg_parse_stream: parse from an input stream (not from an arg vector) 00405 * parse args in stream fp, line by line, according to formlist in form 00406 * Returns 0 on success, negative on failure. 00407 */ 00408 00409 int arg_parse_stream(FILE *fp, ArgForm *form) 00410 { 00411 char c, *av[ACMAX], line[LINEMAX], *p; 00412 Token_type type; 00413 int i, ac, ret, err; 00414 ArgForm *oldregf; 00415 00416 oldregf = regf; 00417 regf = form; 00418 arg_init(form); 00419 00420 av[0] = "hi"; 00421 ret = 0; 00422 for (; ; ) 00423 { /* read and process line */ 00424 p = line; 00425 while ((type = token_char(fp, &c)) == SPACE); 00426 for (ac = 1; type != END && ac < ACMAX; ) 00427 { /* split line into tokens */ 00428 av[ac++] = p; /* save ptr to beginning of token */ 00429 do 00430 { 00431 *p++ = c; 00432 if (p >= line + LINEMAX) 00433 { 00434 fprintf(stderr, "input line too long\n"); 00435 exit(1); 00436 } 00437 } 00438 while ((type = token_char(fp, &c)) == TOKEN); 00439 *p++ = 0; /* terminate this token in line[] */ 00440 if (type == END) break; 00441 while ((type = token_char(fp, &c)) == SPACE); 00442 } 00443 if (feof(fp)) break; 00444 if (arg_debug) 00445 { 00446 fprintf(stderr, "ac=%d: ", ac); 00447 for (i = 1; i < ac; i++) fprintf(stderr, "(%s) ", av[i]); 00448 fprintf(stderr, "\n"); 00449 } 00450 00451 err = arg_parse_form1(ac, av, form); 00452 if (!ret) ret = err; 00453 } 00454 if (!ret) ret = arg_done(); 00455 regf = oldregf; 00456 return ret; 00457 } 00458 00459 /* 00460 * token_char: is next char in stream fp part of a token? 00461 * returns TOKEN if char in token, SPACE if whitespace, END if end of input line 00462 * *p gets new char. 00463 * handles quoted strings and escaped characters. 00464 */ 00465 00466 static Token_type token_char(FILE *fp, char *p) 00467 { 00468 int c, old_mode; 00469 Token_type type; 00470 static int mode = 0; /* = '"' or '\'' if inside quoted string */ 00471 00472 type = TOKEN; 00473 do 00474 { 00475 old_mode = mode; 00476 c = getc(fp); 00477 switch (c) 00478 { 00479 case EOF: 00480 type = END; 00481 break; 00482 case '\\': 00483 switch (c = getc(fp)) 00484 { 00485 case 'b': 00486 c = '\b'; 00487 break; 00488 case 'f': 00489 c = '\f'; 00490 break; 00491 case 'n': 00492 c = '\n'; 00493 break; 00494 case 'r': 00495 c = '\r'; 00496 break; 00497 case 't': 00498 c = '\t'; 00499 break; 00500 case 'v': 00501 c = '\v'; 00502 break; 00503 case '0': 00504 c = '\0'; 00505 break; 00506 } 00507 break; 00508 case '"': 00509 switch (mode) 00510 { 00511 case 0: 00512 mode = '"'; 00513 break; /* begin " */ 00514 case '"': 00515 mode = 0; 00516 break; /* end " */ 00517 } 00518 break; 00519 case '\'': 00520 switch (mode) 00521 { 00522 case 0: 00523 mode = '\''; 00524 break; /* begin ' */ 00525 case '\'': 00526 mode = 0; 00527 break; /* end ' */ 00528 } 00529 break; 00530 case '\n': 00531 switch (mode) 00532 { 00533 case 0: 00534 type = END; 00535 break; 00536 } 00537 break; 00538 } 00539 /* loop until we read a literal character */ 00540 } 00541 while (old_mode != mode); 00542 *p = c; 00543 00544 if (type != END && mode == 0 && (c == ' ' || c == '\t' || c == '\n')) 00545 type = SPACE; 00546 return type; 00547 } 00548 00549 /* 00550 * arg_parse_argv: do the actual parsing! 00551 * parse the arguments in av according to the formlist in form 00552 * Returns 0 on success, negative on failure. 00553 */ 00554 00555 int arg_parse_argv(int ac, char **av, ArgForm *form) 00556 { 00557 int ret; 00558 ArgForm *oldregf; 00559 00560 oldregf = regf; 00561 regf = form; 00562 arg_init(form); 00563 ret = arg_parse_form1(ac, av, form); 00564 if (!ret) ret = arg_done(); 00565 regf = oldregf; 00566 return ret; 00567 } 00568 00569 int arg_parse_form1(int ac, char **av, ArgForm *form) 00570 { 00571 int i, di; 00572 ArgForm *f; 00573 00574 for (i = 1; i < ac; i += di) 00575 { 00576 if (arg_debug) 00577 fprintf(stderr, "arg %d: (%s)\n", i, av[i]); 00578 if (av[i][0] == '-' && !NUMERIC(&av[i][1])) 00579 { /* flag argument */ 00580 f = arg_find_flag(av[i], form); 00581 if (!f) 00582 { 00583 if (av[i][1]) 00584 fprintf(stderr, "unrecognized arg: %s\n", av[i]); 00585 else /* arg was "-"; print usage message */ 00586 arg_form_print(form); 00587 return ARG_EXTRA; 00588 } 00589 di = arg_do(ac - i - 1, &av[i + 1], f); 00590 if (di < 0) return di; 00591 di++; 00592 } 00593 else 00594 { /* regular argument */ 00595 f = arg_find_reg(); 00596 if (!f) 00597 { 00598 /* regular args exhausted, see if any flagless subrflags */ 00599 f = arg_find_flag("", form); 00600 if (!f) 00601 { 00602 fprintf(stderr, "extra arg: %s\n", av[i]); 00603 return ARG_EXTRA; 00604 } 00605 } 00606 di = arg_do(ac - i, &av[i], f); 00607 if (di < 0) return di; 00608 } 00609 } 00610 00611 return 0; 00612 } 00613 00614 /* 00615 * arg_init: initialize formlist before parsing arguments 00616 * Set simple flags and repeat counts to 0. 00617 */ 00618 00619 void arg_init(ArgForm *form) 00620 { 00621 ArgForm *f; 00622 00623 for (f = form; f; f = f->next) 00624 if (f->type == ARG_SUBLISTFLAG) 00625 arg_init(f->sublist); /* recurse */ 00626 else 00627 { 00628 f->rep = 0; 00629 if (f->type == ARG_SIMPFLAG) **f->param = 0; 00630 } 00631 } 00632 00633 int arg_done() 00634 { 00635 for (; regf; regf = regf->next) /* any required reg args remaining? */ 00636 if (regf->type == ARG_REGULAR && !(regf->parammask & 1)) 00637 { 00638 fprintf(stderr, "regular arg %s (%s) not set\n", 00639 regf->format, regf->doc); 00640 return ARG_MISSING; 00641 } 00642 return 0; 00643 } 00644 00645 /* 00646 * arg_find_flag: find the flag matching arg in the form list (tree) 00647 * returns form ptr if found, else 0 00648 */ 00649 00650 ArgForm *arg_find_flag(char *arg, ArgForm *form) 00651 { 00652 ArgForm *f, *t; 00653 00654 for (f = form; f; f = f->next) 00655 { 00656 if (f->type != ARG_REGULAR && f->type != ARG_NOP && str_eq(f->flag, arg)) 00657 return f; 00658 if (f->type == ARG_SUBLISTFLAG) 00659 { 00660 t = arg_find_flag(arg, f->sublist); /* recurse */ 00661 if (t) return t; 00662 } 00663 } 00664 return 0; 00665 } 00666 00667 /* 00668 * arg_find_reg: find next regular argument 00669 * each call advances the global pointer regf through the formlist 00670 */ 00671 00672 ArgForm *arg_find_reg() 00673 { 00674 ArgForm *f; 00675 00676 for (; regf; regf = regf->next) 00677 { 00678 if (regf->type == ARG_REGULAR) 00679 { 00680 f = regf; 00681 regf = regf->next; 00682 return f; 00683 } 00684 } 00685 return 0; 00686 } 00687 00688 /* 00689 * arg_do: process one form by parsing arguments in av according to the 00690 * single form in f 00691 * 00692 * f was found by arg_find_flag or arg_find_reg, 00693 * so if f is a flag then we know av[-1] matches f->flag 00694 * 00695 * examine av[0]-av[ac-1] to determine number of parameters supplied 00696 * if simpleflag, set flag parameter and read no args 00697 * if subrflag, call subroutine on sub-args 00698 * if sublist, call arg_parse_form on sub-args 00699 * else it's a paramflag or regular arg, do arg-to-param assignments 00700 * return number of arguments gobbled, or negative error code 00701 */ 00702 00703 int arg_do(int ac, char **av, ArgForm *f) 00704 { 00705 int narg, skip, used, err, i; 00706 00707 if (arg_debug) 00708 av_print(" arg_do", ac, av); 00709 if (f->type == ARG_SIMPFLAG || f->type == ARG_PARAMFLAG) 00710 { 00711 /* don't complain about repeated subrflags or sublists */ 00712 Assert(str_eq(av[ -1], f->flag), "flag wrong"); 00713 f->rep++; 00714 if (f->rep > 1 && arg_warning) 00715 fprintf(stderr, "warning: more than one %s flag in arglist\n", 00716 f->flag); 00717 } 00718 00719 narg = nargs(ac, av, f, &skip); 00720 00721 used = 0; 00722 switch (f->type) 00723 { 00724 case ARG_SIMPFLAG: 00725 **f->param = 1; 00726 break; 00727 case ARG_SUBRFLAG: 00728 (*f->subr)(narg, av); 00729 break; 00730 case ARG_SUBLISTFLAG: 00731 arg_parse_argv(narg + 1, &av[ -1], f->sublist); /* recurse */ 00732 used = narg; 00733 break; 00734 default: /* convert parameters */ 00735 err = scan(narg, av, f); 00736 if (err) return err; 00737 used = narg < f->nparam ? narg : f->nparam; 00738 break; 00739 } 00740 00741 if ((f->type == ARG_REGULAR || f->type == ARG_PARAMFLAG) && used != narg) 00742 { 00743 fprintf(stderr, "warning: %d unused arg%s to %s: ", 00744 narg - used, narg - used > 1 ? "s" : "", av[ -1]); 00745 for (i = used; i < narg; i++) 00746 fprintf(stderr, "%s ", av[i]); 00747 fprintf(stderr, "\n"); 00748 } 00749 return skip; 00750 } 00751 00752 /* 00753 * nargs: Count number of parameters in arg vector av before the next flag. 00754 * Arguments can be grouped and "escaped" using -{ and -}. 00755 * NOTE: modifies av 00756 * Returns number of valid args in new av. 00757 * Sets *skip to number of args to skip in old av to get to next flag. 00758 * 00759 * This is the most complex code in arg_parse. 00760 * Is there a better way? 00761 * 00762 * examples: 00763 * input: ac=3, av=(3 4 -go) 00764 * output: av unchanged, skip=2, return 2 00765 * 00766 * input: ac=4, av=(-{ -ch r -}) 00767 * output: av=(-ch r X X), skip=4, return 2 00768 * 00769 * input: ac=4, av=(-{ -foo -} -go) 00770 * output: av=(-foo X X -go), skip=3, return 1 00771 * 00772 * input: ac=6, av=(-{ -ch -{ -rgb -} -}) 00773 * output: av=(-ch -{ -rgb -} X X), skip=6, return 4 00774 * 00775 * where X stands for junk 00776 */ 00777 00778 static int nargs(int ac, char **av, ArgForm *f, int *skip) 00779 { 00780 char *flag, **au, **av0; 00781 int i, j, level, die, np, mask, voracious; 00782 00783 np = f->nparam; 00784 mask = f->parammask; 00785 flag = f->type == ARG_REGULAR ? f->format : f->flag; 00786 voracious = f->type == ARG_SUBRFLAG || f->type == ARG_SUBLISTFLAG; 00787 /* subrs&sublists want all the args they can get */ 00788 00789 level = 0; 00790 av0 = au = av; 00791 if (voracious) np = 999; 00792 for (die = 0, i = 0; i < np && i < ac && !die; ) 00793 { 00794 if (voracious) j = 999; 00795 else for (j = i + 1; !(mask >> j&1); j++); /* go until we can stop */ 00796 /* try to grab params i through j-1 */ 00797 for (; i < j && i < ac || level > 0; i++, au++, av++) 00798 { 00799 if (au != av) *au = *av; 00800 if (str_eq(*av, "-{")) 00801 { 00802 if (level <= 0) au--; /* skip "-{" in av if level 0 */ 00803 level++; /* push a level */ 00804 } 00805 else if (str_eq(*av, "-}")) 00806 { 00807 level--; /* pop a level */ 00808 if (level <= 0) au--; /* skip "-}" in av if level 0 */ 00809 if (level < 0) 00810 fprintf(stderr, "ignoring spurious -}\n"); 00811 } 00812 else if (level == 0 && av[0][0] == '-' && !NUMERIC(&av[0][1])) 00813 { 00814 die = 1; /* break out of both loops */ 00815 break; /* encountered flag at level 0 */ 00816 } 00817 } 00818 } 00819 if (arg_debug) 00820 { 00821 fprintf(stderr, " %s: requested %d, got %d args: ", 00822 flag, np, au - av0); 00823 for (j = 0; j < au - av0; j++) 00824 fprintf(stderr, "%s ", av0[j]); 00825 fprintf(stderr, "\n"); 00826 } 00827 *skip = i; 00828 return au - av0; 00829 } 00830 00831 /* 00832 * scan: call sscanf to read args into param array and do conversion 00833 * returns error code (0 on success) 00834 */ 00835 00836 static int scan(int narg, char **arg, ArgForm *f) 00837 { 00838 static char str[] = "%X"; 00839 char *s; 00840 int i, **p; 00841 double x; 00842 00843 if (f->nparam < narg) narg = f->nparam; 00844 if (!(f->parammask >> narg&1)) 00845 { 00846 fprintf(stderr, "you can't give %s just %d params\n", 00847 f->format, narg); 00848 return ARG_MISSING; 00849 } 00850 for (p = f->param, i = 0; i < narg; i++, p++) 00851 { 00852 str[1] = f->code[i]; 00853 switch (str[1]) 00854 { 00855 case 'S': 00856 /* 00857 * dynamically allocate memory for string 00858 * for arg_parse_argv: in case argv gets clobbered (rare) 00859 * for arg_parse_stream: since line[] buffer is reused (always) 00860 */ 00861 s = new char[strlen(arg[i]) + 1]; 00862 strcpy(s, arg[i]); 00863 *(char **)*p = s; 00864 break; 00865 case 's': /* scanf "%s" strips leading, trailing blanks */ 00866 strcpy((char *) *p, arg[i]); 00867 break; 00868 case 'd': 00869 *(int *)*p = expr_eval_int(arg[i]); 00870 if (expr_error == EXPR_BAD) 00871 { /* expression is garbage */ 00872 fprintf(stderr, "bad %s param\n", f->flag); 00873 return ARG_BADARG; 00874 } 00875 break; 00876 case 'D': 00877 *(long *)*p = expr_eval_long(arg[i]); 00878 if (expr_error == EXPR_BAD) 00879 { /* expression is garbage */ 00880 fprintf(stderr, "bad %s param\n", f->flag); 00881 return ARG_BADARG; 00882 } 00883 break; 00884 case 'f': 00885 case 'F': 00886 x = expr_eval(arg[i]); 00887 if (expr_error == EXPR_BAD) 00888 { /* expression is garbage */ 00889 fprintf(stderr, "bad %s param\n", f->flag); 00890 return ARG_BADARG; 00891 } 00892 if (str[1] == 'f') *(float *)*p = x; 00893 else *(double *)*p = x; 00894 break; 00895 default: 00896 if (sscanf(arg[i], str, *p) != 1) 00897 { 00898 fprintf(stderr, "bad %s param: \"%s\" doesn't match %s\n", 00899 f->flag, arg[i], str); 00900 return ARG_BADARG; 00901 } 00902 break; 00903 } 00904 } 00905 return 0; /* return 0 on success */ 00906 } 00907 00908 static char *bar = "==================================================\n"; 00909 00910 /* arg_form_print: print ArgForm as usage message to stderr */ 00911 00912 void arg_form_print(ArgForm *form) 00913 { 00914 ArgForm *f; 00915 00916 for (f = form; f; f = f->next) 00917 { 00918 if (f->type != ARG_NOP || f->format[0]) 00919 { 00920 fprintf(stderr, "%s", f->format); 00921 space(stderr, strlen(f->format), arg_doccol); 00922 } 00923 fprintf(stderr, "%s\n", f->doc); 00924 if (arg_debug) 00925 fprintf(stderr, " %d (%s) [%s][%s]%x (%s)\n", 00926 f->type, f->format, f->flag, f->code, f->parammask, f->doc); 00927 if (f->type == ARG_SUBLISTFLAG) 00928 { 00929 fprintf(stderr, bar); 00930 arg_form_print(f->sublist); 00931 fprintf(stderr, bar); 00932 } 00933 } 00934 } 00935 00936 /* checkstr: check that s is a valid string */ 00937 00938 static int checkstr(char *s, char *name, char *prev) 00939 { 00940 char *delim; 00941 00942 delim = prev ? "\"" : ""; 00943 if (!s || (int)s&ARG_MASKNEXT) 00944 { 00945 fprintf(stderr, "bad arg call: missing %s after %s%s%s\n", 00946 name, delim, prev, delim); 00947 return 1; 00948 } 00949 return 0; 00950 } 00951 00952 /* 00953 * space: currently in column c; tab and space over to column c1 00954 * assumes 8-space tabs 00955 */ 00956 00957 static void space(FILE *fp, int c, int c1) 00958 { 00959 if (c >= c1) 00960 { 00961 putc('\n', fp); 00962 c = 0; 00963 } 00964 for (; c < c1&~7; c = (c + 7) & ~7) putc('\t', fp); 00965 for (; c < c1; c++) putc(' ', fp); 00966 } 00967 00968 void av_print(char *str, int ac, char **av) 00969 { 00970 int i; 00971 00972 fprintf(stderr, "%s: ", str); 00973 for (i = 0; i < ac; i++) 00974 fprintf(stderr, "%s ", av[i]); 00975 fprintf(stderr, "\n"); 00976 } 00977 00978 void arg_form_append(ArgForm *form, ArgForm *additionalForm) 00979 { 00980 ArgForm *af; 00981 00982 for (af = form; af->next; af = af->next) 00983 ; 00984 af->next = additionalForm; 00985 }