Introduction
The get_arguments function was written to provide argument parsing code for multiple platforms. Also, unlike some other common argument parsing functions, the get_arguments code can be used under the terms of the MIT license.
The get_arguments function allows both short and long optional switches, and will work with the ASCII, UTF-8, and Unicode character sets.
Both single character and long optional argument switch names are allowed.
Optional arguments and positional arguments can be interspersed. The benefit of allowing
interspersed optional and positional arguments comes at the cost of requiring that an optional argument either does, or does not, take an additional parameter, but not both.
Also, optional argument names cannot start with a digit character, a period character, or a plus sign. This simplified parsing as a dash followed by one of these characters could also be the start of a numeric positional argument.
Background
The C, and C++, 'main' function is passed two argument parameters. One is an integer parameter named 'argc', and the other is an array of pointers to the string arguments. String arguments are supplied by the user on the command line when the program is run. The first string argument is the path and file name of the program being run.
The get_arguments function in this code accepts optional parameters and positional parameters. Optional parameters are specified using switches that start with a dash character. A single dash character accepts a single character switch name. A double dash character takes an optional switch name that is more than one character. An optional switch name might by the complete optional parameter, or it might be followed by a required string.
Example:
ProgramName -x --name Fred --age 57
The example sets a program value when the '-x' switch is specified and sets the name Fred and an age value.
Positional arguments are not usually preceded by a dash character. A positional argument is only preceded by a dash character, which is a minus sign, if the parameter is a number. To avoid the parsing ambiguity, switch names never start with a digit character. Positional parameters always must be specified in a specified order.
Adding two positional arguments to the previous program line leads to:
ProgramName positional1 someotherargument -x --name Fred --age 57
The code
The get_arguments.h header file shows the definitions and documents the arguments and use of the 'get_arguments' function. File platform_os.h, which is included in get_arguments.h to define 'TCHAR', is shown further below.
The get_arguments function arguments are in the get_arguments header file and are repeated here.
Argument Name | | Description |
argc | | The number of arguments.
|
argv | | An array of pointers to argument strings.
|
argument_option_array_ptr | | A pointer to an array of ArgOption_t structures that specifies that attributes of each optional argument. An example declaration for the array might be:
static const ArgOption_t argument_option_array[] = { { 'x', NULL, OPTION_NO_PARAMETER, }, { 'f', NULL, OPTION_REQUIRES_PARAMETER, }, { 'b', "build", OPTION_REQUIRES_PARAMETER, }, { 'h', "help", OPTION_NO_PARAMETER, }, { 0, NULL, OPTION_NO_PARAMETER } };
This would allow optional arguments of the form:
-x -f <somestring> -b <somestring> --build <somestring> -h --help
A string must be supplied after the -s parameter because OPTION_REQUIRES_PARAMETER is used.
The final line must always be supplied as written to terminate the data.
Either
a short argument name, a long argument name, or both must be specified.
If only a long name is specified, the first field of the ArgOption_t
structure must be set to an out-of-band integer value, which for either
ASCII or Unicode character sets can be any value above 0x011FFFF. |
arg_index_ptr | | A pointer to an 'int' value that is used to index into the argv[] array.
The value pointed to by arg_index_ptr specifies either the index of a
positional argument or the index of the required parameter for an
optional argument that has the OPTION_REQUIRES_PARAMETER attribute. |
| | |
| | |
| | |
| | |
| | |
#ifndef GET_ARGUMENTS_H
#define GET_ARGUMENTS_H
#include "platform_os.h"
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
typedef enum
{
ARG_ERROR_INVALID_SWITCH = -2,
ARG_ERROR_MISSING_SWITCH_ARGUMENT = -1,
ARG_NO_MORE_ARGUMENTS = 0,
ARG_POSITIONAL_ARGUMENT = 0x7FFFFFFF
} ArgOptionChar_t;
typedef enum
{
OPTION_NO_PARAMETER,
OPTION_REQUIRES_PARAMETER
} ArgOptionParameter_t;
typedef struct
{
int c;
TCHAR * long_name;
ArgOptionParameter_t requires_param;
} ArgOption_t;
int get_arguments(int argc,
TCHAR *argv[],
const ArgOption_t * argument_option_array_ptr,
int * arg_index_ptr);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* GET_ARGUMENTS_H */
File get_arguments.c is:
#include "get_arguments.h"
int get_arguments(int argc,
TCHAR *argv[],
const ArgOption_t * argument_option_array_ptr,
int * arg_index_ptr)
{
int arg_length = 0;
int option_param_type = OPTION_NO_PARAMETER;
const ArgOption_t * arg_option_ptr = argument_option_array_ptr;
TCHAR * argument_ptr = NULL;
int option_character = ARG_NO_MORE_ARGUMENTS;
++(*arg_index_ptr);
if (*arg_index_ptr < argc)
{
argument_ptr = argv[*arg_index_ptr];
arg_length = _tcslen(argument_ptr);
if (argument_ptr[0] == _T('-'))
{
if (arg_length > 1)
{
if ((_istdigit(argument_ptr[1]))
|| (argument_ptr[1] == _T('.'))
|| (argument_ptr[1] == _T('+')))
{
option_character = ARG_POSITIONAL_ARGUMENT;
}
else
{
if ((arg_length > 2) && (argument_ptr[1] == _T('-')))
{
argument_ptr += 2;
while (arg_option_ptr->c != _T('\0'))
{
if ((arg_option_ptr->long_name != NULL)
&& (_tcscmp(arg_option_ptr->long_name, argument_ptr) == 0))
{
option_character = arg_option_ptr->c;
option_param_type = arg_option_ptr->requires_param;
break;
}
++arg_option_ptr;
}
}
else
{
++argument_ptr;
while (arg_option_ptr->c != _T('\0'))
{
if (argument_ptr[0] == arg_option_ptr->c)
{
option_character = arg_option_ptr->c;
option_param_type = arg_option_ptr->requires_param;
break;
}
++arg_option_ptr;
}
}
if (option_character != ARG_NO_MORE_ARGUMENTS)
{
if (option_param_type == OPTION_REQUIRES_PARAMETER)
{
++(*arg_index_ptr);
if (*arg_index_ptr >= argc)
{
--(*arg_index_ptr);
option_character = ARG_ERROR_MISSING_SWITCH_ARGUMENT;
}
}
}
else
{
option_character = ARG_ERROR_INVALID_SWITCH;
}
}
}
else if (arg_length == 1)
{
option_character = ARG_ERROR_MISSING_SWITCH_ARGUMENT;
}
else
{
option_character = ARG_POSITIONAL_ARGUMENT;
}
}
else
{
option_character = ARG_POSITIONAL_ARGUMENT;
}
}
else
{
option_character = ARG_NO_MORE_ARGUMENTS;
}
return option_character;
}
The platform_os.h header file.
The platform_os.h header file is included by file get_arguments.h, and allows running the code on Windows and Linux. I tested on Linux a while ago, but have not tested recently, so the header file '#include's for the C library files might need tweaking. I will update this article when I test on Linux again. This works fine on Windows with either ASCII builds or Unicode builds.
#ifndef PLATFORM_OS_H
#define PLATFORM_OS_H
#ifdef WIN32
#include <tchar.h>
#ifdef UNICODE
#include <wctype.h>
#else
#include <ctype.h>
#endif /* UNICODE */
#endif /* WIN32 */
#ifdef __linux__
typedef char TCHAR;
#define SET_STDOUT_MODE
#define _T(X) X
#define _tmain main
#define _tprintf printf
#define _istdigit isdigit
#define _tcstol strtol
#define _tcstod strtod
#endif /* __linux__ */
#define FALSE (0)
#define TRUE (1)
typedef int BOOL;
#endif /* PLATFORM_OS_H */
Why I wrote the get_arguments function
I found myself writing a lot of C and C++ console programs, and I realized that most of the time the parsing code was similar to other programs. To save time, I wrote a code generator in Python. I found other parsing functions encumbered by their license or their behavior wasn't what I wanted. In particular, I wanted to be able to intersperse optional and positional arguments. I also wanted code that ran on multiple platforms with multiple character sets.
Unlike the example in the get_argument.h header file, program ParseExample.c defines the options array using the _T() macro for both characters and strings to allow compiling with different character sets.
About program ParseExample.c
File ParseExample.c demonstrates one possible use of the 'get_arguments' function.
ParseExample.c was designed to have a single return point, which greatly aids debugging as only a single breakpoint need to be set the first time the program is debugged.
To avoid either excessive indenting or using a 'goto' keyword for errors, while still allowing a single return, ParseExample uses the following useful construct. A non-zero status value indicates an error. An error results in breaking out of the do-while statement, which because of the "while (FALSE);" at the end is not a loop. This is a very useful construct.
do
{
[some code here]
if (status != 0)
{
break;
}
[More code here]
if (status != 0)
{
break;
}
[Even more code here]
if (status != 0)
{
break;
}
}
while (FALSE);
[More code here]
Program ParseExample.c source code
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef __linux__
#include <unistd.h>
#endif
#include "get_arguments.h"
#include "platform_os.h"
typedef enum
{
OPTINT0 = 1114112
} OptChar_t;
typedef struct
{
TCHAR * stringVar;
int intVar;
float floatVar;
double doubleVar;
TCHAR * optstringVar;
int optIntVar;
float optFloatVar;
double optDoubleVar;
BOOL optBoolVar;
BOOL optAnotherBoolVar;
} ProgramArgument_T;
int executeParseExample(ProgramArgument_T * program_args_ptr);
void displayUsage();
int _tmain(int argc, TCHAR* argv[])
{
int status = 0;
int arg_index = 0;
int positional_arg_index = 0;
int arg_result = 0;
TCHAR * stop_ptr = NULL;
ProgramArgument_T program_args;
static const ArgOption_t argument_option_array[] =
{
{ _T('s'), NULL, OPTION_REQUIRES_PARAMETER, },
{ _T('i'), NULL, OPTION_REQUIRES_PARAMETER, },
{ _T('f'), NULL, OPTION_REQUIRES_PARAMETER, },
{ _T('d'), NULL, OPTION_REQUIRES_PARAMETER, },
{ _T('b'), _T("build"), OPTION_NO_PARAMETER, },
{ OPTINT0, _T("another"), OPTION_NO_PARAMETER, },
{ _T('h'), _T("help"), OPTION_NO_PARAMETER },
{ 0, NULL, OPTION_NO_PARAMETER }
};
do
{
if (argc < 2)
{
_tprintf(_T("Program ParseExample [version 1.1 TODO:]\n"));
_tprintf(_T("Copyright (C) 2013 William Hallahan.\n\n"));
_tprintf(_T("For help, enter:\n\n"));
_tprintf(_T(" ParseExample -h\n\n"));
break;
}
program_args.stringVar = _T("");
program_args.intVar = 0;
program_args.floatVar = 0.0F;
program_args.doubleVar = 0.0;
program_args.optstringVar = _T("");
program_args.optIntVar = 0;
program_args.optFloatVar = 0.0F;
program_args.optDoubleVar = 0.0;
program_args.optBoolVar = FALSE;
program_args.optAnotherBoolVar = TRUE;
while ((status == 0)
&& ((arg_result = get_arguments(argc,
argv,
&argument_option_array[0],
&arg_index)) > ARG_NO_MORE_ARGUMENTS))
{
if (arg_result != ARG_POSITIONAL_ARGUMENT)
{
switch (arg_result)
{
case 's':
{
program_args.optstringVar = argv[arg_index];
}
break;
case 'i':
{
program_args.optIntVar = _tcstol(argv[arg_index], &stop_ptr, 10);
if (_tcslen(stop_ptr) > 0)
{
_tprintf(_T("Error in argument %s.\n"), argv[arg_index]);
status = -1;
}
}
break;
case 'f':
{
program_args.optFloatVar = (float)_tcstod(argv[arg_index], &stop_ptr);
if (_tcslen(stop_ptr) > 0)
{
_tprintf(_T("Error in argument %s.\n"), argv[arg_index]);
status = -1;
}
}
break;
case 'd':
{
program_args.optDoubleVar = _tcstod(argv[arg_index], &stop_ptr);
if (_tcslen(stop_ptr) > 0)
{
_tprintf(_T("Error in argument %s.\n"), argv[arg_index]);
status = -1;
}
}
break;
case 'b':
{
program_args.optBoolVar = TRUE;
}
break;
case OPTINT0:
{
program_args.optAnotherBoolVar = FALSE;
}
break;
case 'h':
{
displayUsage();
status = 1;
}
break;
default:
{
_tprintf(_T("Program error. Contact support.\n"));
status = 1;
}
break;
}
}
else
{
switch (positional_arg_index)
{
case 0:
{
program_args.stringVar = argv[arg_index];
}
break;
case 1:
{
program_args.intVar = _tcstol(argv[arg_index], &stop_ptr, 10);
if (_tcslen(stop_ptr) > 0)
{
_tprintf(_T("Error in argument %s.\n"), argv[arg_index]);
status = -1;
}
}
break;
case 2:
{
program_args.floatVar = (float)_tcstod(argv[arg_index], &stop_ptr);
if (_tcslen(stop_ptr) > 0)
{
_tprintf(_T("Error in argument %s.\n"), argv[arg_index]);
status = -1;
}
}
break;
case 3:
{
program_args.doubleVar = _tcstod(argv[arg_index], &stop_ptr);
if (_tcslen(stop_ptr) > 0)
{
_tprintf(_T("Error in argument %s.\n"), argv[arg_index]);
status = -1;
}
}
break;
default:
{
_tprintf(_T("Too many positional arguments atarting at %s.\n"), argv[arg_index]);
status = -1;
break;
}
}
++positional_arg_index;
}
}
if (status != 0)
{
break;
}
else if (arg_result == ARG_ERROR_MISSING_SWITCH_ARGUMENT)
{
_tprintf(_T("Error - missing switch argument for switch %s.\n"), argv[arg_index]);
status = -1;
break;
}
else if (arg_result == ARG_ERROR_INVALID_SWITCH)
{
_tprintf(_T("Invalid switch argument %s.\n"), argv[arg_index]);
status = -1;
break;
}
else if (positional_arg_index < 4)
{
_tprintf(_T("Too few arguments.\n"));
status = -1;
break;
}
else
{
status = executeParseExample(&program_args);
}
}
while (FALSE);
return status;
}
int executeParseExample(ProgramArgument_T * program_args_ptr)
{
_tprintf(_T("TCHAR * stringVar = %s\n"), program_args_ptr->stringVar);
_tprintf(_T("int intVar = %d\n"), program_args_ptr->intVar);
_tprintf(_T("float floatVar = %f\n"), program_args_ptr->floatVar);
_tprintf(_T("double doubleVar = %f\n"), program_args_ptr->doubleVar);
_tprintf(_T("TCHAR * optstringVar = %s\n"), program_args_ptr->optstringVar);
_tprintf(_T("int optIntVar = %d\n"), program_args_ptr->optIntVar);
_tprintf(_T("float optFloatVar = %f\n"), program_args_ptr->optFloatVar);
_tprintf(_T("double optDoubleVar = %f\n"), program_args_ptr->optDoubleVar);
_tprintf(_T("BOOL optBoolVar = %d\n"), program_args_ptr->optBoolVar);
_tprintf(_T("BOOL optAnotherBoolVar = %d\n"), program_args_ptr->optAnotherBoolVar);
return 0;
}
void displayUsage()
{
_tprintf(_T("\n"));
_tprintf(_T("Program ParseExample\n"));
_tprintf(_T("Copyright (c) 2013, William H.\n\n"));
_tprintf(_T("This program demonstrates the use of the get_arguments function.\n\n"));
_tprintf(_T("Usage:\n\n"));
_tprintf(_T(" ParseExample <stringVar> <intVar> <floatVar> <doubleVar> [-s optstringVar] [-i optIntVar] [-f optFloatVar] [-d optDoubleVar] [-b, --build] [--another]\n\n"));
_tprintf(_T("Positional arguments:\n\n"));
_tprintf(_T("stringVar String argument\n"));
_tprintf(_T("intVar Integer argument\n"));
_tprintf(_T("floatVar Floating point argument\n"));
_tprintf(_T("doubleVar Double precision floating point argument\n"));
_tprintf(_T("\nOptional arguments:\n\n"));
_tprintf(_T("-s optstringVar String parameter optstringVar\n"));
_tprintf(_T("-i optIntVar Integer parameter optIntVar\n"));
_tprintf(_T("-f optFloatVar Floating point parameter optFloatVar\n"));
_tprintf(_T("-d optDoubleVar Double precision floating point parameter optDoubleVar TODO:\n"));
_tprintf(_T("-b, --build If specified, optBoolVar becomes TRUE.\n"));
_tprintf(_T("--another If specified, optAnotherBoolVar becomes FALSE.\n"));
_tprintf(_T("-h, --help Display program help.\n\n"));
}
History
Oct. 11, 2013 - Posted the first version of the code.
Oct. 11, 2013 - Updated article and code.