view scripts/miscellaneous/inputParser.m @ 32047:e3de59065cf1

inputParser.m: Re-architect internal data structures for 60% speed improvement. Key idea is to replace Parameter and Switch structs with name field containing a nested struct (def, val fields) with a struct array that has fields "name", "def", and "val". This makes it possible to eliminate for loops. * NEWS.9.md: Announce performance improvement. * inputParser.m: Declare "Parameter" and "Switch" properties as struct arrays. Add new private property "last_idx" which caches the last Parameter or Switch lookup. * inputParser.m (addRequired): Adjust input validation to use numel() now rather than numfields(). * inputParser.m (addOptional): Adjust input validation to use numel() now rather than numfields(). * inputParser.m (addParameter): Change code to add new Parameter to a struct array. * inputParser.m (addSwitch): Change code to add new Switch to a struct array. * inputParser.m (parse): Adjust initialization of internal variables ParameterNames and SwitchNames to match new data structure. Use last_idx to simplify call to validate_arg() for Parameters. * inputParser.m (validate_arg): Move creation of error message to code path when error has been found---No need to do it for every function call. Don't capture "exception" variable in try/catch as it is no longer used. * inputParser.m (is_argname): Cache "last_idx" if match was found. * inputParser.m (add_missing): Replace call to setdiff() with in-place code which can take advantage of implicit knowledge to eliminate calls to unique and input validation. Replace for loop with cell2struct/struct2cell combination.
author Rik <rik@octave.org>
date Fri, 21 Apr 2023 11:01:40 -0700
parents ba0596c25479
children 72dcb1cef2c9
line wrap: on
line source

########################################################################
##
## Copyright (C) 2011-2023 The Octave Project Developers
##
## See the file COPYRIGHT.md in the top-level directory of this
## distribution or <https://octave.org/copyright/>.
##
## This file is part of Octave.
##
## Octave is free software: you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## Octave is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Octave; see the file COPYING.  If not, see
## <https://www.gnu.org/licenses/>.
##
########################################################################

classdef inputParser < handle

  ## -*- texinfo -*-
  ## @deftypefn {} {@var{p} =} inputParser ()
  ## Create object @var{p} of the inputParser class.
  ##
  ## This class is designed to allow easy parsing of function arguments.  The
  ## class supports four types of arguments:
  ##
  ## @enumerate
  ## @item mandatory (see @code{addRequired});
  ##
  ## @item optional (see @code{addOptional});
  ##
  ## @item named (see @code{addParameter});
  ##
  ## @item switch (see @code{addSwitch}).
  ## @end enumerate
  ##
  ## After defining the function API with these methods, the supplied arguments
  ## can be parsed with the @code{parse} method and the results accessed with
  ## the @code{Results} accessor.
  ## @end deftypefn
  ##
  ## @deftypefn {} {} inputParser.Parameters
  ## Return the list of parameter names already defined.  (read-only)
  ## @end deftypefn
  ##
  ## @deftypefn {} {} inputParser.Results
  ## Return a structure with argument names as fieldnames and corresponding
  ## values.  (read-only)
  ## @end deftypefn
  ##
  ## @deftypefn {} {} inputParser.Unmatched
  ## Return a structure similar to @code{Results}, but for unmatched
  ## parameters.  (read-only)
  ## See the @code{KeepUnmatched} property.
  ## @end deftypefn
  ##
  ## @deftypefn {} {} inputParser.UsingDefaults
  ## Return cell array with the names of arguments that are using default
  ## values.  (read-only)
  ## @end deftypefn
  ##
  ## @deftypefn {} {} inputParser.FunctionName = @var{name}
  ## Set function name to be used in error messages; Defaults to empty string.
  ## @end deftypefn
  ##
  ## @deftypefn {} {} inputParser.CaseSensitive = @var{boolean}
  ## Set whether matching of argument names should be case sensitive; Defaults
  ## to false.
  ## @end deftypefn
  ##
  ## @deftypefn {} {} inputParser.KeepUnmatched = @var{boolean}
  ## Set whether string arguments which do not match any Parameter are parsed
  ## and stored in the @code{Unmatched} property; Defaults to false.  If false,
  ## an error will be emitted at the first unrecognized argument and parsing
  ## will stop.  Note that since @code{Switch} and @code{Parameter} arguments
  ## can be mixed, it is not possible to know the type of the unmatched
  ## argument.  Octave assumes that all unmatched arguments are of the
  ## @code{Parameter} type and therefore must be followed by a value.
  ## @end deftypefn
  ##
  ## @deftypefn {} {} inputParser.PartialMatching = @var{boolean}
  ## Set whether argument names for @code{Parameter} and @code{Switch} options
  ## may be given in shortened form as long as the name uniquely identifies
  ## an option; Defaults to true.  For example, the argument @qcode{'opt'} will
  ## match a parameter @qcode{'opt_color'}, but will fail if there is also a
  ## parameter @qcode{'opt_case'}.
  ## @end deftypefn
  ##
  ## @deftypefn {} {} inputParser.StructExpand = @var{boolean}
  ## Set whether a structure passed to the function is expanded into
  ## parameter/value pairs (parameter = fieldname); Defaults to true.
  ##
  ## The following example shows how to use this class:
  ##
  ## @example
  ## function check (varargin)
  ## @c The next two comments need to be indented by one for alignment
  ##   p = inputParser ();                      # create object
  ##   p.FunctionName = "check";                # set function name
  ##   p.addRequired ("pack", @@ischar);         # mandatory argument
  ##   p.addOptional ("path", pwd(), @@ischar);  # optional argument
  ##
  ##   ## Create anonymous function handle for validators
  ##   valid_vec = @@(x) isvector (x) && all (x >= 0) && all (x <= 1);
  ##   p.addOptional ("vec", [0 0], valid_vec);
  ##
  ##   ## Create two arguments of type "Parameter"
  ##   vld_type = @@(x) any (strcmp (x, @{"linear", "quadratic"@}));
  ##   p.addParameter ("type", "linear", vld_type);
  ##   vld_tol = @@(x) any (strcmp (x, @{"low", "medium", "high"@}));
  ##   p.addParameter ("tolerance", "low", vld_tol);
  ##
  ##   ## Create a switch type of argument
  ##   p.addSwitch ("verbose");
  ##
  ##   p.parse (varargin@{:@});  # Run created parser on inputs
  ##
  ##   ## The rest of the function can access inputs by using p.Results.
  ##   ## For example, get the tolerance input with p.Results.tolerance
  ## endfunction
  ## @end example
  ##
  ## @example
  ## @group
  ## check ("mech");           # valid, use defaults for other arguments
  ## check ();                 # error, one argument is mandatory
  ## check (1);                # error, since ! ischar
  ## check ("mech", "~/dev");  # valid, use defaults for other arguments
  ##
  ## check ("mech", "~/dev", [0 1 0 0], "type", "linear");  # valid
  ##
  ## ## following is also valid.  Note how the Switch argument type can
  ## ## be mixed in with or before the Parameter argument type (but it
  ## ## must still appear after any Optional arguments).
  ## check ("mech", "~/dev", [0 1 0 0], "verbose", "tolerance", "high");
  ##
  ## ## following returns an error since an Optional argument, 'path',
  ## ## was given after the Parameter argument 'type'.
  ## check ("mech", "type", "linear", "~/dev");
  ## @end group
  ## @end example
  ##
  ## @emph{Note 1}: A function can have any mixture of the four API types but
  ## they must appear in a specific order.  @code{Required} arguments must be
  ## first and can be followed by any @code{Optional} arguments.  Only
  ## the @code{Parameter} and @code{Switch} arguments may be mixed
  ## together and they must appear following the first two types.
  ##
  ## @emph{Note 2}: If both @code{Optional} and @code{Parameter} arguments
  ## are mixed in a function API then once a string Optional argument fails to
  ## validate it will be considered the end of the @code{Optional}
  ## arguments.  The remaining arguments will be compared against any
  ## @code{Parameter} or @code{Switch} arguments.
  ##
  ## @seealso{nargin, validateattributes, validatestring, varargin}
  ## @end deftypefn

  properties
    ## FIXME: set input checking for these properties
    CaseSensitive   = false;
    FunctionName    = "";
    KeepUnmatched   = false;
    PartialMatching = true;
    StructExpand    = true;
  endproperties

  properties (SetAccess = protected)
    Parameters    = cell ();
    Results       = struct ();
    Unmatched     = struct ();
    UsingDefaults = cell ();
  endproperties

  properties (Access = protected)
    ## Since Required and Optional are ordered, they get a cell array of
    ## structs with the fields "name", "def" (default), and "val" (validator).
    Required = cell ();
    Optional = cell ();
    ## Parameter and Switch are unordered so we use a struct array with
    ## fields "name", "def" (default", and "val" (validator).
    Parameter = struct ([]);  # create 0x0 struct array, not scalar struct
    Switch    = struct ([]);

    ## List of Parameter and Switch names to simplify searches
    ParameterNames = cell ();
    SwitchNames    = cell ();

    ## Simplify searches by cacheing the last name and last index of a
    ## match from is_argname() into the Parameter or Switch struct arrays.
    last_name = "";
    last_idx = 1;
  endproperties

  properties (Access = protected, Constant = true)
    ## Default validator, always returns scalar true.
    def_val = @(~) true;
  endproperties

  methods

    function addRequired (this, name, val = inputParser.def_val)

      ## -*- texinfo -*-
      ## @deftypefn  {} {} addRequired (@var{argname})
      ## @deftypefnx {} {} addRequired (@var{argname}, @var{validator})
      ## Add new mandatory argument to the object @var{parser} of inputParser
      ## class.  This method belongs to the inputParser class and implements
      ## an ordered-argument type of API.
      ##
      ## @var{argname} must be a string with the name of the new argument.  The
      ## order in which new arguments are added with @code{addRequired}
      ## represents the expected order of arguments.
      ##
      ## The optional argument @var{validator} is a function (handle or name)
      ## that will return false or throw an error if the input @var{argname}
      ## is invalid.
      ##
      ## See @code{help inputParser} for examples.
      ##
      ## @emph{Note}: A Required argument can be used together with other
      ## types of arguments but it must be the first (see @code{@@inputParser}).
      ##
      ## @end deftypefn

      if (nargin < 2)
        print_usage ();
      elseif (numel (this.Optional)
              || numel (this.Parameter) || numel (this.Switch))
        error (["inputParser.addRequired: can't have a Required argument " ...
                "after Optional, Parameter, or Switch"]);
      endif
      this.validate_name ("Required", name);
      this.Required{end+1} = struct ("name", name, "val", val);

    endfunction

    function addOptional (this, name, def, val = inputParser.def_val)

      ## -*- texinfo -*-
      ## @deftypefn  {} {} addOptional (@var{argname}, @var{default})
      ## @deftypefnx {} {} addOptional (@var{argname}, @var{default}, @var{validator})
      ## Add new optional argument to the object @var{parser} of the class
      ## inputParser to implement an ordered-argument type of API
      ##
      ## @var{argname} must be a string with the name of the new argument.  The
      ## order in which new arguments are added with @code{addOptional}
      ## represents the expected order of arguments.
      ##
      ## @var{default} will be the value used when the argument is not
      ## specified.
      ##
      ## The optional argument @var{validator} is a function (handle or name)
      ## that will return false or throw an error if the input @var{argname}
      ## is invalid.
      ##
      ## See @code{help inputParser} for examples.
      ##
      ## @emph{Note1}: If an optional argument is not given a
      ## validator then anything will be valid, and therefore a string in the
      ## correct position (after Required arguments) will be assigned to the
      ## value of the Optional argument @emph{even} if the string is the name
      ## of a Parameter key.  @sc{matlab} adds a default validator
      ## @code{@@(x) ~ischar (x)} if none is specified which emits an error
      ## in this instance.
      ##
      ## @emph{Note2}: if a string argument fails validation, it will be
      ## considered as a possible Parameter.
      ## @end deftypefn

      if (nargin < 3)
        print_usage ();
      elseif (numel (this.Parameter) || numel (this.Switch))
        error (["inputParser.Optional: can't have Optional arguments " ...
                "after Parameter or Switch"]);
      endif
      this.validate_name ("Optional", name);
      if (iscell (def))
        def = {def};
      endif
      this.Optional{end+1} = struct ("name", name, "def", def, "val", val);

    endfunction

    function addParamValue (this, name, def, val = inputParser.def_val)

      ## -*- texinfo -*-
      ## @deftypefn  {} {} addParamValue (@var{argname}, @var{default})
      ## @deftypefnx {} {} addParamValue (@var{argname}, @var{default}, @var{validator})
      ## This function is deprecated.  Use @code{addParameter} in all new code.
      ##
      ## Add new parameter to the object @var{parser} of the class inputParser
      ## to implement a name/value pair type of API.
      ##
      ## This is an alias for @code{addParameter} method without the
      ## @qcode{"PartialMatchPriority"} option.  See @code{addParameter} for
      ## the help text.
      ##
      ## @end deftypefn

      if (nargin < 3)
        print_usage ();
      endif
      this.addParameter (name, def, val);

    endfunction

    function addParameter (this, name, def, varargin)

      ## -*- texinfo -*-
      ## @deftypefn  {} {} addParameter (@var{argname}, @var{default})
      ## @deftypefnx {} {} addParameter (@var{argname}, @var{default}, @var{validator})
      ## Add new parameter argument to the object @var{parser} of the class
      ## inputParser to implement a name/value pair type of API.
      ##
      ## @var{argname} must be a string with the name of the new parameter.
      ##
      ## @var{default} will be the value used when the parameter is not
      ## specified.
      ##
      ## The optional argument @var{validator} is a function (handle or name)
      ## that will return false or throw an error if the input @var{argname}
      ## is invalid.
      ##
      ## See @code{help inputParser} for examples.
      ##
      ## @end deftypefn

      if (nargin < 3 || nargin > 6)
        print_usage ();
      endif

      n_opt = numel (varargin);

      if (n_opt == 0 || n_opt == 2)
        val = inputParser.def_val;
      else  # n_opt is 1 or 3
        val = varargin{1};
      endif

      if (n_opt == 0 || n_opt == 1)
        match_priority = 1;
      else  # n_opt is 2 or 3
        if (! strcmpi (varargin{end-1}, "PartialMatchPriority"))
          error ("inputParser.addParameter: unrecognized option");
        endif
        match_priority = varargin{end};
        validateattributes (match_priority, {"numeric"}, {"positive", "integer"},
                            "inputParser.addParameter",
                            "PartialMatchPriority");
      endif

      this.validate_name ("Parameter", name);
      this.Parameter(end+1) = struct ("name", name, "def", def, "val", val);

    endfunction

    function addSwitch (this, name)

      ## -*- texinfo -*-
      ## @deftypefn {} {} addSwitch (@var{argname})
      ## Add new switch argument to the object @var{parser} of the class
      ## inputParser to implement a name/boolean type of API.
      ##
      ## @var{argname} must be a string with the name of the new argument.
      ##
      ## Arguments of this type must be specified after @code{Required} and
      ## @code{Optional} arguments, but can be mixed with any @code{Parameter}
      ## definitions.  The default for switch arguments is false.  During
      ## parsing, if one of the arguments supplied is a string such as
      ## @var{argname} that matches a defined switch such as
      ## @w{@code{addSwitch (@var{argname})}}, then after parsing the value
      ## of @code{parse.Results.@var{argname}} will be true.
      ##
      ## See @code{help inputParser} for examples.
      ##
      ## Compatibility Note: @code{addSwitch} is an Octave extension not
      ## present in @sc{matlab}.
      ##
      ## @end deftypefn

      if (nargin != 2)
        print_usage ();
      endif
      this.validate_name ("Switch", name);
      this.Switch(end+1) = struct ("name", name, "def", false);

    endfunction

    function parse (this, varargin)

      ## -*- texinfo -*-
      ## @deftypefn {} {} parse (@var{varargin})
      ## Parse and validate list of arguments according to object @var{parser}
      ## of the class inputParser.
      ##
      ## After parsing, the results can be accessed with the @code{Results}
      ## accessor.  See @code{help inputParser} for a more complete
      ## description.
      ##
      ## @end deftypefn

      this.Results = struct ();
      this.Unmatched = struct ();
      this.UsingDefaults = cell ();
      if (numel (varargin) < numel (this.Required))
        if (this.FunctionName)
          print_usage (this.FunctionName);
        else
          this.error ("inputParser.parse: not enough input arguments");
        endif
      endif
      pnargin = numel (varargin);
      this.ParameterNames = {};
      if (numel (this.Parameter))
        this.ParameterNames = {this.Parameter.name};
      endif
      this.SwitchNames = {};
      if (numel (this.Switch))
        this.SwitchNames = {this.Switch.name};
      endif

      ## Evaluate the Required arguments first
      nReq = numel (this.Required);
      for idx = 1:nReq
        req = this.Required{idx};
        this.validate_arg (req.name, req.val, varargin{idx});
      endfor

      vidx = nReq;  # current index in varargin

      ## Search for a list of Optional arguments
      idx  = 0;     # current index on the array of Optional
      nOpt = numel (this.Optional);
      while (vidx < pnargin && idx < nOpt)
        opt = this.Optional{++idx};
        in  = varargin{++vidx};
        if ((this.is_argname ("Parameter", in) && vidx < pnargin)
            || this.is_argname ("Switch", in))
          ## The string value looks like an optional parameter/value pair or a
          ## switch.  The Optional positional argument which could apply here
          ## is specifically not used (this is Matlab compatible).
          ## See bug #50752.
          idx -= 1;
          vidx -= 1;
          break;
        endif
        valid_option = this.validate_arg ("", opt.val, in);
        if (! valid_option)
          ## If it does not match there are two options:
          ##   1a) it's a Parameter or Switch name and we should use
          ##       the default for the rest;
          ##   1b) it's a struct with the Parameter pairs.
          ##   2) input is actually wrong and we should error;
          if (ischar (in)
              || (this.StructExpand && isstruct (in) && isscalar (in)))
            idx -= 1;
            vidx -= 1;
            break;
          else
            this.error (sprintf (["failed validation of '%s'\n", ...
                                  "Validation function: %s"],
                                 opt.name, disp (opt.val)));
          endif
        endif
        this.Results.(opt.name) = in;
      endwhile

      ## Fill in with defaults of missing Optional
      while (idx++ < nOpt)
        opt = this.Optional{idx};
        this.UsingDefaults{end+1} = opt.name;
        this.Results.(opt.name) = opt.def;
      endwhile

      ## Search unordered Options (Switch and Parameter)
      while (vidx++ < pnargin)
        name = varargin{vidx};

        if (this.StructExpand && isstruct (name) && isscalar (name))
          expanded_options = [fieldnames(name) struct2cell(name)]'(:);
          if (isempty (expanded_options))
            continue;  # empty, continue to next argument
          endif
          n_new_args = numel (expanded_options) - 1;
          pnargin += n_new_args;
          varargin(vidx+n_new_args+1:pnargin) = varargin(vidx+1:end);
          varargin(vidx:vidx+n_new_args) = expanded_options;
          name = varargin{vidx};
        endif

        if (! ischar (name))
          this.error ("Parameter or Switch name must be a string");
        endif

        if (this.is_argname ("Parameter", name))
          if (++vidx > pnargin)
            this.error (sprintf ("no value for parameter '%s'", name));
          endif
          this.validate_arg (this.last_name,
                             this.Parameter(this.last_idx).val,
                             varargin{vidx});
        elseif (this.is_argname ("Switch", name))
          this.Results.(this.last_name) = true;
        else
          if (vidx++ < pnargin && this.KeepUnmatched)
            this.Unmatched.(name) = varargin{vidx};
          else
            this.error (sprintf ("argument '%s' is not a declared parameter or switch", name));
          endif
        endif
      endwhile
      ## Add them to the UsingDefaults list
      this.add_missing ("Parameter");
      this.add_missing ("Switch");

      ## Sort fields for Matlab compatibility (bug #64003)
      this.Results = orderfields (this.Results);

    endfunction

    function disp (this)

      if (nargin != 1)
        print_usage ();
      endif
      printf ("inputParser object with properties:\n\n");
      b2s = @(x) ifelse (any (x), "true", "false");
      printf (["   FunctionName    : \"%s\"\n" ...
               "   CaseSensitive   : %s\n" ...
               "   KeepUnmatched   : %s\n" ...
               "   PartialMatching : %s\n" ...
               "   StructExpand    : %s\n\n"],
               this.FunctionName, b2s (this.CaseSensitive),
               b2s (this.KeepUnmatched), b2s (this.PartialMatching),
               b2s (this.StructExpand));
      printf ("Defined parameters:\n\n   {%s}\n",
              strjoin (this.Parameters, ", "));

    endfunction

  endmethods

  methods (Access = private)

    function validate_name (this, type, name)

      if (! isvarname (name))
        error ("inputParser.add%s: NAME is an invalid identifier", method);
      elseif (any (strcmpi (this.Parameters, name)))
        ## Even if CaseSensitive is true, we still shouldn't allow
        ## two args with the same name.
        error ("inputParser.add%s: argname '%s' has already been declared",
               type, name);
      endif
      this.Parameters{end+1} = name;
      ## Sort Parameters for Matlab compatibility (bug #64003)
      this.Parameters = sort (this.Parameters);

    endfunction

    function r = validate_arg (this, name, val, in)

      ## Validation function can either produce a true/false result or have
      ## no outputs but throw an error when failing.  Tricky code here
      ## relies on side effect, but calls validation function only once
      ## which is a performance win and may also be necessary if validation
      ## function maintains state.  See bug #49793.
      ans = true;
      try
        val (in);  # call function with no arguments in case nargout == 0
        ok = ans;  # use side effect of assignment to 'ans' when nargout == 1
      catch
        ok = false;
      end_try_catch

      if (nargout > 0)
        r = ok;
      else
        if (! ok)
          err = sprintf ('Checked with "%s"', func2str (val));
          this.error (sprintf ("failed validation of '%s'.  %s", name, err));
        endif
        this.Results.(name) = in;
      endif

    endfunction

    function r = is_argname (this, type, name)

      r = ischar (name) && isrow (name);
      if (r)
        fnames = this.([type "Names"]);
        if (this.PartialMatching)
          if (this.CaseSensitive)
            idx = strncmp (name, fnames, numel (name));
            r = sum (idx);
            if (r > 1)
              ## Check for exact match and prefer it over a partial match
              idx2 = strcmp (name, {fnames{idx}});
              if (any (idx2))
                idx = (find (idx))(idx2);
                r = 1;
              else
                matches = sprintf ("'%s', ", fnames{idx})(1:end-1);
                matches(end) = '.';
                this.error (sprintf ("argument '%s' matches more than one %s: %s", name, type, matches));
              endif
            endif
            r = logical (r);
          else  # not CaseSensitive
            idx = strncmpi (name, fnames, numel (name));
            r = sum (idx);
            if (r > 1)
              idx2 = strcmpi (name, {fnames{idx}});
              if (any (idx2))
                idx = (find (idx))(idx2);
                r = 1;
              else
                matches = sprintf ("'%s', ", fnames{idx})(1:end-1);
                matches(end) = '.';
                this.error (sprintf ("argument '%s' matches more than one %s: %s", name, type, matches));
              endif
            endif
            r = logical (r);
          endif
        else  # no PartialMatching
          if (this.CaseSensitive)
            idx = strcmp (name, fnames);
            r = any (idx(:));
          else
            idx = strcmpi (name, fnames);
            r = any (idx(:));
          endif
        endif
      endif

      if (r)
        ## Cache values to simplify and speed up later code.
        this.last_name = fnames{idx};
        this.last_idx = find (idx);
      endif

    endfunction

    function add_missing (this, type)

      if (isempty (this.(type)))
        return;
      endif

      ## Implement setdiff() without calling out to function for performance.
      typenames = {this.(type).name}(:); 
      Resultnames = fieldnames (this.Results);
      [sorted, sidx] = sort ([typenames; Resultnames]);
      dups = strcmp (sorted(1:end-1), sorted(2:end));
      idx = true (size (typenames));
      idx(sidx(dups)) = false;
      if (any (idx))
        unmatched_names = typenames(idx);
        unmatched_def = {this.(type)(idx).def}(:);
        this.Results = ...
          cell2struct (vertcat (struct2cell (this.Results), unmatched_def),
                       [Resultnames; unmatched_names]);
        this.UsingDefaults = [this.UsingDefaults, unmatched_names];
      endif

    endfunction

    function error (this, msg)

      where = "";
      if (this.FunctionName)
        where = [this.FunctionName ": "];
      endif
      error ("%s%s", where, msg);

    endfunction

  endmethods

endclassdef


%!function p = create_p ()
%!  p = inputParser ();
%!  p.CaseSensitive = true;
%!  p.PartialMatching = false;
%!  p.addRequired ("req1", @(x) ischar (x));
%!  p.addOptional ("op1", "val", @(x) any (strcmp (x, {"val", "foo"})));
%!  p.addOptional ("op2", 78, @(x) x > 50);
%!  p.addSwitch ("verbose");
%!  p.addParameter ("line", "tree", @(x) any (strcmp (x, {"tree", "circle"})));
%!endfunction

## check normal use, only required are given
%!test
%! p = create_p ();
%! p.parse ("file");
%! r = p.Results;
%! assert (r.req1, "file");
%! assert (sort (p.UsingDefaults), sort ({"op1", "op2", "verbose", "line"}));
%! assert ({r.req1, r.op1, r.op2, r.verbose, r.line},
%!         {"file", "val", 78,    false,     "tree"});

## check normal use, but give values different than defaults
%!test
%! p = create_p ();
%! p.parse ("file", "foo", 80, "line", "circle", "verbose");
%! r = p.Results;
%! assert ({r.req1, r.op1, r.op2, r.verbose, r.line},
%!         {"file", "foo", 80,    true,      "circle"});

## check optional is skipped and considered Parameter if unvalidated string
%!test
%! p = create_p ();
%! p.parse ("file", "line", "circle");
%! r = p.Results;
%! assert ({r.req1, r.op1, r.op2, r.verbose, r.line},
%!         {"file", "val", 78,    false,     "circle"});

## check CaseSensitive (default false state)
%!test
%! p = create_p ();
%! p.CaseSensitive = false;
%! p.parse ("file", "foo", 80, "LiNE", "circle", "vERbOSe");
%! r = p.Results;
%! assert ({r.req1, r.op1, r.op2, r.verbose, r.line},
%!         {"file", "foo", 80,    true,      "circle"});

## check PartialMatching (default true state)
%!test
%! p = create_p ();
%! p.PartialMatching = true;
%! p.parse ("file", "foo", 80, "l", "circle", "verb");
%! r = p.Results;
%! assert ({r.req1, r.op1, r.op2, r.verbose, r.line},
%!         {"file", "foo", 80,    true,      "circle"});

## Check PartialMatching selects an exact match if it exists
%!test
%! p = inputParser ();
%! p.addParameter ('Mass', []);
%! p.addParameter ('MassSingular', []);
%! p.parse ("Mass", pi);
%! r = p.Results;
%! assert ({r.Mass, r.MassSingular}, {pi, []});

## check KeepUnmatched
%!test
%! p = create_p ();
%! p.KeepUnmatched = true;
%! p.parse ("file", "foo", 80, "line", "circle", "verbose", "extra", 50);
%! assert (p.Unmatched.extra, 50);

## check error when missing required
%!error <not enough input arguments>
%! p = create_p ();
%! p.parse ();

## check error when required arg does not validate
%!error <failed validation of >
%! p = create_p ();
%! p.parse (50);

## check error when optional arg does not validate
%!error <is not a declared parameter or switch>
%! p = create_p ();
%! p.parse ("file", "no-val");

## check error when Parameter arg does not validate
%!error <failed validation of >
%! p = create_p ();
%! p.parse ("file", "foo", 51, "line", "round");

## check PartialMatching errors
%!error <'arg' matches more than one Parameter: 'arg123', 'arg456'>
%! p = inputParser ();
%! p.addParameter ('arg123', 123);
%! p.addParameter ('arg456', 456);
%! p.addSwitch ('arg789');
%! p.addSwitch ('arg790');
%! p.parse ('arg', 'bad');

%!error <'arg7' matches more than one Switch: 'arg789', 'arg790'>
%! p = inputParser ();
%! p.addParameter ('arg123', 123);
%! p.addParameter ('arg456', 456);
%! p.addSwitch ('arg789');
%! p.addSwitch ('arg790');
%! p.parse ('arg7', 'bad');

## check alternative method (obj, ...) API
%!function p2 = create_p2 ();
%!  p2 = inputParser ();
%!  addRequired (p2, "req1", @(x) ischar (x));
%!  addOptional (p2, "op1", "val", @(x) any (strcmp (x, {"val", "foo"})));
%!  addOptional (p2, "op2", 78, @(x) x > 50);
%!  addSwitch (p2, "verbose");
%!  addParameter (p2, "line", "tree", @(x) any (strcmp (x, {"tree", "circle"})));
%!endfunction

## check normal use, only required are given
%!test
%! p2 = create_p2 ();
%! parse (p2, "file");
%! r = p2.Results;
%! assert ({r.req1, r.op1, r.op2, r.verbose, r.line},
%!         {"file", "val", 78,    false,     "tree"});
%! assert (sort (p2.UsingDefaults), sort ({"op1", "op2", "verbose", "line"}));

## check normal use, but give values different than defaults
%!test
%! p2 = create_p2 ();
%! parse (p2, "file", "foo", 80, "line", "circle", "verbose");
%! r = p2.Results;
%! assert ({r.req1, r.op1, r.op2, r.verbose, r.line},
%!         {"file", "foo", 80,    true,      "circle"});

## Octave must not perform validation of default values
%!test <*45837>
%! p = inputParser ();
%! p.addParameter ("Dir", [], @ischar);
%! p.parse ();
%! assert (p.Results.Dir, []);

%!test
%! p = inputParser ();
%! p.addParameter ("positive", -1, @(x) x > 5);
%! p.parse ();
%! assert (p.Results.positive, -1);

## Throw an error on validation of optional argument to check that it
## is caught without preventing continuation into param/value pairs.
%!test
%! p = inputParser ();
%! p.addOptional ("err", "foo", @error);
%! p.addParameter ("not_err", "bar", @ischar);
%! p.parse ("not_err", "qux");
%! assert (p.Results.err, "foo");
%! assert (p.Results.not_err, "qux");

## With more Parameters to test StructExpand
%!function p3 = create_p3 ();
%!  p3 = inputParser ();
%!  addOptional (p3, "op1", "val", @(x) any (strcmp (x, {"val", "foo"})));
%!  addOptional (p3, "op2", 78, @(x) x > 50);
%!  addSwitch (p3, "verbose");
%!  addParameter (p3, "line", "tree", @(x) any (strcmp (x, {"tree", "circle"})));
%!  addParameter (p3, "color", "red", @(x) any (strcmp (x, {"red", "green"})));
%!  addParameter (p3, "style", "tt", @(x) any (strcmp (x, {"tt", "f", "i"})));
%!endfunction

## check StructExpand
%!test
%! p3 = create_p3 ();
%! p3.parse (struct ("line", "circle", "color", "green"));
%! assert (p3.Results, struct ("op1", "val", "op2", 78, "verbose", false,
%!                             "line", "circle", "color", "green",
%!                             "style", "tt"))

%!test  # check last param/value pair overrides previous
%! p3 = create_p3 ();
%! p3.parse (struct ("line", "circle", "color", "green"), "line", "tree");
%! assert (p3.Results.line, "tree");
%! p3.parse ("line", "tree", struct ("line", "circle", "color", "green"));
%! assert (p3.Results.line, "circle");

%!test # unmatched parameters with StructExpand
%! p3 = create_p3 ();
%! p3.KeepUnmatched = true;
%! p3.parse (struct ("line", "circle", "color", "green", "bar", "baz"));
%! assert (p3.Unmatched.bar, "baz");

## The validation for the second optional argument throws an error with
## a struct so check that we can handle it.
%!test
%! p3 = create_p3 ();
%! p3.parse ("foo", struct ("color", "green"), "line", "tree");
%! assert (p3.Results.op1, "foo");
%! assert (p3.Results.line, "tree");
%! assert (p3.Results.color, "green");
%! assert (p3.Results.verbose, false);

## Some simple tests for addParamValue since all the other ones use add
## addParameter but they use the same codepath.
%!test
%! p = inputParser ();
%! addParamValue (p, "line", "tree", @(x) any (strcmp (x, {"tree", "circle"})));
%! addParamValue (p, "color", "red", @(x) any (strcmp (x, {"red", "green"})));
%! p.parse ("line", "circle");
%! assert ({p.Results.line, p.Results.color}, {"circle", "red"});

%!test
%! p = inputParser ();
%! p.addParamValue ("foo", "bar", @ischar);
%! p.parse ();
%! assert (p.Results, struct ("foo", "bar"));
%! p.parse ("foo", "qux");
%! assert (p.Results, struct ("foo", "qux"));

## An Optional argument which has a string value that is the name of a
## Parameter will be parsed as a Parameter/Value pair.
## This is required for Matlab compatibility.
%!test <*50752>
%! p = inputParser ();
%! p.addOptional ("op1", "val");
%! p.addParameter ("line", "tree");
%! p.parse ("line", "circle");
%! assert (p.Results, struct ("op1", "val", "line", "circle"));
%!
%! p = inputParser ();
%! p.addOptional ("op1", "val1");
%! p.addOptional ("op2", "val2");
%! p.addParameter ("line", "tree");
%! p.parse ("line", "circle");
%! assert (p.Results.op1, "val1");
%! assert (p.Results.op2, "val2");
%! assert (p.Results.line, "circle");
%!
%! ## If there's enough arguments to fill the positional options and
%! ## param/key, it still skips positional options.
%! p = inputParser ();
%! p.addOptional ("op1", "val1");
%! p.addOptional ("op2", "val2");
%! p.addParameter ("line", "tree");
%! p.parse ("line", "circle", "line", "rectangle");
%! assert (p.Results, struct ("op1", "val1", "op2", "val2",
%!                            "line", "rectangle"))
%!
%! ## Even if the key/param fails validation, it does not backtrack to
%! ## check if the values are valid positional options.
%! p = inputParser ();
%! p.addOptional ("op1", "val1", @ischar);
%! p.addOptional ("op2", "val2", @isnumeric);
%! p.addParameter ("line", "circle", @ischar);
%! fail ('p.parse ("line", 89)', "failed validation of 'line'")
%!
%! p = inputParser ();
%! p.addOptional ("op1", "val1");
%! p.addParamValue ("line", "circle", @ischar);
%! fail ('p.parse ("line", "line", 89)',
%!       "Parameter or Switch name must be a string")

%!test <*50752>
%! ## This fails in Matlab but works in Octave.  It is a bug there
%! ## that we do not replicate.
%! p = inputParser ();
%! p.addOptional ("op1", "val1");
%! p.addParameter ("line", "circle");
%! p.parse ("line");
%! assert (p.Results, struct ("op1", "line", "line", "circle"));

%!test <*50752>
%! p = inputParser ();
%! p.addOptional ("op1", "val1");
%! p.addSwitch ("line");
%! p.parse ("line");
%! assert (p.Results.op1, "val1");
%! assert (p.Results.line, true);

%!test
%! p = inputParser ();
%! p.addParameter ("a", []);
%! p.addParameter ("b", []);
%! p.parse ("a", 1);
%! p.parse ("b", 1);
%! assert (p.Results, struct ("a", [], "b", 1));
%! assert (p.UsingDefaults, {"a"});

%!test
%! p = inputParser ();
%! p.addParameter ("b", []);
%! p.KeepUnmatched = true;
%! p.parse ("a", 1);
%! p.parse ("b", 1);
%! assert (p.Results, struct ("b", 1));
%! assert (p.Unmatched, struct ());

## Test for patch #9241
%!error <failed validation of 'a'.*ischar>
%! p = inputParser ();
%! p.addParameter ("a", [], @ischar);
%! p.parse ("a", 1);

%!test <*58112>
%! p = inputParser ();
%! p.addRequired ("first");
%! p.addOptional ("second", []);
%! p.parse (1, {"test", 1, 2, 3});
%! r = p.Results;
%! assert (r.first, 1);
%! assert (r.second, {"test", 1, 2, 3});

%!test <*62639>
%! p = inputParser ();
%! p.addOptional ("opt", {});
%! p.parse ();
%! r = p.Results;
%! assert (r.opt, {});
%! p.parse ("x");
%! r = p.Results;
%! assert (r.opt, "x");

%!test <*62639>
%! p = inputParser ();
%! p.addOptional ("opt", {1,2,3});
%! p.parse ();
%! r = p.Results;
%! assert (r.opt, {1,2,3});
%! p.parse ("x");
%! r = p.Results;
%! assert (r.opt, "x");

%!test <*64003>
%! p = inputParser ();
%! p.addOptional ('c',3);
%! p.addOptional ('z',4);
%! p.addParameter ('b',2);
%! p.addParameter ('a',1);
%! p.parse (30, 'b', 20, 'a',10);
%! assert (fieldnames (p.Results), {'a'; 'b'; 'c'; 'z'});

%!test <*49793>
%! p = inputParser ();
%! p.addRequired ("name", @(x) validateattributes (x, {'char'}, {'nonempty'}));
%! p.addOptional ("year", 0001, @(x) validateattributes (x, {'numeric'}, ...
%!                                     {'nonempty','integer','positive'}));
%! p.addParameter ("color", '-', @(x) validateattributes (x, {'char'}, ...
%!                                                           {'nonempty'}));
%! p.parse ('Jim', 1980, 'color', 'black');
%! assert (p.Results.name, 'Jim');
%! assert (p.Results.year, 1980);
%! assert (p.Results.color, 'black');