From f1898377bd36ff56829266d423aba8fe3d984579 Mon Sep 17 00:00:00 2001 From: millaway Date: Sat, 13 Oct 2001 00:44:01 +0000 Subject: Added long option parsing. All positive %options are now accessible from command line. Added option -D, (like gcc's -D) to define a preprocessor symbol. Added option --header=FILE to specify a C .h file to generate. Options -n and -c, previously deprecated, now cause flex to abort. Options are now parsed left to right. --- Makefile.am | 10 +- flexdef.h | 6 + main.c | 521 +++++++++++++++++++++------------------ options.c | 78 ++++++ options.h | 58 +++++ scanopt.c | 801 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ scanopt.h | 128 ++++++++++ 7 files changed, 1358 insertions(+), 244 deletions(-) create mode 100644 options.c create mode 100644 options.h create mode 100644 scanopt.c create mode 100644 scanopt.h diff --git a/Makefile.am b/Makefile.am index 79795cc..d9f9883 100644 --- a/Makefile.am +++ b/Makefile.am @@ -57,7 +57,9 @@ flex_SOURCES = \ skel.c \ sym.c \ tblcmp.c \ - yylex.c + yylex.c \ + options.c \ + scanopt.c libfl_a_SOURCES = \ libmain.c \ @@ -66,7 +68,9 @@ libfl_a_SOURCES = \ noinst_HEADERS = \ flexdef.h \ parse.h \ - version.h + version.h \ + options.h \ + scanopt.h include_HEADERS = \ FlexLexer.h @@ -111,6 +115,8 @@ parse.o: parse.c flexdef.h config.h skel.o: skel.c flexdef.h config.h sym.o: sym.c flexdef.h config.h tblcmp.o: tblcmp.c flexdef.h config.h +options.o: options.c options.h scanopt.h flexdef.h config.h +scanopt.o: scanopt.c scanopt.h flexdef.h config.h #The below recipe for making the ChangeLog will only work if you have a copy of the cvs tree handy so let it fail gracefully. diff --git a/flexdef.h b/flexdef.h index 0ccc5d9..983d7e5 100644 --- a/flexdef.h +++ b/flexdef.h @@ -31,8 +31,12 @@ /* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR */ /* PURPOSE. */ +#ifndef FLEXDEF_H +#define FLEXDEF_H 1 + #include #include +#include #include "config.h" @@ -1022,3 +1026,5 @@ extern void stack1 PROTO((int, int, int, int)); /* from file yylex.c */ extern int yylex PROTO((void)); + +#endif /* not defined FLEXDEF_H */ diff --git a/main.c b/main.c index ea1785f..5b771f0 100644 --- a/main.c +++ b/main.c @@ -39,6 +39,7 @@ char copyright[] = #include "flexdef.h" #include "version.h" +#include "options.h" static char flex_version[] = FLEX_VERSION; @@ -244,10 +245,10 @@ void check_options() yytext_is_array = false; } - if ( C_plus_plus && (reentrant || reentrant_bison_pure) ) + if ( C_plus_plus && (reentrant || reentrant_bison_pure) ) flexerror( _( "Options -+ and -R are mutually exclusive." ) ); - - + + if ( useecs ) { /* Set up doubly-linked equivalence classes. */ @@ -362,7 +363,7 @@ void check_options() if ( do_yylineno ) GEN_PREFIX( "lineno" ); - + if ( do_yylineno && reentrant) outn ( "#define YY_USE_LINENO 1"); } @@ -461,12 +462,12 @@ int exit_status; putc( 'p', stderr ); if ( performance_report > 1 ) putc( 'p', stderr ); - if ( spprdflt ) + if ( spprdflt ) putc( 's', stderr ); - if ( reentrant ) + if ( reentrant ) { putc( 'R', stderr ); - + if( reentrant_bison_pure ) putc( 'b', stderr ); } @@ -629,8 +630,9 @@ void flexinit( argc, argv ) int argc; char **argv; { - int i, sawcmpflag; + int i, sawcmpflag, rv, optind; char *arg; + scanopt_t sopt; printstats = syntaxerror = trace = spprdflt = caseins = false; lex_compat = C_plus_plus = backing_up_report = ddebug = fulltbl = false; @@ -662,240 +664,275 @@ char **argv; C_plus_plus = true; /* read flags */ - for ( --argc, ++argv; argc ; --argc, ++argv ) - { - arg = argv[0]; - - /* Stop at first non-option. */ - if ( arg[0] != '-' || arg[1] == '\0' ) - break; - - if ( arg[1] == '-' ) - { /* --option */ - if ( ! strcmp( arg, "--help" ) ) - arg = "-h"; - - else if ( ! strcmp( arg, "--version" ) ) - arg = "-V"; - - else if ( ! strcmp( arg, "--" ) ) - { /* end of options */ - --argc; - ++argv; - break; - } - } + sopt = scanopt_init(flexopts, argc, argv, 0); + if (!sopt) { + /* This will only happen when flexopts array is altered. */ + fprintf(stderr, + _("Internal error. flexopts are malformed.\n")); + exit(1); + } + + while((rv=scanopt(sopt, &arg, &optind)) != 0){ + + if (rv < 0) { + /* Scanopt has already printed an option-specific error message. */ + fprintf( stderr, _( "For usage, try\n\t%s --help\n" ), + program_name ); + exit( 1 ); + break; + } - for ( i = 1; arg[i] != '\0'; ++i ) - switch ( arg[i] ) - { - case '+': - C_plus_plus = true; - break; - - case 'B': - interactive = false; - break; - - case 'b': - backing_up_report = true; - break; - - case 'c': - break; - - case 'C': - if ( i != 1 ) - flexerror( - _( "-C flag must be given separately" ) ); - - if ( ! sawcmpflag ) - { - useecs = false; - usemecs = false; - fulltbl = false; - sawcmpflag = true; - } - - for ( ++i; arg[i] != '\0'; ++i ) - switch ( arg[i] ) - { - case 'a': - long_align = - true; - break; - - case 'e': - useecs = true; - break; - - case 'F': - fullspd = true; - break; - - case 'f': - fulltbl = true; - break; - - case 'm': - usemecs = true; - break; - - case 'r': - use_read = true; - break; - - default: - lerrif( - _( "unknown -C option '%c'" ), - (int) arg[i] ); - break; - } - - goto get_next_arg; - - case 'd': - ddebug = true; - break; - - case 'f': - useecs = usemecs = false; - use_read = fulltbl = true; - break; - - case 'F': - useecs = usemecs = false; - use_read = fullspd = true; - break; - - case '?': - case 'h': - usage(); - exit( 0 ); - - case 'I': - interactive = true; - break; - - case 'i': - caseins = true; - break; - - case 'l': - lex_compat = true; - break; - - case 'L': - gen_line_dirs = false; - break; - - case 'n': - /* Stupid do-nothing deprecated - * option. - */ - break; - - case 'o': - if ( i != 1 ) - flexerror( - _( "-o flag must be given separately" ) ); - - outfilename = arg + i + 1; - did_outfilename = 1; - goto get_next_arg; - - case 'P': - if ( i != 1 ) - flexerror( - _( "-P flag must be given separately" ) ); - - prefix = arg + i + 1; - goto get_next_arg; - - case 'p': - ++performance_report; - break; - - case 'R': - if ( i != 1 ) - flexerror( - _( "-R flag must be given separately" ) ); + switch ((enum flexopt_flag_t)rv){ + case OPT_CPLUSPLUS: + C_plus_plus = true; + break; + + case OPT_BATCH: + interactive = false; + break; + + case OPT_BACKUP: + backing_up_report = true; + break; + + case OPT_DEPRECATED: + flexerror(_("Options -c and -n are deprecated. Do not use.")); + break; + + case OPT_COMPRESSION: + if ( ! sawcmpflag ) + { + useecs = false; + usemecs = false; + fulltbl = false; + sawcmpflag = true; + } + + for( i=0 ; arg && arg[i] != '\0'; i++) + switch ( arg[i] ) + { + case 'a': + long_align = true; + break; + + case 'e': + useecs = true; + break; + + case 'F': + fullspd = true; + break; + + case 'f': + fulltbl = true; + break; + + case 'm': + usemecs = true; + break; + + case 'r': + use_read = true; + break; + + default: + lerrif( + _( "unknown -C option '%c'" ), + (int) arg[i] ); + break; + } + break; + + case OPT_DEBUG: + ddebug = true; + break; + + case OPT_FULL: + useecs = usemecs = false; + use_read = fulltbl = true; + break; + + case OPT_FAST: + useecs = usemecs = false; + use_read = fullspd = true; + break; + + case OPT_HELP: + usage(); + exit( 0 ); + + case OPT_INTERACTIVE: + interactive = true; + break; + + case OPT_CASE_INSENSITIVE: + caseins = true; + break; + + case OPT_LEX_COMPAT: + lex_compat = true; + break; + + case OPT_MAIN: + action_define( "YY_MAIN", 1); + break; + + case OPT_NOLINE: + gen_line_dirs = false; + break; + + case OPT_OUTFILE: + outfilename = arg; + did_outfilename = 1; + break; + + case OPT_PREFIX: + prefix = arg; + break; + + case OPT_PERF_REPORT: + ++performance_report; + break; + + case OPT_REENTRANT_BISON: + reentrant = true; + reentrant_bison_pure = true; + break; + + case OPT_REENTRANT: reentrant = true; - - /* Optional arguments follow -R */ - - for ( ++i; arg[i] != '\0'; ++i ) - switch ( arg[i] ) - { - case 'b': - reentrant_bison_pure = true; - break; - - default: - lerrif( - _( "unknown -R option '%c'" ), - (int) arg[i] ); - break; - } - goto get_next_arg; - - case 'S': - if ( i != 1 ) - flexerror( - _( "-S flag must be given separately" ) ); - - skelname = arg + i + 1; - goto get_next_arg; - - case 's': - spprdflt = true; - break; - - case 't': - use_stdout = true; - break; - - case 'T': - trace = true; - break; - - case 'v': - printstats = true; - break; - - case 'V': - printf( _( "%s version %s\n" ), - program_name, flex_version ); - exit( 0 ); - - case 'w': - nowarn = true; - break; - - case '7': - csize = 128; - break; - - case '8': - csize = CSIZE; - break; - - default: - fprintf( stderr, - _( "%s: unknown flag '%c'. For usage, try\n\t%s --help\n" ), - program_name, (int) arg[i], - program_name ); - exit( 1 ); - } - - /* Used by -C, -S, -o, and -P flags in lieu of a "continue 2" - * control. - */ - get_next_arg: ; - } - num_input_files = argc; - input_files = argv; + /* Optional 'b' follows -R */ + if (arg) { + if (strcmp(arg,"b")==0) + reentrant_bison_pure = true; + else + lerrif(_( "unknown -R option '%c'" ),(int)arg[i]); + } + break; + + case OPT_SKEL: + skelname = arg; + break; + + case OPT_DEFAULT: + spprdflt = false; + break; + + case OPT_NODEFAULT: + spprdflt = true; + break; + + case OPT_STDOUT: + use_stdout = true; + break; + + case OPT_TRACE: + trace = true; + break; + + case OPT_VERBOSE: + printstats = true; + break; + + case OPT_VERSION: + printf( _( "%s version %s\n" ), + program_name, flex_version ); + exit( 0 ); + + case OPT_NOWARN: + nowarn = true; + break; + + case OPT_7BIT: + csize = 128; + break; + + case OPT_8BIT: + csize = CSIZE; + break; + + case OPT_ALIGN: + long_align = true; + break; + + case OPT_ALWAYS_INTERACTIVE: + action_define("YY_ALWAYS_INTERACTIVE", 1); + break; + + case OPT_NEVER_INTERACTIVE: + action_define( "YY_NEVER_INTERACTIVE", 1 ); + break; + + case OPT_ARRAY: + yytext_is_array = true; + break; + + case OPT_POINTER: + yytext_is_array = false; + break; + + case OPT_ECS: + useecs = true; + break; + + case OPT_HEADER: /*TODO*/ + break; + + case OPT_META_ECS: + usemecs = true; + + + case OPT_PREPROCDEFINE: + { + /* arg is "symbol" or "symbol=definition". + Replace the first '=' with ' '. + I'm not sure that argv[] is writable. So use a copy. */ + char *sym, *p; + sym = (char*)malloc(strlen("#define ")+strlen(arg)+1); + sprintf(sym,"#define %s\n",arg); + for(p=sym; *p != '\0'; ++p) + if (*p == '='){ + *p = ' '; + break; + } + + add_action(sym); + free(sym); + } + break; + + case OPT_READ: + use_read = true; + break; + + case OPT_STACK: + action_define( "YY_STACK_USED", 1 ); + break; + + case OPT_STDINIT: + do_stdinit = true; + break; + + case OPT_YYCLASS: + yyclass = arg; + break; + + case OPT_YYLINENO: + do_yylineno = true; + break; + + case OPT_YYWRAP: + do_yywrap = true; + break; + + } /* switch */ + } /* while scanopt() */ + + scanopt_destroy(sopt); + + num_input_files = argc - optind; + input_files = argv + optind; set_input_file( num_input_files > 0 ? input_files[0] : NULL ); lastccl = lastsc = lastdfa = lastnfa = 0; @@ -1040,7 +1077,7 @@ _( "Variable trailing context rules entail a large performance penalty\n" ) ); } else - { + { /* In reentrant scanner, stdinit is handled in flex.skl. */ if ( do_stdinit ) { @@ -1065,7 +1102,7 @@ _( "Variable trailing context rules entail a large performance penalty\n" ) ); outn( "#endif" ); } - else + else { outn( "#ifndef YY_REENTRANT" ); outn( yy_nostdinit ); @@ -1102,7 +1139,7 @@ _( "Variable trailing context rules entail a large performance penalty\n" ) ); "\tLexerError( \"yyFlexLexer::yylex invoked but %option yyclass used\" );" ); outn( "\treturn 0;" ); outn( "\t}" ); - + out_str( "\n#define YY_DECL int %s::yylex()\n", yyclass ); } diff --git a/options.c b/options.c new file mode 100644 index 0000000..bd0af32 --- /dev/null +++ b/options.c @@ -0,0 +1,78 @@ +#include "options.h" + +optspec_t flexopts[] = { + +{"--7bit", OPT_7BIT,0},/* Generate 7-bit scanner. */ +{"-7", OPT_7BIT,0}, +{"--8bit", OPT_8BIT,0},/* Generate 8-bit scanner. */ +{"-8", OPT_8BIT,0}, +{"--align", OPT_ALIGN,0},/* Trade off larger tables for better memory alignment. */ +{"--always-interactive",OPT_ALWAYS_INTERACTIVE,0}, +{"--array", OPT_ARRAY,0}, +{"--backup", OPT_BACKUP,0},/* Generate backing-up information to lex.backup. */ +{"-b", OPT_BACKUP,0}, +{"--batch", OPT_BATCH,0},/* Generate batch scanner (opposite of -I). */ +{"-B", OPT_BATCH,0}, +{"--case-insensitive", OPT_CASE_INSENSITIVE,0},/* Generate case-insensitive scanner. */ +{"-i", OPT_CASE_INSENSITIVE,0}, +{"-C[aefFmr]", OPT_COMPRESSION,"Specify degree of table compression (default is -Cem)"}, +{"--c++", OPT_CPLUSPLUS,0},/* Generate C++ scanner class. */ +{"-+", OPT_CPLUSPLUS,0}, +{"--debug", OPT_DEBUG,0},/* Turn on debug mode in generated scanner. */ +{"-d", OPT_DEBUG,0}, +{"--default", OPT_DEFAULT,0}, +{"-c", OPT_DEPRECATED,0},/* Deprecated. Do not use. */ +{"-n", OPT_DEPRECATED,0},/* Deprecated. Do not use. */ +{"--ecs", OPT_ECS,0},/* Construct equivalence classes. */ +{"--fast", OPT_FAST,0},/* Same as -CFr. */ +{"-F", OPT_FAST,0}, +{"--full", OPT_FULL,0},/* Same as -Cfr. */ +{"-f", OPT_FULL,0}, +{"--header[=FILE]", OPT_HEADER,0}, +{"--help", OPT_HELP,0},/* Produce this help message. */ +{"-?", OPT_HELP,0}, +{"-h", OPT_HELP,0}, +{"--interactive", OPT_INTERACTIVE,0},/* Generate interactive scanner (opposite of -B). */ +{"-I", OPT_INTERACTIVE,0}, +{"--lex-compat", OPT_LEX_COMPAT,0},/* Maximal compatibility with original lex. */ +{"-l", OPT_LEX_COMPAT,0}, +{"--main", OPT_MAIN,0}, /* use built-in main() function. */ +{"--meta-ecs", OPT_META_ECS,0},/* Construct meta-equivalence classes. */ +{"--never-interactive", OPT_NEVER_INTERACTIVE,0}, +{"--no-default", OPT_NODEFAULT,0},/* Suppress default rule to ECHO unmatched text. */ +{"-s", OPT_NODEFAULT,0}, +{"--no-line", OPT_NOLINE,0},/* Suppress #line directives in scanner. */ +{"-L", OPT_NOLINE,0},/* Suppress #line directives in scanner. */ +{"--no-warn", OPT_NOWARN,0},/* Suppress warning messages. */ +{"-w", OPT_NOWARN,0}, +{"--outfile=FILE", OPT_OUTFILE,0},/* Write to FILE (default is lex.yy.c) */ +{"-o FILE", OPT_OUTFILE,0}, +{"--perf-report", OPT_PERF_REPORT,0},/* Generate performance report to stderr. */ +{"-p", OPT_PERF_REPORT,0}, +{"--pointer", OPT_POINTER,0}, +{"--prefix=PREFIX", OPT_PREFIX,0},/* Use PREFIX (default is yy) */ +{"-P PREFIX", OPT_PREFIX,0}, +{"-D", OPT_PREPROCDEFINE,0},/* Define a preprocessor symbol. */ +{"--read", OPT_READ,0},/* Use read(2) instead of stdio. */ +{"--reentrant", OPT_REENTRANT,0},/* Generate a reentrant C scanner. */ +{"-R[b]", OPT_REENTRANT,0}, +{"--reentrant-bison", OPT_REENTRANT_BISON,0},/* Reentrant scanner to be called by a bison pure parser. */ +{"--skel=FILE", OPT_SKEL,0},/* Use skeleton from FILE */ +{"-S FILE", OPT_SKEL,0}, +{"--stack", OPT_STACK,0}, +{"--stdinit", OPT_STDINIT,0}, +{"--stdout", OPT_STDOUT,0},/* Write generated scanner to stdout. */ +{"-t", OPT_STDOUT,0}, +{"--trace", OPT_TRACE,0},/* Flex should run in trace mode. */ +{"-T", OPT_TRACE,0}, +{"--verbose", OPT_VERBOSE,0},/* Write summary of scanner statistics to stdout. */ +{"-v", OPT_VERBOSE,0}, +{"--version", OPT_VERSION,0},/* Report flex version. */ +{"-V", OPT_VERSION,0}, +{"--yyclass=NAME", OPT_YYCLASS,0}, +{"--yylineno", OPT_YYLINENO,0}, +{"--yywrap" , OPT_YYWRAP,0}, +{0,0,0} /* required final NULL entry.*/ +}; + +/* vim:set tabstop=8 softtabstop=4 shiftwidth=4: */ diff --git a/options.h b/options.h new file mode 100644 index 0000000..bdfdcd7 --- /dev/null +++ b/options.h @@ -0,0 +1,58 @@ +#ifndef OPTIONS_H +#define OPTIONS_H +#include "scanopt.h" + +extern optspec_t flexopts[]; + +enum flexopt_flag_t { + /* Use positive integers only, since they are return codes for scanopt. + * Order is not important. */ + OPT_7BIT=1, + OPT_8BIT, + OPT_ALIGN, + OPT_ALWAYS_INTERACTIVE, + OPT_ARRAY, + OPT_BACKUP, + OPT_BATCH, + OPT_CASE_INSENSITIVE, + OPT_COMPRESSION, + OPT_CPLUSPLUS, + OPT_DEBUG, + OPT_DEFAULT, + OPT_DEPRECATED, + OPT_ECS, + OPT_FAST, + OPT_FULL, + OPT_HEADER, + OPT_HELP, + OPT_INTERACTIVE, + OPT_LEX_COMPAT, + OPT_MAIN, + OPT_META_ECS, + OPT_NEVER_INTERACTIVE, + OPT_NODEFAULT, + OPT_NOLINE, + OPT_NOWARN, + OPT_OUTFILE, + OPT_PERF_REPORT, + OPT_POINTER, + OPT_PREFIX, + OPT_PREPROCDEFINE, + OPT_READ, + OPT_REENTRANT, + OPT_REENTRANT_BISON, + OPT_SKEL, + OPT_STACK, + OPT_STDINIT, + OPT_STDOUT, + OPT_TRACE, + OPT_VERBOSE, + OPT_VERSION, + OPT_YYCLASS, + OPT_YYLINENO, + OPT_YYWRAP +}; + +#endif + +/* vim:set tabstop=8 softtabstop=4 shiftwidth=4 textwidth=0: */ diff --git a/scanopt.c b/scanopt.c new file mode 100644 index 0000000..bf63728 --- /dev/null +++ b/scanopt.c @@ -0,0 +1,801 @@ +/* + * Copyright (c) 2001, John W. Millaway + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "scanopt.h" + + +/* Internal structures */ + +#ifdef HAVE_STRCASECMP +#define STRCASECMP(a,b) strcasecmp(a,b) +#else +static int STRCASECMP(a,b) + const char* a; + const char* b; +{ + while(tolower(*a++) == tolower(*b++)) + ; + return b-a; +} +#endif + +#define ARG_NONE 0x01 +#define ARG_REQ 0x02 +#define ARG_OPT 0x04 +#define IS_LONG 0x08 + +struct _aux { + int flags; /* The above hex flags. */ + int namelen; /* Length of the actual option word, e.g., "--file[=foo]" is 4 */ + int printlen; /* Length of entire string, e.g., "--file[=foo]" is 12 */ +}; + + +struct _scanopt_t +{ + const optspec_t * options; /* List of options. */ + struct _aux * aux; /* Auxiliary data about options. */ + int optc; /* Number of options. */ + int argc; /* Number of args. */ + char ** argv; /* Array of strings. */ + int index; /* Used as: argv[index][subscript]. */ + int subscript; + char no_err_msg; /* If true, do not print errors. */ + char has_long; + char has_short; +}; + +/* Accessor functions. These WOULD be one-liners, but portability calls. */ +static const char* NAME(s,i) + struct _scanopt_t *s; int i; +{ + return s->options[i].opt_fmt + ((s->aux[i].flags & IS_LONG)?2:1); +} + +static int PRINTLEN(s,i) + struct _scanopt_t *s; int i; +{ + return s->aux[i].printlen; +} + +static int RVAL(s,i) + struct _scanopt_t *s; int i; +{ + return s->options[i].r_val; +} + +static int FLAGS(s,i) + struct _scanopt_t *s; int i; +{ + return s->aux[i].flags; +} +static const char* DESC(s,i) + struct _scanopt_t *s; int i; +{ + return s->options[i].desc ? s->options[i].desc : ""; +} + +#ifndef NO_SCANOPT_USAGE +static int get_cols() +{ + char *env; + int cols = 80; /* default */ + +#ifdef HAVE_NCURSES_H + initscr(); + endwin(); + if ( COLS > 0 ) + return COLS; +#endif + + if((env = getenv("COLUMNS"))!=NULL) + cols=atoi(env); + + return cols; +} +#endif + +/* Macro to check for NULL before assigning a value. */ +#define SAFE_ASSIGN(ptr,val) \ + do{ \ + if((ptr)!=NULL) \ + *(ptr) = val; \ + }while(0) + +/* Macro to assure we reset subscript whenever we adjust s->index.*/ +#define INC_INDEX(s,n) \ + do{ \ + (s)->index += (n); \ + (s)->subscript= 0; \ + }while(0) + +scanopt_t * +scanopt_init ( options, argc, argv, flags) + const optspec_t* options; + int argc; + char** argv; + int flags; +{ + int i; + struct _scanopt_t * s; + s = (struct _scanopt_t*)malloc(sizeof(struct _scanopt_t)); + + s->options = options; + s->optc = 0; + s->argc = argc; + s->argv = (char**)argv; + s->index = 1; + s->subscript = 0; + s->no_err_msg = (flags & SCANOPT_NO_ERR_MSG); + s->has_long = 0; + s->has_short = 0; + + /* Determine option count. (Find entry with all zeros).*/ + s->optc = 0; + while ( options[s->optc].opt_fmt + || options[s->optc].r_val + || options[s->optc].desc ) + s->optc++; + + /* Build auxiliary data */ + s->aux = (struct _aux*)malloc(s->optc * sizeof(struct _aux)); + + for (i=0; i < s->optc; i++) { + const char * p, *pname; + const struct optspec_t* opt; + struct _aux * aux; + + opt = s->options + i; + aux = s->aux + i; + + aux->flags = ARG_NONE; + + if( opt->opt_fmt[0] == '-' && opt->opt_fmt[1] == '-') { + aux->flags |= IS_LONG; + pname = opt->opt_fmt + 2; + s->has_long = 1; + }else{ + pname = opt->opt_fmt + 1; + s->has_short = 1; + } + aux->printlen = strlen(opt->opt_fmt); + + aux->namelen = 0; + for (p=pname; *p; p++) { + if (*p == '=' || isspace(*p)) { + if (aux->namelen==0) + aux->namelen = p - pname; + aux->flags |= ARG_REQ; + aux->flags &= ~ARG_NONE; + } + if (*p == '[') { + if (aux->namelen==0) + aux->namelen = p - pname; + aux->flags &= ~(ARG_REQ|ARG_NONE); + aux->flags |= ARG_OPT; + break; + } + } + if (aux->namelen ==0) + aux->namelen = p - pname; + } + return (scanopt_t*)s; +} + +#ifndef NO_SCANOPT_USAGE +/* these structs are for scanopt_usage(). */ +struct usg_elem { + int idx; + struct usg_elem * next; + struct usg_elem * alias; +}; +typedef struct usg_elem usg_elem; + + +/* Prints a usage message based on contents of optlist. + * Parameters: + * scanner - The scanner, already initialized with scanopt_init(). + * fp - The file stream to write to. + * usage - Text to be prepended to option list. + * Return: Always returns 0 (zero). + * The output looks something like this: + +[indent][option, alias1, alias2...][indent][description line1 + description line2...] + */ +int scanopt_usage (scanner,fp,usage) + scanopt_t* scanner; + FILE* fp; + const char* usage; +{ + struct _scanopt_t * s; + int i,columns,indent=2; + usg_elem *byr_val=NULL; /* option indices sorted by r_val */ + usg_elem *store; /* array of preallocated elements. */ + int store_idx=0; + usg_elem *ue; + int maxlen[2] = {0,0}; + int desccol=0; + int print_run=0; + + + s = (struct _scanopt_t*)scanner; + + if (usage){ + fprintf(fp,"%s\n",usage); + }else{ + /* Find the basename of argv[0] */ + const char * p; + p = s->argv[0] + strlen(s->argv[0]); + while(p != s->argv[0] && *p != '/') + --p; + if (*p == '/') + p++; + + fprintf(fp,"Usage: %s [OPTIONS]...\n", p); + } + fprintf(fp,"\n"); + + /* Sort by r_val and string. Yes, this is O(n*n), but n is small. */ + store = (usg_elem*)malloc(s->optc*sizeof(usg_elem)); + for (i=0; i < s->optc; i++) { + + /* grab the next preallocate node. */ + ue = store + store_idx++; + ue->idx = i; + ue->next = ue->alias = NULL; + + /* insert into list.*/ + if( !byr_val ) + byr_val = ue; + else { + int found_alias=0; + usg_elem **ue_curr, **ptr_if_no_alias=NULL; + ue_curr = &byr_val; + while (*ue_curr) { + if( RVAL(s,(*ue_curr)->idx) == RVAL(s,ue->idx)) { + /* push onto the alias list. */ + ue_curr = &((*ue_curr)->alias); + found_alias=1; + break; + } + if( !ptr_if_no_alias + && STRCASECMP(NAME(s,(*ue_curr)->idx),NAME(s,ue->idx)) > 0){ + ptr_if_no_alias = ue_curr; + } + ue_curr = &((*ue_curr)->next); + } + if (!found_alias && ptr_if_no_alias) + ue_curr = ptr_if_no_alias; + ue->next = *ue_curr; + *ue_curr = ue; + } + } + +#if 0 + if(1){ + printf("ORIGINAL:\n"); + for(i=0; i < s->optc;i++) + printf("%2d: %s\n",i,NAME(s,i)); + printf("SORTED:\n"); + ue = byr_val; + while(ue) { + usg_elem *ue2; + printf("%2d: %s\n",ue->idx,NAME(s,ue->idx)); + for(ue2=ue->alias; ue2; ue2=ue2->next) + printf(" +---> %2d: %s\n", ue2->idx, NAME(s,ue2->idx)); + ue = ue->next; + } + } +#endif + + /* Now build each row of output. */ + + /* first pass calculate how much room we need. */ + for (ue=byr_val; ue; ue=ue->next) { + usg_elem *ap; + int len=0; + int nshort=0,nlong=0; + + +#define CALC_LEN(i) do {\ + if(FLAGS(s,i) & IS_LONG) \ + len += (nlong++||nshort) ? 2+PRINTLEN(s,i) : PRINTLEN(s,i);\ + else\ + len += (nshort++||nlong)? 2+PRINTLEN(s,i) : PRINTLEN(s,i);\ + }while(0) + + if(!(FLAGS(s,ue->idx) & IS_LONG)) + CALC_LEN(ue->idx); + + /* do short aliases first.*/ + for(ap=ue->alias; ap; ap=ap->next){ + if(FLAGS(s,ap->idx) & IS_LONG) + continue; + CALC_LEN(ap->idx); + } + + if(FLAGS(s,ue->idx) & IS_LONG) + CALC_LEN(ue->idx); + + /* repeat the above loop, this time for long aliases. */ + for(ap=ue->alias; ap; ap=ap->next){ + if( !(FLAGS(s,ap->idx) & IS_LONG)) + continue; + CALC_LEN(ap->idx); + } + + if(len > maxlen[0]) + maxlen[0] = len; + + /* It's much easier to calculate length for description column!*/ + len = strlen(DESC(s,ue->idx)); + if(len > maxlen[1]) + maxlen[1] = len; + } + + /* Determine how much room we have, and how much we will allocate to each col. + * Do not address pathological cases. Output will just be ugly. */ + columns = get_cols() - 1; + if(maxlen[0] + maxlen[1] + indent*2 > columns ) { + /* col 0 gets whatever it wants. we'll wrap the desc col. */ + maxlen[1] = columns - (maxlen[0] + indent*2); + if(maxlen[1]< 14) /* 14 is arbitrary lower limit on desc width.*/ + maxlen[1]= INT_MAX; + } + desccol = maxlen[0] + indent*2; + +#define PRINT_SPACES(fp,n)\ + do{\ + int _n;\ + _n=(n);\ + while(_n-- > 0)\ + fputc(' ',(fp));\ + }while(0) + + + /* Second pass (same as above loop), this time we print. */ + /* Sloppy hack: We iterate twice. The first time we print short and long options. + The second time we print those lines that have ONLY long options. */ + while(print_run++ < 2) { + for (ue=byr_val; ue; ue=ue->next) { + usg_elem *ap; + int nwords=0,nchars=0,has_short=0; + +/* TODO: get has_short schtick to work */ + has_short = !(FLAGS(s,ue->idx)&IS_LONG); + for(ap=ue->alias; ap; ap=ap->next){ + if(!(FLAGS(s,ap->idx) & IS_LONG)){ + has_short=1; + break; + } + } + if( (print_run == 1 && !has_short) || + (print_run == 2 && has_short)) + continue; + + PRINT_SPACES(fp,indent);nchars+=indent; + + /* Print, adding a ", " between aliases. */ + #define PRINT_IT(i) do{\ + if(nwords++)\ + nchars+=fprintf(fp,", ");\ + nchars+=fprintf(fp,"%s",s->options[i].opt_fmt);\ + }while(0) + + if(!(FLAGS(s,ue->idx) & IS_LONG)) + PRINT_IT(ue->idx); + + /* print short aliases first.*/ + for(ap=ue->alias; ap; ap=ap->next){ + if(!(FLAGS(s,ap->idx) & IS_LONG)) + PRINT_IT(ap->idx); + } + + + if(FLAGS(s,ue->idx) & IS_LONG) + PRINT_IT(ue->idx); + + /* repeat the above loop, this time for long aliases. */ + for(ap=ue->alias; ap; ap=ap->next){ + if( FLAGS(s,ap->idx) & IS_LONG) + PRINT_IT(ap->idx); + } + + /* pad to desccol */ + PRINT_SPACES(fp, desccol - nchars); + + /* Print description, wrapped to maxlen[1] columns.*/ + if(1){ + const char * pstart; + pstart = DESC(s,ue->idx); + while(1){ + int n=0; + const char * lastws=NULL,*p; + p=pstart; + + while(*p && n < maxlen[1] && *p != '\n'){ + if(isspace(*p) || *p=='-') + lastws = p; + n++; + p++; + } + + if(!*p){ /* hit end of desc. done. */ + fprintf(fp,"%s\n",pstart); + break; + } + else if(*p == '\n'){ /* print everything up to here then wrap.*/ + fprintf(fp,"%.*s\n",n,pstart); + PRINT_SPACES(fp,desccol); + pstart = p+1; + continue; + } + else{ /* we hit the edge of the screen. wrap at space if possible.*/ + if( lastws){ + fprintf(fp,"%.*s\n",lastws-pstart,pstart); + pstart = lastws+1; + }else{ + fprintf(fp,"%.*s\n",n,pstart); + pstart = p+1; + } + PRINT_SPACES(fp,desccol); + continue; + } + } + } + } + }/* end while */ + free(store); + return 0; +} +#endif /* no scanopt_usage */ + + +static int +scanopt_err(s,opt_offset,is_short,err) + struct _scanopt_t * s; + int opt_offset; + int is_short; + int err; +{ + const char *optname=""; + char optchar[2]; + const optspec_t * opt=NULL; + + if ( opt_offset >= 0) + opt = s->options + opt_offset; + + if ( !s->no_err_msg ) { + + if( s->index > 0 && s->index < s->argc){ + if (is_short ) { + optchar[0] = s->argv[s->index][s->subscript]; + optchar[1] = '\0'; + optname = optchar; + }else { + optname = s->argv[s->index]; + } + } + + fprintf(stderr,"%s: ", s->argv[0]); + switch (err) { + case SCANOPT_ERR_ARG_NOT_ALLOWED: + fprintf(stderr,"option `%s' doesn't allow an argument\n",optname); + break; + case SCANOPT_ERR_ARG_NOT_FOUND: + fprintf(stderr,"option `%s' requires an an argument\n",optname); + break; + case SCANOPT_ERR_OPT_AMBIGUOUS: + fprintf(stderr,"option `%s' is ambiguous\n",optname); + break; + case SCANOPT_ERR_OPT_UNRECOGNIZED: + fprintf(stderr,"Unrecognized option -- `%s'\n",optname); + break; + default: + fprintf(stderr,"Unknown error=(%d)\n",err); + break; + } + } + return err; +} + + +/* Internal. Match str against the regex ^--([^=]+)(=(.*))? + * return 1 if *looks* like a long option. + * 'str' is the only input argument, the rest of the arguments are output only. + * optname will point to str + 2 + * + */ +static int +matchlongopt(str, optname ,optlen, arg, arglen) + char* str; + char** optname; + int* optlen; + char** arg; + int* arglen; +{ + char * p; + + *optname = *arg = (char*)0; + *optlen = *arglen = 0; + + /* Match regex /--./ */ + p = str; + if( p[0]!='-' || p[1]!='-' || !p[2]) + return 0; + + p += 2; + *optname = (char*)p; + + /* find the end of optname */ + while(*p && *p != '=') + ++p; + + *optlen = p - *optname; + + if (!*p) + /* an option with no '=...' part. */ + return 1; + + + /* We saw an '=' char. The rest of p is the arg.*/ + p++; + *arg = p; + while(*p) + ++p; + *arglen = p - *arg; + + return 1; +} + + +/* Internal. Look up long or short option by name. + * Long options must match a non-ambiguous prefix, or exact match. + * Short options must be exact. + * Return boolean true if found and no error. + * Error stored in err_code or zero if no error. */ +static int +find_opt (s, lookup_long, optstart, len, err_code, opt_offset) + struct _scanopt_t * s; + int lookup_long; + char * optstart; + int len; + int *err_code; + int* opt_offset; +{ + int nmatch=0,lastr_val=0,i; + *err_code = 0; + *opt_offset = -1; + + if (!optstart) + return 0; + + for(i=0; i < s->optc; i++) { + char* optname; + optname = (char*)(s->options[i].opt_fmt + (lookup_long?2:1)); + + if (lookup_long && (s->aux[i].flags & IS_LONG)) { + if (len > s->aux[i].namelen) + continue; + + if (strncmp(optname, optstart, len) == 0) { + nmatch++; + *opt_offset = i; + + /* exact match overrides all.*/ + if(len == s->aux[i].namelen){ + nmatch=1; + break; + } + + /* ambiguity is ok between aliases. */ + if(lastr_val && lastr_val == s->options[i].r_val) + nmatch--; + lastr_val = s->options[i].r_val; + } + } + else if ( !lookup_long && !(s->aux[i].flags&IS_LONG)) { + if (optname[0] == optstart[0]){ + nmatch++; + *opt_offset = i; + } + } + } + + if ( nmatch == 0 ) { + *err_code = SCANOPT_ERR_OPT_UNRECOGNIZED; + *opt_offset = -1; + } + else if ( nmatch > 1) { + *err_code = SCANOPT_ERR_OPT_AMBIGUOUS; + *opt_offset = -1; + } + + return *err_code ? 0 : 1; +} + + +int +scanopt (svoid, arg, optindex) + scanopt_t * svoid; + char ** arg; + int * optindex; +{ + char * optname=NULL, * optarg=NULL, *pstart; + int namelen=0, arglen=0; + int errcode=0, has_next; + const optspec_t * optp; + struct _scanopt_t* s; + struct _aux * auxp; + int is_short; + int opt_offset=-1; + + s = (struct _scanopt_t*)svoid; + + /* Normalize return-parameters. */ + SAFE_ASSIGN(arg,NULL); + SAFE_ASSIGN(optindex , s->index); + + if ( s->index >= s->argc ) + return 0; + + /* pstart always points to the start of our current scan. */ + pstart = s->argv[s->index] + s->subscript; + if ( !pstart ) + return 0; + + if ( s->subscript == 0 ) { + + /* test for exact match of "--" */ + if ( pstart[0]=='-' && pstart[1]=='-' && !pstart[2]) { + SAFE_ASSIGN(optindex,s->index+1); + INC_INDEX(s,1); + return 0; + } + + /* Match an opt. */ + if(matchlongopt(pstart,&optname,&namelen,&optarg,&arglen)) { + + /* it LOOKS like an opt, but is it one?! */ + if( !find_opt(s, 1, optname, namelen, &errcode,&opt_offset)){ + scanopt_err(s,opt_offset,0,errcode); + return errcode; + } + /* We handle this below. */ + is_short=0; + + /* Check for short opt. */ + }else if ( pstart[0] == '-' && pstart[1]) { + /* Pass through to below. */ + is_short=1; + s->subscript++; + pstart++; + } + + else { + /* It's not an option. We're done. */ + return 0; + } + } + + /* We have to re-check the subscript status because it + * may have changed above. */ + + if(s->subscript != 0){ + + /* we are somewhere in a run of short opts, + * e.g., at the 'z' in `tar -xzf` */ + + optname = pstart; + namelen = 1; + + if(!find_opt(s, 0, pstart, namelen, &errcode,&opt_offset)) { + return scanopt_err(s,opt_offset,1,errcode); + } + + optarg = pstart+1; + arglen = 0; + while(optarg[arglen]) + arglen++; + + if (arglen==0) + optarg=NULL; + } + + /* At this point, we have a long or short option matched at opt_offset into + * the s->options array (and corresponding aux array). + * A trailing argument is in {optarg,arglen}, if any. + */ + + /* Look ahead in argv[] to see if there is something + * that we can use as an argument (if needed). */ + has_next = s->index+1 < s->argc + && strcmp("--",s->argv[s->index+1]) != 0; + + optp = s->options + opt_offset; + auxp = s->aux + opt_offset; + + /* case: no args allowed */ + if ( auxp->flags & ARG_NONE) { + if ( optarg){ + scanopt_err(s,opt_offset,is_short,errcode=SCANOPT_ERR_ARG_NOT_ALLOWED); + INC_INDEX(s,1); + return errcode; + } + INC_INDEX(s,1); + return optp->r_val; + } + + /* case: required */ + if (auxp->flags & ARG_REQ) { + if ( !optarg && !has_next) + return scanopt_err(s,opt_offset,is_short,SCANOPT_ERR_ARG_NOT_FOUND); + + if (!optarg) { + /* Let the next argv element become the argument. */ + SAFE_ASSIGN(arg,s->argv[s->index+1]); + INC_INDEX(s,2); + }else{ + SAFE_ASSIGN(arg,(char*)optarg); + INC_INDEX(s,1); + } + return optp->r_val; + } + + /* case: optional */ + if (auxp->flags & ARG_OPT){ + SAFE_ASSIGN(arg,optarg); + INC_INDEX(s,1); + return optp->r_val; + } + + + /* Should not reach here. */ + return 0; +} + + +int +scanopt_destroy(svoid) + scanopt_t* svoid; +{ + struct _scanopt_t* s; + s = (struct _scanopt_t*)svoid; + if ( s ) { + if (s->aux) + free (s->aux); + free(s); + } + return 0; +} + + +/* vim:set tabstop=8 softtabstop=4 shiftwidth=4: */ diff --git a/scanopt.h b/scanopt.h new file mode 100644 index 0000000..b6a0638 --- /dev/null +++ b/scanopt.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2001, John W. Millaway + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SCANOPT_H +#define SCANOPT_H + +#include "flexdef.h" + + +#ifndef NO_SCANOPT_USAGE +/* Used by scanopt_usage for pretty-printing. */ +#ifdef HAVE_NCURSES_H +#include +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef PROTO +#define PROTO(args) args +#endif + +/* Error codes. */ +enum scanopt_err_t { + SCANOPT_ERR_OPT_UNRECOGNIZED = -1, /* Unrecognized option. */ + SCANOPT_ERR_OPT_AMBIGUOUS = -2, /* It matched more than one option name. */ + SCANOPT_ERR_ARG_NOT_FOUND= -3, /* The required arg was not found.*/ + SCANOPT_ERR_ARG_NOT_ALLOWED = -4 /* Option does not take an argument. */ +}; + + +/* flags passed to scanopt_init */ +enum scanopt_flag_t { + SCANOPT_NO_ERR_MSG = 0x01 /* Suppress printing to stderr. */ +}; + +/* Specification for a single option. */ +struct optspec_t +{ + const char * opt_fmt; /* e.g., "--foo=FILE", "-f FILE", "-n [NUM]" */ + int r_val; /* Value to be returned by scanopt_ex(). */ + const char* desc; /* Brief description of this option, or NULL. */ +}; +typedef struct optspec_t optspec_t; + + +/* Used internally by scanopt() to maintain state. */ +/* Never modify these value directly. */ +typedef void * scanopt_t; + + +/* Initializes scanner and checks option list for errors. + * Parameters: + * options - Array of options. + * argc - Same as passed to main(). + * argv - Same as passed to main(). First element is skipped. + * flags - Control behavior. + * Return: A malloc'd pointer . + */ +scanopt_t* scanopt_init PROTO(( const optspec_t* options, + int argc, char** argv, int flags )); + +/* Frees memory used by scanner. + * Always returns 0. */ +int scanopt_destroy PROTO((scanopt_t* scanner)); + +#ifndef NO_SCANOPT_USAGE +/* Prints a usage message based on contents of optlist. + * Parameters: + * scanner - The scanner, already initialized with scanopt_init(). + * fp - The file stream to write to. + * usage - Text to be prepended to option list. May be NULL. + * Return: Always returns 0 (zero). + */ +int scanopt_usage PROTO(( scanopt_t* scanner, FILE* fp, const char* usage)); +#endif + +/* Scans command-line options in argv[]. + * Parameters: + * scanner - The scanner, already initialized with scanopt_init(). + * optarg - Return argument, may be NULL. + * On success, it points to start of an argument. + * optindex - Return argument, may be NULL. + * On success or failure, it is the index of this option. + * If return is zero, then optindex is the NEXT valid option index. + * + * Return: > 0 on success. Return value is from optspec_t->rval. + * == 0 if at end of options. + * < 0 on error (return value is an error code). + * + */ +int scanopt PROTO(( scanopt_t * scanner, char ** optarg, int * optindex)); + +#ifdef __cplusplus +} +#endif +#endif + +/* vim:set tabstop=8 softtabstop=4 shiftwidth=4: */ -- cgit v1.2.1