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 }