function varargout= argcheck(maxargs, inargs, defaults, varargin)
% USAGE: [err, <outargs>]= argcheck(maxargs, inargs, defaults)
% [err, <outargs>]= argcheck(maxargs, inargs, defaults, <check list>)
%
% Sets missing input args to the defaults, does argument checking, and handles property id/value lists.
% Although not slow, the emphasis is on error checking, not on speed.
% If a value is tagged as 'proplist' the property id/value list will be returned as a structure.
% Further, if the default value for it is a structure, only property ids that are field names of the
% structure are allowed (the structure's field names must be lower case!). If you don't want this
% check, make the default an empty value, e.g. {}.
%
% EXAMPLES:
% Say you have three arguments x, a, and b, the two latter optional with default values a=0 and b=1.
% Using the function declaration 'function whateverout= funcname(varargin)' you would call
% [err, x, a, b]= argcheck(3, varargin, {0, 1});
% You want to check if you can do an elemetwise calculation with x, a, b, e.g. x.*a+b:
% [err, x, a, b]= argcheck(3, varargin, {0, 1}, 'mathsize');
% And b must be positive:
% [err, x, a, b]= argcheck(3, varargin, {0, 1}, 'mathsize', 'positive', [3]);
% Maybe we want an optional property id/value list:
% [err, x, a, b, p]= argcheck(4, varargin, {0, 1, {}}, ...
% 'mathsize', [1 2 3], 'positive', [3], 'proplist', [4]);
% If the returned err is not empty, signal an error with it:
% if(~isempty(err)), error(err); end
%
% ARGUMENTS:
% err: Error message. If empty, all went well.
% <outargs>: A list of receiving variables, there must be exactly maxargs of them.
% maxargs: Maximum number of arguments, a positive integer scalar.
% inargs: The original input arguments, a cell vector.
% defaults: Default values, a cell vector, where length(defaults) <= length(inargs) <= maxargs.
% <check list>: An optional list that specifies what tests the arguments should pass. Each
% test specification consists of the test id (a string, see below) and an optional
% argument index specifying the the test arguments (an array with inargs indeces).
%
% TEST IDS:
% Check arguments' dimensions:
% - 'length' Arguments are of the same length, uses length(arg).
% - 'mathlength' Arguments are non-empty, numeric, and of the same length or scalar.
% - 'length=N' Arguments are of length N, uses length(arg) == N.
% - 'mathlength=N' Arguments are non-empty, numeric, and of length N or scalar.
% - 'size' Arguments are of the same size.
% - 'mathsize' Arguments are non-empty, numeric, and of the same size or scalar.
% - 'size1=N' Arguments are have N rows, uses size(arg, 1) == N.
% - 'size2=N' Arguments are have N columns, uses size(arg, 2) == N.
% - 'non-empty' Arguments are non-empty, uses ~isempty(arg).
% - 'scalar' Arguments are scalars, uses isscalar.
% - 'vector' Arguments are vectors, uses isvector.
% - 'colvector' Arguments are column vectors.
% - 'rowvector' Arguments are row vectors.
% - 'square' Arguments are non-empty, numeric, square, and two-dimensional arrays.
% - 'dim=N' Arguments are of dimension N, uses ndims(arg) == N.
% Check argument values:
% - 'numeric' Arguments are numeric arrays, uses isa(arg, 'numeric').
% - 'real' Arguments are numeric real arrays, uses isa(arg, 'real').
% - 'finite' Arguments are of only finite elements, uses all(isfinite(arg)).
% - 'int' Arguments are integer values (use 'integer' for integer TYPES, see below).
% - 'positive' Arguments are of only positiv elements.
% - 'non-positive' Arguments are of only non-positiv elements.
% - 'negative' Arguments are of only negative elements.
% - 'non-negative' Arguments are of only non-negative elements.
% - 'non-zero' Arguments are of only non-zero elements.
% - 'proplist' Arguments are property lists -- a cell array with an even number of items where the first,
% third etc. items are strings (property identifiers).
% - 'directory' Arguments are directories, uses isdir(arg).
% Check arguments' class:
% - 'cellstr' Arguments are cell array of strings, uses iscellstr(arg).
% - 'string' Arguments are char or cellstr.
% - 'float' Arguments are floating-point arrays, uses isa(arg, 'float').
% - 'integer' Arguments are of integer data types, uses isa(arg, 'integer').
% - 'sym' Arguments are symbolic expression, uses isa(arg, 'sym').
% - '<other>' Arguments are of a given class, uses isa(arg, 'TAG'), e.g. sym, logical, char, single,
% double, cell, struct, function_handle, int8, uint8, int16, uint16, int32,
% uint32, int64, uint64, or any other class.
%
% HISTORY:
% Version 1.0, 2006-11-16.
% Version 1.1, 2006-11-23.
% - Added check for allowed property ids in property id/value lists.
% - Added structure generation from property id/value lists.
% Version 1.2, 2006-11-27.
% - Returned arguments are no longer made into NaNs if there was an error.
% - Removed some unnecessary case sensitivity.
% - Added check for integer values, tag 'int'.
% - Clarified examples.
%
% COPYRIGHT (C) Peder Axensten <peder at axensten dot se>
% KEYWORDS: argument check, error report, type checking, property lists
%
% ACKNOWLEDGE: 9593, 9082
%
% REQUIREMENTS: Tested on Matlab 7.0.1 (R14SP1), not tested, but maybe works, on earlier.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Check the arguments. This checks the arguments passed to argumentcheck
if(nargin < 3), error('There must be at least 3 arguments.'); end
if(~isscalar(maxargs)), error('First argument must be a positive SCALAR integer.'); end
if(~isnumeric(maxargs)), error('First argument must be a positive scalar INTEGER.'); end
if(floor(maxargs) ~= maxargs), error('First argument must be a positive scalar INTEGER.'); end
if(maxargs <= 0), error('First argument must be a POSITIVE scalar integer.'); end
if(~iscell(inargs)), error('Second argument must be a CELL vector.'); end
if(~isvector(inargs) && ~isempty(inargs)), error('Second argument must be a cell VECTOR.'); end
if(~iscell(defaults)), error( 'Third argument must be a CELL vector.'); end
if(~isvector(defaults) && ~isempty(defaults)), error( 'Third argument must be a cell VECTOR.'); end
if(length(defaults) > maxargs), error(['Maximum maxargs (' num2str(maxargs) ...
') default values (now is ' num2str(length(defaults)) ').']);
elseif(nargout ~= maxargs+1), error(['There must be maxargs+1 (' num2str(maxargs+1) ...
') output arguments (now is ' num2str(nargout) ').']);
end
% Checks the number of inargs and uses default values if need be.
err= '';
if(length(inargs) < maxargs-length(defaults)) % To few arguments.
err= ['\n There must be at least ' num2str(maxargs-length(defaults)) ' arguments.'];
elseif(length(inargs) > maxargs) % To many arguments.
err= ['\n There must be at most ' num2str(maxargs) ' arguments.'];
elseif(length(inargs) < maxargs) % Set defaults.
inargs= {inargs{:} defaults{end-maxargs+length(inargs)+1 : end}};
end
defaults2= cell(1, maxargs);
defaults2(maxargs-length(defaults)+1:end)= defaults(:);
% Extra checks.
if(isempty(err)), [err, inargs]= extrachecks(defaults2, inargs, varargin{:}); end
% Ok, we are done. Convert \n to newlines. Organise output.
if(~isempty(err)), err= sprintf(['Error with input arguments: ' err]); end
varargout= {err inargs{:}};
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Extra checks.
function [err, inargs] = extrachecks(defaults, inargs, varargin)
err= '';
N= 1;
testlist= {varargin{:}, {}}; % In case the last index is omitted...
while(N < length(testlist))
tag= testlist{N};
ind= testlist{N+1};
N= N+1;
% Check the properties given.
if(~ischar(tag))
error(['Expected a string, but got a ' class(tag) ' in argument ' num2str(2+N) '.']);
elseif(isnumeric(ind)), N= N+1;
else ind= [];
end
if(isempty(ind)), ind= 1:length(inargs);
elseif(min(ind) < 1), error(['Non-positive index in argument ' num2str(2+N) '.']);
elseif(max(ind) > length(inargs))
error(['Index to large (max ' num2str(length(inargs)) ') in argument ' num2str(2+N) '.']);
end
% Some sizes/lengths are specific, find the value.
tagN= tag;
res= regexpi(tag, '^(length|mathlength|dim|size1|size2)=([0-9]+)$', 'tokens');
if(~isempty(res))
tagN= [res{1}{1} 'N'];
tagarg= str2double(res{1}{2});
end
% Some tags should be checked for other stuff too.
if(regexpi(tag, '^(math(length|lengthn|size)|square)$'))
testlist= {testlist{1:N-1} 'non-empty' ind 'numeric' ind testlist{N:end}};
end
% Time to do the test.
switch(lower(tagN))
case 'length' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, 'Arguments must have the same length', tag, ...
(celldo(inargs, ind, @length) == length(inargs{ind(1)})));
case 'mathlength' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ind= intersect(ind, find(~celldo(inargs, ind, @isscalar)));
err= errstr(ind, err, 'Arguments must have the same length (or be scalars)', tag, ...
(celldo(inargs, ind, @length) == length(inargs{ind(1)})));
case 'lengthn' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, ['Arguments must be of length ' num2str(tagarg)], tag, ...
(celldo(inargs, ind, @length) == tagarg));
case 'mathlengthn' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ind= intersect(ind, find(~celldo(inargs, ind, @isscalar)));
err= errstr(ind, err, ...
['Argument(s) must be of length ' num2str(tagarg) ' (or be scalar)'], ...
tag, (celldo(inargs, ind, @length) == tagarg));
case 'size' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, 'Arguments must have the same size', tag, samesize(inargs, ind));
case 'mathsize' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ind= intersect(ind, find(~celldo(inargs, ind, @isscalar)));
err= errstr(ind, err, 'Arguments must have the same size (or be scalar)', tag, ...
samesize(inargs, ind));
case 'size1n' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, ['Arguments must have ' num2str(tagarg) ' rows'], tag, ...
(cellsz(inargs, ind, 1) == tagarg));
case 'size2n' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, ['Arguments must have ' num2str(tagarg) ' columns'], tag, ...
(cellsz(inargs, ind, 2) == tagarg));
case 'non-empty' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, 'Argument(s) must not be empty', tag, ...
~celldo(inargs, ind, @isempty));
case 'scalar' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, 'Argument(s) must be scalar(s)', tag, ...
celldo(inargs, ind, @isscalar));
case 'vector' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, 'Argument(s) must be vector(s)', tag, ...
celldo(inargs, ind, @isvector));
case 'colvector' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, 'Argument(s) must be column vector(s)', tag, ...
(celldo(inargs, ind, @isvector) & (cellsz(inargs, ind, 2) == 1)));
case 'rowvector' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, 'Argument(s) must be row vector(s)', tag, ...
(celldo(inargs, ind, @isvector) & (cellsz(inargs, ind, 1) == 1)));
case 'square' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, 'Argument(s) must be matrices and square', tag, ...
(cellsz(inargs, ind, 1) == cellsz(inargs, ind, 2)) & ...
(celldo(inargs, ind, @ndims) == 2));
case 'dimn' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, ['Argument(s) must be of dimension ', num2str(tagarg)], tag, ...
(celldo(inargs, ind, @ndims) == tagarg));
% case 'numeric' in other! %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
case 'real' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= allvalues(ind, err, 'real', tag, inargs, @isreal);
case 'finite' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= allvalues(ind, err, 'finite', tag, inargs, @isfinite);
case 'int' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= allvalues(ind, err, 'integer', tag, inargs, (@(x) floor(x) == x));
case 'positive' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= allvalues(ind, err, 'positive', tag, inargs, (@(x) x > 0));
case 'non-positive' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= allvalues(ind, err, 'non-positive', tag, inargs, (@(x) x <= 0));
case 'negative' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= allvalues(ind, err, 'negative', tag, inargs, (@(x) x < 0));
case 'non-negative' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= allvalues(ind, err, 'non-negative', tag, inargs, (@(x) x >= 0));
case 'non-zero' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= allvalues(ind, err, 'non-zero', tag, inargs, (@(x) x ~= 0));
case 'proplist' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
[err, inargs]= proplists(err, tag, defaults, inargs, ind);
case 'directory' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ind2= find(celldo(inargs, ind, @ischar));
err= errstr(ind, err, 'Argument(s) must be paths to directories', tag, ...
(celldo(inargs, ind, @ischar) & celldo(inargs, ind2, @isdir)));
case 'cellstr' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, 'Argument(s) must be cell array of strings', tag, ...
celldo(inargs, ind, @iscellstr));
case 'string' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
err= errstr(ind, err, 'Argument(s) must be char or cellstr', tag, ...
(celldo(inargs, ind, @ischar) | celldo(inargs, ind, @iscellstr)));
otherwise %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% numeric, logical, char, float, single, double, cell, struct, sym, function_handle,
% integer, int8, uint8, int16, uint16, int32, uint32, int64, uint64
res= false(size(inargs));
for n = 1:length(ind), res(ind(n))= isa(inargs{ind(n)}, tagN); end
err= errstr(ind, err, ['Argument(s) must be of class ' tag ' (see ''help isa'')'], ...
tag, res);
end
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function err = errstr(ind, err, errmsg, tag, oklist)
res= setdiff(ind, find(oklist));
if(~isempty(res))
err= [err, '\n ' errmsg ': argument ' num2str(res) ' [' tag ']'];
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function res = celldo(args, ind, func)
res= zeros(size(args)); % Must be zeros, not NaN, false, or true...
for n = 1:length(ind), res(ind(n))= func(args{ind(n)}); end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function err = allvalues(ind, err, errmsg, tag, args, func)
res= NaN(size(args));
for n = 1:length(ind)
if(isnumeric(args{ind(n)})), res(ind(n))= all(all(all(func(args{ind(n)}))));
else res(ind(n))= false;
end
end
err= errstr(ind, err, ['Argument(s) must contain only ' errmsg ' numeric values'], tag, res);
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function res = cellsz(args, ind, dim)
res= NaN(size(args));
for n = 1:length(ind), res(ind(n))= size(args{ind(n)}, dim); end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function res = samesize(inargs, ind)
res= true(size(inargs));
if(~isempty(ind))
for N= 1:ndims(inargs{ind(1)})
res= res & (size(inargs{ind(1)}, N) == cellsz(inargs, ind, N));
end
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [err, args] = proplists(err, tag, defaults, args, ind)
res= false(size(args));
for n = 1:length(ind)
a= args{ind(n)};
a(1:2:end)= lower(a(1:2:end));
res(ind(n))= iscell(a) & ...
(bitand(length(a), 2) == 0) & ...
iscellstr(a(1:2:end));
if(res(ind(n))) % Check the ids and make a struct.
def= defaults{ind(n)};
if(isstruct(def))
nonos= setdiff(a(1:2:end), fieldnames(def));
if(~isempty(nonos))
%%%%%%%%% (STILL NEED TO DELIMIT THE NONOS...) %%%%%%%%%%%%%%%%%%%%
err= errstr(n, err, ['Undefined property id(s) ' nonos{:}], tag, false(size(args)));
end
elseif(~isempty(def))
error('To check a property value list the default must be a struct.');
end
if(~isempty(a)), for m = 2:2:length(a), def.(a{m-1})= a{m}; end; end
args{ind(n)}= def;
else
err= errstr(ind, err, 'Argument(s) must be list of property id/value pairs', tag, res);
end
end
end