%--------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %--------------------------------------------------% % Copyright (C) 1994-1999,2001-2007, 2011 The University of Melbourne. % Copyright (C) 2014-2018,2020 The Mercury team. % This file is distributed under the terms specified in COPYING.LIB. %--------------------------------------------------% % % File: getopt.m. % Authors: fjh, zs. % Stability: medium. % % This module defines predicates that parse command line options. % % These predicates allow both short (single-character) options, % which are preceded on command lines with a single dash, and GNU-style % long options, which are preceded on command lines with a double dash. % An argument starting with a single dash can specify more than one % short option, so that e.g. `-cde' is equivalent to `-c -d -e', while % each long option name must be in an argument of its own. % % The predicates in this module support the GNU extension of recognizing % options anywhere in the command line, not just at its start. % % To use this module: % % - You must provide an `option' type which is an enumeration of % all your different options. % - You must provide predicates `short_option(Char, Option)' and % `long_option(String, Option)' which convert the short and long names % respectively for the option to this enumeration type. % (An option can have as many names as you like, long or short.) % - You must provide a predicate `option_default(Option, OptionData)' % which specifies both the type and the default value for every option. % % You may optionally provide a predicate `special_handler(Option, SpecialData, % OptionTable, MaybeOptionTable)' for handling special option types. % (See below.) % % We support the following "simple" option types: % % - bool % - int % - string % - maybe_int (which have a value of `no' or `yes(int)') % - maybe_string (which have a value of `no' or `yes(string)') % % We also support one "accumulating" option type: % % - accumulating (which accumulates a list of strings) % % And the following "special" option types: % % - special % - bool_special % - int_special % - string_special % - maybe_string_special % - file_special (in the predicate variants that do I/O; see below) % % For the "simple" option types, if there are multiple occurrences of the same % option on the command line, then the last (right-most) occurrence will take % precedence. For "accumulating" options, multiple occurrences will be % appended together into a list. % % With the exception of file_special, the "special" option types are handled % by a special option handler (see `special_handler' below), which may perform % arbitrary modifications to the option_table. For example, an option which % is not yet implemented could be handled by a special handler which produces % an error report, or an option which is a synonym for a set of more % "primitive" options could be handled by a special handler which sets those % "primitive" options. % % It is an error to use a "special" option (other than file_special) % for which there is no handler, or for which the handler fails. % % The file_special option type requires no handler, and is implemented % entirely by this module. It always takes a single argument, a file name. % Its handling always consists of % % - reading the named file, % - converting its contents into a sequence of words separated by white space, % and % - interpreting those words as options in the usual manner. % % The reading of the file obviously requires doing I/O, which means that % only the predicate variants that take an I/O state pair of arguments % support file_special options. If a call to a predicate variant that % does not take a pair of I/O states does nevertheless specify a file_special % option, that predicate will report an error when processing a command line % that contains that option. % % Boolean (i.e. bool or bool_special), maybe_int, maybe_string and % accumulating options can be negated. Negating an accumulating option % empties the accumulated list of strings. Single-character options % can be negated by following them with another `-', e.g. `-x-' will negate % the `-x' option. Long options can be negated by preceding them with `--no-', % e.g. `--no-foo' will negate the `--foo' option. % % Note that arguments following an option may be separated from the option by % either whitespace or the equals character `=', so that e.g. `--foo 3' and % `--foo=3' both specify the option `--foo' with the integer argument `3'. % % If the argument `--' is encountered on the command line, then option % processing will immediately terminate, without processing any remaining % arguments. This is sometimes needed to tell a program to treat strings % that start with a dash as non-option arguments. % %--------------------------------------------------% %--------------------------------------------------% :- module getopt. :- interface. :- import_module bool. :- import_module char. :- import_module io. :- import_module list. :- import_module map. :- import_module maybe. :- import_module set. %--------------------------------------------------% :- type short_option(OptionType) == (pred(char, OptionType)). :- inst short_option == (pred(in, out) is semidet). :- type long_option(OptionType) == (pred(string, OptionType)). :- inst long_option == (pred(in, out) is semidet). :- type option_default_value(OptionType) == (pred(OptionType, option_data)). :- inst option_default_value_nondet == (pred(out, out) is nondet). :- inst option_default_value_multi == (pred(out, out) is multi). :- type special_handler(OptionType) == (pred(OptionType, special_data, option_table(OptionType), maybe_option_table(OptionType))). :- inst special_handler == (pred(in, in, in, out) is semidet). % The predicates below that process options, namely % % - process_options % - process_options_io % - process_options_track % - process_options_track_io % % all take an argument of the option_ops type to tell them % % - what the default value of each option is; % - what the short and long names of the options are; % (see the comment at the top for a description of the distinction), and % - if there are any special options, how they should be handled. % % The job of the option_ops type is to hold the three or four predicates % used to categorize a set of options. Their interfaces should be % like these: % % % True if the character names a valid single-character short option. % % % :- pred short_option(char::in, option::out) is semidet. % % % True if the string names a valid long option. % % % :- pred long_option(string::in, option::out) is semidet. % % % Nondeterministically returns all the options with their % % corresponding types and default values. % % % :- pred option_default(option::out, option_data::out) is multi. % % % This predicate is invoked whenever getopt finds an option % % designated as special (by either a short or long name), % % with special_data holding the argument of the option (if any). % % The predicate can change the option table in arbitrary ways % % in the course of handling the option, or it can return % % an error message. % % % % The canonical examples of special options are -O options setting % % optimization levels in compilers, which set many other options % % at once. % % % :- pred special_handler(option::in, special_data::in, % option_table::in, maybe_option_table(_)::out) is semidet. % % The four function symbols in the option_ops type differ in % % - whether they contain a special_handler or not, and % - whether the determinism of option_default is nondet or multi. % :- type option_ops(OptionType) ---> option_ops( short_option(OptionType), long_option(OptionType), option_default_value(OptionType) ) ; option_ops( short_option(OptionType), long_option(OptionType), option_default_value(OptionType), special_handler(OptionType) ) ; option_ops_multi( short_option(OptionType), long_option(OptionType), option_default_value(OptionType) ) ; option_ops_multi( short_option(OptionType), long_option(OptionType), option_default_value(OptionType), special_handler(OptionType) ). :- inst option_ops for option_ops/1 ---> option_ops( short_option, long_option, option_default_value_nondet ) ; option_ops( short_option, long_option, option_default_value_nondet, special_handler ) ; option_ops_multi( short_option, long_option, option_default_value_multi ) ; option_ops_multi( short_option, long_option, option_default_value_multi, special_handler ). %--------------------------------------------------% :- type special_handler_track(OptionType) == (pred(OptionType, special_data, option_table(OptionType), maybe_option_table(OptionType), set(OptionType))). :- inst special_handler_track == (pred(in, in, in, out, out) is semidet). % A version of the option_ops type for the process_options_track % predicate and its process_options_track_io variant. % Unlike the option_ops type, it does not contain a predicate % for setting the initial default values of options, since % process_options_track expects that to be done separately. % :- type option_ops_track(OptionType) ---> option_ops_track( short_option(OptionType), long_option(OptionType), special_handler_track(OptionType) ). :- inst option_ops_track for option_ops_track/1 ---> option_ops_track( short_option, long_option, special_handler_track ). %--------------------------------------------------% :- type user_data_handler(OptionType, UserDataType) == (pred(OptionType, special_data, option_table(OptionType), maybe_option_table(OptionType), UserDataType, UserDataType)). :- inst user_data_handler == (pred(in, in, in, out, in, out) is semidet). % A version of the option_ops type for the process_options_userdata % predicate and its process_options_userdata_io variant. % :- type option_ops_userdata(OptionType, UserDataType) ---> option_ops_userdata( short_option(OptionType), long_option(OptionType), user_data_handler(OptionType, UserDataType) ). :- inst option_ops_userdata for option_ops_userdata/2 ---> option_ops_userdata( short_option, long_option, user_data_handler ). %--------------------------------------------------% :- type option_data ---> bool(bool) ; int(int) ; string(string) ; maybe_int(maybe(int)) ; maybe_string(maybe(string)) ; accumulating(list(string)) ; special ; bool_special ; int_special ; string_special ; maybe_string_special ; file_special. :- type special_data ---> none ; bool(bool) ; int(int) ; string(string) ; maybe_string(maybe(string)). :- type option_table(OptionType) == map(OptionType, option_data). %--------------------------------------------------% :- type maybe_option_table(OptionType) ---> ok(option_table(OptionType)) ; error(string). :- type maybe_option_table_se(OptionType) ---> ok(option_table(OptionType)) ; error(option_error(OptionType)). %--------------------------------------------------% :- type option_error(OptionType) ---> unrecognized_option(string) % An option that is not recognized appeared on the command line. % The argument gives the option as it appeared on the command line. ; option_error(OptionType, string, option_error_reason). % An error occurred with a specific option. The first argument % identifies the option enumeration value; the second identifies % the string that appeared on the command line for that option; % the third argument describes the nature of the error with that % option. :- type option_error_reason ---> unknown_type % No type for this option has been specified in the % `option_default'/2 predicate. ; requires_argument % The option requires an argument but it occurred on the command % line without one. ; does_not_allow_argument(string) % The option does not allow an argument but it was provided with % one on the command line. % The argument gives the contents of the argument position on the % command line. ; cannot_negate % The option cannot be negated but its negated form appeared on the % command line. ; special_handler_failed % The special option handler predicate for the option failed. ; special_handler_missing % A special option handler predicate was not provided % for the option. ; special_handler_error(string) % The special option handler predicate for the option returned an % error. % The argument is a string describing the error. ; requires_numeric_argument(string) % The option requires a numeric argument but it occurred on the % command line with a non-numeric argument. % The argument gives the contents of the argument position on the % command line. ; file_special_but_no_io(string) % The option is a file_special option whose argument is the file % named by the first argument, but the user has not given the % predicate access to the I/O state. ; file_special_cannot_open(string, io.error) % The option is a file_special option whose argument is the file % named by the first argument. % Attempting to open this file resulted in the I/O error given % by the second argument. ; file_special_cannot_read(string, io.error) % The option is a file_special option whose argument is the file % named by the first argument. % Attempting to read from this file resulted in the I/O error given % by the second argument. ; file_special_contains_non_option_args(string) % The option is a file_special option whose argument is the file % named by the argument. This file contained some non-option % arguments. ; file_special_circular_inclusion(string). % The option is a file_special option whose argument is the file % named by the argument. This file contained either a direct % or an indirect reference to an option that called for its % inclusion, which, if followed, would have resulted in % an infinite loop of inclusions. %--------------------------------------------------% % process_options(OptionOps, Args, NonOptionArgs, Result): % process_options(OptionOps, Args, OptionArgs, NonOptionArgs, Result): % process_options_io(OptionOps, Args, NonOptionArgs, % Result, !IO): % process_options_io(OptionOps, Args, OptionArgs, NonOptionArgs, % Result, !IO): % % These four predicates do effectively the same job, differing % from each other in two minor ways. % % The job they all do is scanning through 'Args' looking for options. % The various fields of the OptionOps structure specify the names % (both short and long) of the options to look for, as well as their % default values, and possibly the handler for the special options. % The structure of the `OptionOps' argument is documented above, % at the definition of the option_ops type. % % All these predicates place all the non-option arguments in % 'NonOptionArgs', and the predicates that have an `OptionArgs' argument % place the option arguments there. (While some callers will want % the arguments contain the options, other callers will not, considering % that the only information they want from them is that contained in % the option table.) % % If they find a problem, such as an unknown option name, an option % being given an argument of the wrong type, or the failure of the handler % for a special option, all the predicates will put into `Result' % an `error' wrapped around an error code. That error code can be turned % into an error message using the option_error_to_string function below. % % If they do not find a problem, all these predicates will place into % `Result' an `ok' wrapped around an option table, which maps each option % to its final value. Unless updated by an option in `Args', this will be % its default value. % % The predicate versions whose names end in `io' take a pair of I/O state % arguments. This is so that they can handle file_special options, which % require reading a named file, and treating the file's contents as % specifying additional options. The predicate versions whose names % do not end in `io' cannot do I/O, and will report an error if they % encounter a file_special option. % :- pred process_options(option_ops(OptionType)::in(option_ops), list(string)::in, list(string)::out, maybe_option_table_se(OptionType)::out) is det. :- pred process_options(option_ops(OptionType)::in(option_ops), list(string)::in, list(string)::out, list(string)::out, maybe_option_table_se(OptionType)::out) is det. :- pred process_options_io(option_ops(OptionType)::in(option_ops), list(string)::in, list(string)::out, maybe_option_table_se(OptionType)::out, io::di, io::uo) is det. :- pred process_options_io(option_ops(OptionType)::in(option_ops), list(string)::in, list(string)::out, list(string)::out, maybe_option_table_se(OptionType)::out, io::di, io::uo) is det. % process_options_track(OptionOps, Args, OptionArgs, NonOptionArgs, % OptionTable0, Result, OptionsSet): % process_options_track_io(OptionOps, Args, OptionArgs, NonOptionArgs, % OptionTable0, Result, OptionsSet, !IO): % % These predicates differ from the non-track variants above % in only two respects. % % First, they expect the caller to supply an argument containing % the initial contents of the option table, instead of calling % the initialization predicate themselves. This allows a program % to initialize the option table just once (using either the % init_option_table or the init_option_table_multi predicate below), % but then call process_options_track or process_options_track_io % several times, with different sets of arguments, perhaps obtained % from different sources (such as command line, configuration file, % and so on). % % Second, each call to one of these predicates returns the set of options % that were set by that call. This helps with the same objective. % For example, the caller can tell whether an option was set from % a configuration file, the command line, both, or neither. % :- pred process_options_track( option_ops_track(OptionType)::in(option_ops_track), list(string)::in, list(string)::out, list(string)::out, option_table(OptionType)::in, maybe_option_table_se(OptionType)::out, set(OptionType)::out) is det. :- pred process_options_track_io( option_ops_track(OptionType)::in(option_ops_track), list(string)::in, list(string)::out, list(string)::out, option_table(OptionType)::in, maybe_option_table_se(OptionType)::out, set(OptionType)::out, io::di, io::uo) is det. % process_options_userdata(OptionOps, Args, OptionArgs, NonOptionArgs, % MaybeError, OptionsSet, !OptionTable, !UserData): % process_options_userdata_io(OptionOps, Args, OptionArgs, NonOptionArgs, % MaybeError, OptionsSet, !OptionTable, !UserData, !IO): % % These predicates are similar to the track predicates above, but differ % in two ways. % % - They also thread a piece of state of a user-specified "userdata" type % through all the handlers of special options, so that each % special handler can read from and/or write to this state. % Amongst other things, this can be used by the caller to recover % the *sequence* in which special options are specified, % information that is not present in the (orderless) set % of specified options. % % - Even if they find an error, they return the option table as it was % just before it found the error. This option table will reflect % all the previous options that could be correctly processed. % :- pred process_options_userdata( option_ops_userdata(OptionType, UserDataType)::in(option_ops_userdata), list(string)::in, list(string)::out, list(string)::out, maybe(option_error(OptionType))::out, set(OptionType)::out, option_table(OptionType)::in, option_table(OptionType)::out, UserDataType::in, UserDataType::out) is det. :- pred process_options_userdata_io( option_ops_userdata(OptionType, UserDataType)::in(option_ops_userdata), list(string)::in, list(string)::out, list(string)::out, maybe(option_error(OptionType))::out, set(OptionType)::out, option_table(OptionType)::in, option_table(OptionType)::out, UserDataType::in, UserDataType::out, io::di, io::uo) is det. %--------------------------------------------------% % init_option_table(InitPred, OptionTable): % init_option_table_multi(InitPred, OptionTable): % % Create an initial option table that maps each option to the default % value specified for it by InitPred. % :- pred init_option_table( pred(OptionType, option_data)::in(pred(out, out) is nondet), option_table(OptionType)::out) is det. :- pred init_option_table_multi( pred(OptionType, option_data)::in(pred(out, out) is multi), option_table(OptionType)::out) is det. %--------------------------------------------------% % Each value of this type specifies % % - the identity of an option (in its first argument), % - the string on the command line setting that option % (in its second argument), and, if the option takes a value, % - the value of the option (in its third argument). % % "special" options have no third argument, because they have % no associated value. % % Each occurrence of an accumulating option adds only one string % to the option's value, which is why ov_accumulating has one string. % Options that reset an accumulating option to the empty list % obviously have no associated value. :- type option_value(OptionType) ---> ov_bool(OptionType, string, bool) ; ov_int(OptionType, string, int) ; ov_string(OptionType, string, string) ; ov_maybe_int(OptionType, string, maybe(int)) ; ov_maybe_string(OptionType, string, maybe(string)) ; ov_accumulating(OptionType, string, string) ; ov_accumulating_reset(OptionType, string) ; ov_special(OptionType, string) ; ov_bool_special(OptionType, string, bool) ; ov_int_special(OptionType, string, int) ; ov_string_special(OptionType, string, string) ; ov_maybe_string_special(OptionType, string, maybe(string)) ; ov_file_special(OptionType, string, string). :- type maybe_option_error(OptionType) ---> no_option_error ; found_option_error(option_error(OptionType)). % record_arguments(ShortOptionPred, LongOptionPred, OptionTable, % Args, NonOptionArgs, OptionArgs, MaybeError, OptionValues): % % Given Args, which is a list of command line arguments, % % - classify them into arguments that are and are not option args, % returning them as OptionArgs and NonOptionArgs respectively, % % - use the ShortOptionPred and LongOptionPred predicates % to figure out which user-defined options the OptionArgs refer to, % % - use OptionTable to figure out what kind of value, if any, % each of those user-defined options has as its argument, % % - find those arguments and convert them to the expected type, and % % - provided no errors occurred in any of the above steps, % return a list of those options and their values in OptionValues, % and set MaybeError to no_option_error. % % - If some errors *did* occur, then set MaybeError to found_option_error % wrapped around a description of one of them. This will probably be % the first, but we do not guarantee that. Also, in this error case, % OptionValues will probably contain the values of the options processed % before the error, but we do not guarantee that either. % % Note that unlike the process_options_... predicates above, % this predicate does *not* update the option table in any way. % It also simply returns file_special options in OptionValues; % it does not process them. That processing can be done by % expand_file_specials below. % :- pred record_arguments(short_option(OptionType)::in(short_option), long_option(OptionType)::in(long_option), option_table(OptionType)::in, list(string)::in, list(string)::out, list(string)::out, maybe_option_error(OptionType)::out, list(option_value(OptionType))::out) is det. %--------------------------------------------------% % An inst that lists all the function symbols of the option_value type % *except* ov_file_special. :- inst non_file_special for option_value/1 ---> ov_bool(ground, ground, ground) ; ov_int(ground, ground, ground) ; ov_string(ground, ground, ground) ; ov_maybe_int(ground, ground, ground) ; ov_maybe_string(ground, ground, ground) ; ov_accumulating(ground, ground, ground) ; ov_accumulating_reset(ground, ground) ; ov_special(ground, ground) ; ov_bool_special(ground, ground, ground) ; ov_int_special(ground, ground, ground) ; ov_string_special(ground, ground, ground) ; ov_maybe_string_special(ground, ground, ground). % expand_file_specials(ShortOptionPred, LongOptionPred, OptionTable, % OptionValues, MaybeError, NonFileSpecialOptionValues, !MaybeIO): % % Given a list of OptionValues as generated for example by % record_arguments, replace each ov_file_special option value in that list % with the option values in the file named by that option. % If there are any errors, return a description of one of them % in MaybeError; otherwise, return the fully expanded list of options % in NonFileSpecialOptionValues, and set MaybeError to no_option_error. % % The ShortOptionPred, LongOptionPred and OptionTable arguments % play the same role as in record_arguments, since expand_file_specials % must of course record all the options in files named by ov_file_special % option values. % :- pred expand_file_specials(short_option(OptionType)::in(short_option), long_option(OptionType)::in(long_option), option_table(OptionType)::in, list(option_value(OptionType))::in, maybe_option_error(OptionType)::out, list(option_value(OptionType))::out(list_skel(non_file_special)), io::di, io::uo) is det. %--------------------------------------------------% % The following functions and predicates search the option table % for an option of the specified kind. If the option is not in the table, % they throw an exception. :- func lookup_bool_option(option_table(Option), Option) = bool. :- pred lookup_bool_option(option_table(Option)::in, Option::in, bool::out) is det. :- func lookup_int_option(option_table(Option), Option) = int. :- pred lookup_int_option(option_table(Option)::in, Option::in, int::out) is det. :- func lookup_string_option(option_table(Option), Option) = string. :- pred lookup_string_option(option_table(Option)::in, Option::in, string::out) is det. :- func lookup_maybe_int_option(option_table(Option), Option) = maybe(int). :- pred lookup_maybe_int_option(option_table(Option)::in, Option::in, maybe(int)::out) is det. :- func lookup_maybe_string_option(option_table(Option), Option) = maybe(string). :- pred lookup_maybe_string_option(option_table(Option)::in, Option::in, maybe(string)::out) is det. :- func lookup_accumulating_option(option_table(Option), Option) = list(string). :- pred lookup_accumulating_option(option_table(Option)::in, Option::in, list(string)::out) is det. %--------------------------------------------------% % Convert the structured representation of an error % to an error message. % :- func option_error_to_string(option_error(OptionType)) = string. %--------------------------------------------------% % If the argument represents an error, then convert that error from % the structured representation to an error message. % :- func convert_to_maybe_option_table(maybe_option_table_se(OptionType)) = maybe_option_table(OptionType). %--------------------------------------------------% %--------------------------------------------------%