Next: , Previous: getopt, Up: Top   [Contents]


31 getopt_io

%--------------------------------------------------%
% 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_io.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 for 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_io.
:- interface.

:- import_module bool.
:- import_module char.
:- import_module io.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module set.

%--------------------------------------------------%

    % 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, and
    % - what the short and long names of the options are.
    %   (See the comment at the top for a description of the distinction.)
    %
    % 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
    %   % (long or short) designated as special, 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(
                pred(char, OptionType),                     % short_option
                pred(string, OptionType),                   % long_option
                pred(OptionType, option_data)               % option_default
            )
    ;       option_ops(
                pred(char, OptionType),                     % short_option
                pred(string, OptionType),                   % long_option
                pred(OptionType, option_data),              % option_default
                pred(OptionType, special_data,              % special handler
                    option_table(OptionType),
                    maybe_option_table(OptionType))
            )
    ;       option_ops_multi(
                pred(char, OptionType),                     % short_option
                pred(string, OptionType),                   % long_option
                pred(OptionType, option_data)               % option_default
            )
    ;       option_ops_multi(
                pred(char, OptionType),                     % short_option
                pred(string, OptionType),                   % long_option
                pred(OptionType, option_data),              % option_default
                pred(OptionType, special_data,              % special handler
                    option_table(OptionType),
                    maybe_option_table(OptionType))
            ).

:- inst option_ops for option_ops/1
    --->    option_ops(
                pred(in, out) is semidet,                   % short_option
                pred(in, out) is semidet,                   % long_option
                pred(out, out) is nondet                    % option_default
            )
    ;       option_ops_multi(
                pred(in, out) is semidet,                   % short_option
                pred(in, out) is semidet,                   % long_option
                pred(out, out) is multi                     % option_default
            )
    ;       option_ops(
                pred(in, out) is semidet,                   % short_option
                pred(in, out) is semidet,                   % long_option
                pred(out, out) is nondet,                   % option_default
                pred(in, in, in, out) is semidet            % special handler
            )
    ;       option_ops_multi(
                pred(in, out) is semidet,                   % short_option
                pred(in, out) is semidet,                   % long_option
                pred(out, out) is multi,                    % option_default
                pred(in, in, in, out) is semidet            % special handler
            ).

%--------------------------------------------------%

    % 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(
                pred(char, OptionType),                     % short_option
                pred(string, OptionType),                   % long_option
                pred(OptionType, special_data,              % special handler
                    option_table(OptionType),
                    maybe_option_table(OptionType),
                    set(OptionType))
            ).

:- inst option_ops_track for option_ops_track/1
    --->    option_ops_track(
                pred(in, out) is semidet,                   % short_option
                pred(in, out) is semidet,                   % long_option
                pred(in, in, in, out, out) is semidet       % special handler
            ).

%--------------------------------------------------%

    % 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(
                pred(char, OptionType),                     % short_option
                pred(string, OptionType),                   % long_option
                pred(OptionType, special_data,              % special handler
                    option_table(OptionType),
                    maybe_option_table(OptionType),
                    UserDataType, UserDataType)
            ).

:- inst option_ops_userdata for option_ops_userdata/2
    --->    option_ops_userdata(
                pred(in, out) is semidet,                   % short_option
                pred(in, out) is semidet,                   % long_option
                pred(in, in, in, out, in, out) is semidet   % special 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.

%--------------------------------------------------%

    % 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.

%--------------------------------------------------%

    % 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).

%--------------------------------------------------%
%--------------------------------------------------%


Next: , Previous: getopt, Up: Top   [Contents]