// // Cforall Version 1.0.0 Copyright (C) 2022 University of Waterloo // // The contents of this file are covered under the licence agreement in the // file "LICENCE" distributed with Cforall. // // parseargs.cfa // implementation of arguments parsing (argc, argv) // // Author : Thierry Delisle // Created On : Wed Oct 12 15:28:01 2022 // Last Modified By : Peter A. Buhr // Last Modified On : Mon Jul 8 18:18:23 2024 // Update Count : 7 // #include "parseargs.hfa" #include #include #include #include #include #include extern "C" { #include #include struct FILE; extern FILE * stderr; extern FILE * stdout; extern int fileno( FILE *stream ); extern int fprintf ( FILE * stream, const char * format, ... ); extern long long int strtoll ( const char* str, char** endptr, int base ); extern unsigned long long int strtoull( const char* str, char** endptr, int base ); extern double strtod ( const char* str, char** endptr ); } #include "common.hfa" #include "limits.hfa" #pragma GCC visibility push( default ) extern int cfa_args_argc __attribute__(( weak )); extern char ** cfa_args_argv __attribute__(( weak )); extern char ** cfa_args_envp __attribute__(( weak )); forall([N]) static void usage( char * cmd, const array( cfa_option, N ) & options, const char * usage, FILE * out ) __attribute__ (( noreturn )); //----------------------------------------------------------------------------- // checking forall([N]) static void check_args( const array( cfa_option, N ) & options ) { for ( i; N ) { for ( j; N ) { if ( i == j ) continue; if ( options[i].short_name != '\0' && options[i].short_name == options[j].short_name ) abort( "Parse Args error: two options have short name '%c' (%zu & %zu)", options[i].short_name, i, j ); if (0 == strcmp( options[i].long_name, options[j].long_name )) abort( "Parse Args error: two options have long name '%s' (%zu & %zu)", options[i].long_name, i, j ); } } } //----------------------------------------------------------------------------- // Parsing args forall([opt_count]) { void parse_args( const array( cfa_option, opt_count ) & options, const char * usage, char ** & left ) { if ( 0p != &cfa_args_argc ) { parse_args( cfa_args_argc, cfa_args_argv, options, usage, left ); } else { char * temp[1] = { 0p }; parse_args(0, temp, options, usage, left ); } } void parse_args( int argc, char * argv[], const array( cfa_option, opt_count ) & options, const char * usage, char ** & left ) { check_args( options ); int maxv = 'h'; char optstring[(opt_count * 3) + 2] = { '\0' }; { int idx = 0; for ( i; opt_count ) { if ( options[i].short_name ) { maxv = max( options[i].short_name, maxv ); optstring[idx] = options[i].short_name; idx++; if ( (intptr_t)options[i].parse != (intptr_t)parse_settrue && ((intptr_t)options[i].parse) != ((intptr_t)parse_setfalse) ) { optstring[idx] = ':'; idx++; } } } optstring[idx+0] = 'h'; optstring[idx+1] = '\0'; } struct option optarr[opt_count + 2]; { int idx = 0; for ( i; opt_count ) { if ( options[i].long_name ) { // we don't have the mutable keyword here, which is really what we would want int & val_ref = (int &)(const int &)options[i].val; val_ref = (options[i].short_name != '\0') ? ((int)options[i].short_name) : ++maxv; optarr[idx].name = options[i].long_name; optarr[idx].flag = 0p; optarr[idx].val = options[i].val; if ( ((intptr_t)options[i].parse) == ((intptr_t)parse_settrue) || ((intptr_t)options[i].parse) == ((intptr_t)parse_setfalse) ) { optarr[idx].has_arg = no_argument; } else { optarr[idx].has_arg = required_argument; } idx++; } } optarr[idx+0].[name, has_arg, flag, val] = ["help", no_argument, 0, 'h']; optarr[idx+1].[name, has_arg, flag, val] = [0, no_argument, 0, 0]; } FILE * out = stderr; NEXT_ARG: for () { int idx = 0; int opt = getopt_long( argc, argv, optstring, optarr, &idx ); switch( opt ) { case -1: if ( &left != 0p ) left = argv + optind; return; case 'h': out = stdout; case '?': usage( argv[0], options, usage, out ); default: for ( i; opt_count ) { if ( opt == options[i].val ) { const char * arg = optarg ? optarg : ""; if ( arg[0] == '=' ) { arg++; } // work around for some weird bug void * variable = options[i].variable; bool (*parse_func)(const char *, void * ) = options[i].parse; bool success = parse_func( arg, variable ); if ( success ) continue NEXT_ARG; fprintf( out, "Argument '%s' for option %c could not be parsed\n\n", arg, (char)opt ); usage( argv[0], options, usage, out ); } } abort( "Internal parse arg error\n" ); } } } } static inline int next_newline( const char * str ) { int ret; const char * ptr = strstr( str, "\n" ); if ( ! ptr ) return MAX; /* paranoid */ verify( str <= ptr ); intptr_t low = (intptr_t)str; intptr_t hi = (intptr_t)ptr; ret = hi - low; return ret; } //----------------------------------------------------------------------------- // Print usage static void printopt( FILE * out, int width, int max, char sn, const char * ln, const char * help ) { // check how wide we should be printing // this includes all options and the help message int hwidth = max - (11 + width); if ( hwidth <= 0 ) hwidth = max; // check which pieces we have bool has_ln = ln && strcmp( "", ln ); bool has_help = help && strcmp( "", help ); // print the small name if present if ( sn != '\0') fprintf( out, " -%c", sn ); else fprintf( out, " " ); // print a comma if we have both short and long names if ( sn != '\0' && has_ln ) fprintf( out, ", " ); else fprintf( out, " " ); // print the long name if present if ( has_ln ) fprintf( out, "--%-*s", width, ln ); else if ( has_help ) fprintf( out, " %-*s", width, "" ); if ( has_help ) { // print the help // We need to wrap at the max width, and also indent newlines so everything is nice and pretty // for each line to print for () { //find out if there is a newline int nextnl = next_newline( help ); int real = min( min( strlen( help ), hwidth ), nextnl ); fprintf( out, " %.*s", real, help ); // printf( "%d %d\n", real, nextnl ); help += real; if ( nextnl == real ) help++; if ('\0' == *help ) break; fprintf( out, "\n%*s", width + 8, "" ); } } fprintf( out, "\n" ); } void print_args_usage( cfa_option options[], const size_t opt_count, const char * usage, bool error ) __attribute__ ((noreturn )) { const array( cfa_option, opt_count ) & arr = (const array( cfa_option, opt_count ) &) *options; usage( cfa_args_argv[0], arr, usage, error ? stderr : stdout ); } void print_args_usage( int , char * argv[], cfa_option options[], const size_t opt_count, const char * usage, bool error ) __attribute__ (( noreturn )) { const array( cfa_option, opt_count ) & arr = (const array( cfa_option, opt_count ) &) *options; usage( argv[0], arr, usage, error ? stderr : stdout ); } forall( [N] ) { void print_args_usage( const array(cfa_option, N ) & options, const char * usage, bool error ) { usage( cfa_args_argv[0], options, usage, error ? stderr : stdout ); } void print_args_usage( int argc, char * argv[], const array( cfa_option, N ) & options, const char * usage, bool error ) { usage( argv[0], options, usage, error ? stderr : stdout ); } } forall([N]) static void usage( char * cmd, const array( cfa_option, N ) & options, const char * help, FILE * out ) __attribute__(( noreturn )) { int width = 0; { for ( i; N ) { if ( options[i].long_name ) { int w = strlen( options[i].long_name ); if ( w > width ) width = w; } } } int max_width = 1_000_000; int outfd = fileno( out ); if ( isatty( outfd ) ) { struct winsize size; int ret = ioctl( outfd, TIOCGWINSZ, &size ); if ( ret < 0 ) abort( "ioctl error: (%d) %s\n", (int)errno, strerror( errno) ); max_width = size.ws_col; } fprintf( out, "Usage:\n %s %s\n", cmd, help ); for ( i; N ) { printopt( out, width, max_width, options[i].short_name, options[i].long_name, options[i].help ); } fprintf( out, " -%c, --%-*s %s\n", 'h', width, "help", "print this help message" ); exit( out == stdout ? 0 : 1 ); } //----------------------------------------------------------------------------- // Typed argument parsing bool parse_yesno( const char * arg, bool & value ) { if ( strcmp( arg, "yes" ) == 0 ) { value = true; return true; } if ( strcmp( arg, "Y" ) == 0 ) { value = true; return true; } if ( strcmp( arg, "y" ) == 0 ) { value = true; return true; } if ( strcmp( arg, "no" ) == 0 ) { value = false; return true; } if ( strcmp( arg, "N" ) == 0 ) { value = false; return true; } if ( strcmp( arg, "n" ) == 0 ) { value = false; return true; } return false; } bool parse_truefalse( const char * arg, bool & value ) { if ( strcmp( arg, "true" ) == 0 ) { value = true; return true; } if ( strcmp( arg, "false" ) == 0 ) { value = false; return true; } return false; } bool parse_settrue ( const char *, bool & value ) { value = true; return true; } bool parse_setfalse( const char *, bool & value ) { value = false; return true; } bool parse( const char * arg, const char * & value ) { value = arg; return true; } bool parse( const char * arg, int & value ) { char * end; errno = 0; long long int r = strtoll( arg, &end, 0 ); if ( errno ) return false; if (*end != '\0') return false; if ( r > (int)MAX ) return false; if ( r < (int)MIN ) return false; value = r; return true; } static unsigned long long int strict_strtoull( const char * arg, int base ) { errno = 0; { const char * in = arg; for () { if ( '\0' == *in ) { errno = EINVAL; return 0; } if ( ! isspace(*in )) break; in++; } if ( ! isdigit(*in )) { errno = EINVAL; return 0; } } *char end; unsigned long long int r = strtoull( arg, &end, base ); if (*end != '\0') errno = EINVAL; if ( errno ) return 0; return r; } bool parse( const char * arg, unsigned & value ) { unsigned long long int r = strict_strtoull( arg, 0 ); if ( errno ) return false; if ( r > (unsigned)MAX ) return false; value = r; return true; } bool parse( const char * arg, unsigned long & value ) { unsigned long long int r = strict_strtoull( arg, 0 ); if ( errno ) return false; if ( r > (unsigned long)MAX ) return false; value = r; return true; } bool parse( const char * arg, unsigned long long & value ) { unsigned long long int r = strict_strtoull( arg, 0 ); if ( errno ) return false; if ( r > (unsigned long long)MAX ) return false; value = r; return true; } bool parse( const char * arg, double & value ) { char * end; double r = strtod( arg, &end ); if ( *end != '\0') return false; value = r; return true; }