No BSD License  

Highlights from
STAKK

from STAKK by Fabio Wegmann
STAKK stores workspaces in base and compares workspaces of functions in debugging phase.

stakk(varargin)
function stakkname = stakk(varargin)
% STAKK stores and compares workspaces of functions in runtime.
% STAKK is a function that helps debugging of data and programs. During
% development, there are often several versions of functions used in
% the main program and direct comparison of the functions' workspace
% is not easily possible between different program runs or at different 
% points in runtime. STAKK stores all or selected variables of the current 
% workspace in a struct in the base workspace for later retrieval and/or 
% comparison. It is possible to select variables by providing one 
% regular expression string (Matlab 7.0 or later).
% 
% You use STAKK by putting STAKK('pushall', ...) or STAKK('push', ...)
% into the code of functions you want to store the workspace at that time. 
% Later, in KEYBOARD/Debug mode or after the program exits you will find 
% the saved workspace in the base workspace as a structs with the default
% names 's1', 's2',... You may then invoke STAKK('compare', 's1', 's2')
% to quickly check whether s1 and s2 contain the same data in the same
% fields and which differences are found where. Note that this version 
% only checks equality in a limited, non-recursive way.
%
% Invoke STAKK with the following syntax: 
% 
% STAKK;            The same as STAKK('pushall');    
%
% STAKK('pushall'); Stores the whole workspace in a storage struct in base. 
%                   The default name of the storage struct is formed by
%                   concatenating the <storeprefix> string (currently 
%                   's') with a number, i.e. 's1'. If 's1' already exists,
%                   a unique name is created by appending increasing
%                   numbers ('s2','s3',...).
%
% STAKK('pushall', lagername); Stores the whole workspace in <lagername>.
%                   If <lagername> already exists, a unique name is created
%                   by appending increasing numbers.
%
% STAKK('push', regexp); Stores variables with names that fulfill a regular
%                   expression condition. Note that in contrast to 
%                   'whos('-regexp',...)' you may only give one regexp 
%                   string (but you are allowed to use the pipe '|'). This
%                   functionality works in Matlab 7.0 and later; see 
%                   'help regexp' for descriptions.
%
% STAKK('push', regexp, lagername); Stores variables with names that
%                   fulfill a regular expression condition in <lagername>.
%                   If <lagername> already exists, a unique name is created
%                   by appending increasing numbers. This functionality
%                   works in Matlab 7.0 and later.
%
% STAKK('compare', 'lager1', 'lager2'); Compares two previously stored 
%                   structs. A report is printed onto Matlab's command
%                   window. 
%
% STAKK('includeGlobals'); Include globals in subsequent calls of stakk.
%                   This is the default behavior.
% 
% STAKK('excludeGlobals'); Exclude globals in subsequent calls of stakk.
%
%
% The name of the storage struct is returned as a string (exception: 
% STAKK('includeGlobals') and STAKK('excludeGlobals') do not return any 
% objects). It is therefore allowed to use i.e. 
% STAKK('compare', 's1', stakk()); to compare s1 with the actual workspace. 
%
% see 'edit stakk' for further details.

% More Info:
%
% - Variables that are declared as 'global' in the caller's workspace
% are included by default, but can be excluded. Note that only those
% global variables are included which are declared as 'global' within
% the caller, not all global variables.
%
% - This version checks equality by using MatLab's ISEQUALWITHEQUALNANS,
% which works also for structs and cell arrays. In a next version, the
% structs and cells will be recursively checked and differences listed.
% 
% - Note that if STAKK's calling workspace is 'base', consecutive calls of
% STAKK contain all the previously stored structs as well (i.e. the first
% storage struct s1 will be in s2, and s1 and s2 will be in s3 etc...).
% You can remove them manually before invoking STAKK('compare',...) by
% using MatLab's function 'rmfield'; ie.: 's2 = rmfield(s2,'s1')', to 
% remove the storage struct s1 from s2.
% 
% - If you want to change the default name of the storage structs, you
% may manually change the persistent variable 'storeprefix' below. 
%
%
% Version 1.0.3, 09.03.05, made by 
% Fabio Wegmann with help from Laurent Cavin
%
% Please send bugs, feature requests etc. to fabio.wegmann (at) 
% ethz.ch
% You may also contact me if you want to be notified about new releases
% of STAKK.
% 
% 
% ideas for future improvements: 
% - recursive struct parsing and content comparison
%

persistent showGlobals storeprefix storenum;

storeprefix = 's'; % standard name prefix for stored stakk in base

if nargin > 3
    error('stakk.m: maximal 3 inputs. See ''help stakk'' for more information.');
end
inputcell = varargin;
numinputs = nargin;
if nargin == 0      % if STAKK is invoked as 'STAKK'
    numinputs = 1;
    inputcell{1} = 'pushall';
end

if isempty(showGlobals)
    showGlobals = true; % default: show globals 
end

tmpstakk = [];

switch numinputs
    case 1
        % includeGlobals, excludeGlobals, pushall
        switch inputcell{1}
            case 'includeGlobals'
                showGlobals = true;
            case 'excludeGlobals'
                showGlobals = false;
            case 'pushall'
                % stores all variables into a struct 
                if isempty(storenum)
                   storenum = 0; % part of lager name
                end
                storenum = storenum + 1;
                stakkname = [storeprefix int2str(storenum)];
                
                % build two lists of local and global vars
                allvar = evalin('caller','whos');
                globvar = evalin('caller','whos(''global'')');
                
                setall = {allvar.name}; % names of all vars in caller
                setglob = {globvar.name}; % names of all global vars in caller
                setglob = intersect(setglob, setall); % take only globals
                % that are declared global in caller's workspace
                [ignore, locidx] = setdiff(setall, setglob);
                locvar = allvar(locidx); % list of all local variables in caller
                 
                % if globals should be included...
                if showGlobals
                    for i = 1:length(setglob)
                        tmpval = evalin('caller', setglob{1});
                        tmpstakk.(setglob{1}) = tmpval;
                    end
                end

                % store locals in tmpstakk
                for i = 1:length(locvar)
                    tmpval = evalin('caller', locvar(i).name);
                    tmpstakk.(locvar(i).name) = tmpval;
                end
                
                % create unique stakkname in base and store stakk in base
                while evalin('base',['exist(''' stakkname ''')'])
                    storenum = storenum + 1;
                    stakkname = [storeprefix int2str(storenum)];
                end
                
                assignin('base', stakkname, struct(tmpstakk));

            otherwise
                error(['stakk.m: unidentifiable 1-parameter usage. See ''help stakk'' for more information']);
        end

    case 2
        % pushall(lagername), push(regexp)
        switch inputcell{1}
            case 'pushall'
                % store all variables in <lagername> in base
                lagername = inputcell{2};
                if isempty(storenum)
                    storenum = 0; % part of lager name
                end
                
                % build two lists of local and global vars
                allvar = evalin('caller','whos');
                globvar = evalin('caller','whos(''global'')');
                
                setall = {allvar.name}; % names of all vars in caller
                setglob = {globvar.name}; % names of all global vars in caller
                setglob = intersect(setglob, setall); % take only globals
                % that are declared global in caller's workspace
                [ignore, locidx] = setdiff(setall, setglob);
                locvar = allvar(locidx); % list of all local variables in caller
                
                
                % if globals should be included...
                if showGlobals
                    for i = 1:length(setglob)
                        tmpval = evalin('caller', setglob{1});
                        tmpstakk.(setglob{1}) = tmpval;
                    end
                end

                % store locals in tmpstakk
                for i = 1:length(locvar)
                    tmpval = evalin('caller', locvar(i).name);
                    tmpstakk.(locvar(i).name) = tmpval;
                end
                
                % check if lagername in base is unique
                while evalin('base',['exist(''' lagername ''')'])
                    storenum = storenum + 1;
                    neulager = [lagername int2str(storenum)];
                    warning('stakk:NAME',...
                        'stakk.m: the lagername %s already exists in base! I try %s', ...
                        lagername, neulager);
                    lagername = neulager;
                end
                
                % store stakk in base under unique name lagername
                assignin('base', lagername, struct(tmpstakk));
                stakkname = lagername; % copy return value

            case 'push'
                % pushes regexp-selected variables only
                if isempty(storenum)
                   storenum = 0; % part of lager name
                end
                storenum = storenum + 1;
                stakkname = [storeprefix int2str(storenum)];
                regulexp = inputcell{2};
                
                ver = version; % check whether '-regexp' in 'whos' below works...
                if str2num(ver(1))<7
                    error('stakk.m: regular expression works in Matlab 7.0 and greater.');
                end

                
                % build two lists of local and global vars
                allvar = evalin('caller',['whos(''-regexp'', ''' regulexp ''')']);
                globvar = evalin('caller',['whos(''global'', ''-regexp'', ''' regulexp ''')']);
                
                setall = {allvar.name}; % names of all vars in caller
                setglob = {globvar.name}; % names of all global vars in caller
                setglob = intersect(setglob, setall); % take only globals
                % that are declared global in caller's workspace
                [ignore, locidx] = setdiff(setall, setglob);
                locvar = allvar(locidx); % list of all local variables in caller
                
                
                % if globals should be included...
                if showGlobals
                    for i = 1:length(setglob)
                        tmpval = evalin('caller', setglob{1});
                        tmpstakk.(setglob{1}) = tmpval;
                    end
                end

                % store locals in tmpstakk
                for i = 1:length(locvar)
                    tmpval = evalin('caller', locvar(i).name);
                    tmpstakk.(locvar(i).name) = tmpval;
                end
                
                % create unique stakkname in base and store stakk in base
                while evalin('base',['exist(''' stakkname ''')'])
                    storenum = storenum + 1;
                    stakkname = [storeprefix int2str(storenum)];
                end
                
                assignin('base', stakkname, struct(tmpstakk));
                
            otherwise
                error('stakk.m: unidentifiable 2-parameter usage. See ''help stakk'' for more information');
        end
    case 3
        % push(regexp, lagername), compare(lager1, lager2)
        switch inputcell{1}
            case 'push'
                % pushes regexp-selected variables in <lagername>
                
                ver = version; % check whether '-regexp' in 'whos' below works...
                if str2num(ver(1))<7
                    error('stakk.m: regular expression works in Matlab 7.0 and greater');
                end
                
                regulexp = inputcell{2}
                lagername = inputcell{3}
                if isempty(storenum)
                   storenum = 0; % part of lager name
                end
                storenum = storenum + 1;
                                
                % build two lists of local and global vars
                allvar = evalin('caller',['whos(''-regexp'', ''' regulexp ''')']);
                globvar = evalin('caller',['whos(''global'', ''-regexp'', ''' regulexp ''')']);
                
                setall = {allvar.name}; % names of all vars in caller
                setglob = {globvar.name}; % names of all global vars in caller
                setglob = intersect(setglob, setall); % take only globals
                % that are declared global in caller's workspace
                [ignore, locidx] = setdiff(setall, setglob);
                locvar = allvar(locidx); % list of all local variables in caller
                
                
                % if globals should be included...
                if showGlobals
                    for i = 1:length(setglob)
                        tmpval = evalin('caller', setglob{1});
                        tmpstakk.(setglob{1}) = tmpval;
                    end
                end

                % store locals in tmpstakk
                for i = 1:length(locvar)
                    tmpval = evalin('caller', locvar(i).name);
                    tmpstakk.(locvar(i).name) = tmpval;
                end
                
                % check if lagername in base is unique
                while evalin('base',['exist(''' lagername ''')'])
                    storenum = storenum + 1;
                    neulager = [lagername int2str(storenum)];
                    warning('stakk:NAME',...
                        'stakk.m: the lagername %s already exists in base! I try %s', ...
                        lagername, neulager);
                    lagername = neulager;
                end
                
                % store stakk in base under unique name lagername
                assignin('base', lagername, struct(tmpstakk));
                stakkname = lagername; % copy return value
                
            case 'compare'
                % compare two different stakks
                lager1 = inputcell{2};
                lager2 = inputcell{3};
                if ~(strcmp(class(lager1),'char') && strcmp(class(lager2),'char'))
                    error('stakk.m: use compare with two struct names given as char.');
                end
                
                % get actual & set new format spacing
                formspace = get(0,'FormatSpacing');
                format('compact');
                
                % check whether the two lagers are existing and of the
                % correct type
                chk1 = evalin('base',['exist(''' lager1 ''')']);
                chk2 = evalin('base',['exist(''' lager2 ''')']);
                if ~(chk1 && chk2)
                    error('stakk.m: non-existing %s and %s! Comparison not possible.', lager1, lager2);
                end
                chk1 = evalin('base',['isstruct(' lager1 ')']);
                chk2 = evalin('base',['isstruct(' lager2 ')']);
                if ~(chk1 && chk2)
                    error('stakk.m:  %s and %s must be structs! Comparison not possible.', lager1, lager2);
                end
                
                % get fieldnames of each struct
                names1 = evalin('base',['fieldnames(' lager1 ')']);
                names2 = evalin('base',['fieldnames(' lager2 ')']);
                disp(['Total number of variables in '...
                    lager1 ': ' num2str(length(names1))]);
                disp(['Total number of variables in '...
                    lager2 ': ' num2str(length(names2))]);

                % check (exclusive-or) objects of both sets
                [xornames, id1, id2] = setxor(names1, names2);
                if isempty(xornames)
                    disp(['Identical list of variable names in '...
                        lager1 ' and ' lager2 '.']);
                    diffnames = 0;
                else
                    disp(['Different list of variable names in '...
                        lager1 ' and ' lager2 '.']);
                    if ~isempty(id1)
                        disp([num2str(length(id1)) ...
                            ' variables exclusively present in ' lager1 ':']);
                        disp(names1(id1));
                    end
                    if ~isempty(id2)
                        disp([num2str(length(id2)) ...
                            ' variables exclusively present in ' lager2 ':']);
                        disp(names2(id2));
                    end
                    diffnames = length(xornames);
                end
                disp('-----------------------------------------------');

                % check contents of variables with common names
                [andnames, id1, id2] = intersect(names1, names2);
                numnames = length(andnames); % number of variables in intersection
                diffcont = 0; % number of different contents
                difftype = 0; % number of different types
                diffdims = 0; % number of different dimensions
                diffsize = 0; % number of different sizes
                difffields = 0; % number of structs with different fields
                
                for i = 1:length(andnames)
                    var1 = evalin('base',[lager1 '.' andnames{i}]);
                    var2 = evalin('base',[lager2 '.' andnames{i}]);
                    typ1 = evalin('base',['class(' lager1 '.' andnames{i} ')']);
                    typ2 = evalin('base',['class(' lager2 '.' andnames{i} ')']);
                    size1 = evalin('base',['size(' lager1 '.' andnames{i} ')']);
                    size2 = evalin('base',['size(' lager2 '.' andnames{i} ')']);
                    dimstri1 = sprintf('%dx',size1);
                    dimstri1 = dimstri1(1:end-1);
                    dimstri2 = sprintf('%dx',size2);
                    dimstri2 = dimstri2(1:end-1);
                    if size1(1)>1
                        firstrow1 = ' (first row below)';
                    else
                        firstrow1 = '';
                    end
                    if size2(1)>1
                        firstrow2 = ' (first row below)';
                    else
                        firstrow2 = '';
                    end

                    if strcmp(typ1,typ2)==0  % same name but different type
                        disp(['Variable found with different type:    ' andnames{i}]);
                        disp([' in ' lager1 ': ' dimstri1 ' ' class(var1) firstrow1]);
                        if ~isempty(var1), disp(var1(1,:)); end
                        disp([' in ' lager2 ': ' dimstri2 ' ' class(var2) firstrow2]);
                        if ~isempty(var2), disp(var2(1,:)); end
                        difftype = difftype + 1;
                    else % same name and type, probably different size & content
                        if length(size1) == length(size2) % same num of dims?
                            sizecomp = size1 == size2; % compare sizes
                            numdiffsize = length(size1)-sum(sizecomp);...
                                % number of differently-sized dimensions
%                             disp([andnames{i} ': matching number of dimensions in '...
%                                lager1 ' and ' lager2 '.']);

                            switch typ1
                                case 'struct'
                                    fields1 = evalin('base',['fieldnames('...
                                        lager1 '.' andnames{i} ')']);
                                    fields2 = evalin('base',['fieldnames('...
                                        lager2 '.' andnames{i} ')']);
                                    [xorfields, if1, if2] = setxor(fields1, fields2);
                                    if isempty(xorfields)
%                                         disp([' Equal 1st-level fieldnames found in struct: '...
%                                             andnames{i}]);
                                        if ~isequalwithequalnans(var1, var2)
                                            diffcont = diffcont + 1;
                                            disp(['Different contents found in struct:    ' andnames{i}]);
                                            disp(['  (idential 1st-level fieldnames)']);
                                            % disp(['  in ' lager1 ': ']);
                                            % disp(var1);
                                            % disp(['  in ' lager2 ': ']);
                                            % disp(var2);
                                        end
                                    else % structs with different fieldnames
                                        difffields = difffields + 1;
                                        disp(['Different 1st-level fieldnames found in struct    '...
                                                andnames{i}]);
                                        if ~isempty(if1)
                                            disp([ '  only in ' lager1 ': ']);
                                            disp(fields1(if1));
                                        end
                                        if ~isempty(if2)
                                            disp([ '  only in ' lager2 ': ']);
                                            disp(fields2(if2));
                                        end
                                    end
                                otherwise
                                    if ~isequalwithequalnans(var1, var2)
                                        diffcont = diffcont + 1;
                                        disp(['Difference found in variable:    ' andnames{i}]);
                                        
                                        disp([' in ' lager1 ': ' dimstri1 ' ' class(var1) firstrow1]);
                                        if ~isempty(var1), disp(var1(1,:)); end
                                        disp([' in ' lager2 ': ' dimstri2 ' ' class(var2) firstrow2]);
                                        if ~isempty(var2), disp(var2(1,:)); end
                                    end
                            end
                        else % same name & type, but different length(size(...))
                            diffdims = diffdims + 1;
                            disp(['Different number of dimensions for ' andnames{i}]);
                            disp([' in ' lager1 ': ' dimstri1 ' ' class(var1) ': (first row given)']);
                            if ~isempty(var1), disp(var1(1,:)); end
                            disp([' in ' lager2 ': ' dimstri2 ' ' class(var2) ': (first row given)']);
                            if ~isempty(var2), disp(var2(1,:)); end
                        end
                    end
                end

                disp('-----------------------------------------------');
                if ~diffcont && ~diffnames && ~diffdims && ~difffields
                    disp(['The two storage structs ' lager1 ' and ' lager2 ' are identical.']);
                else
                    disp([num2str(diffnames) ' variables are found only in one of the two storage structs.']);
                    disp(['A total of ' num2str(diffcont) ...
                        ' different contents have been found within the']);
                    disp(['total ' num2str(numnames) ...
                        ' variables of the intersection of ' lager1 ...
                        ' and ' lager2 '.']);
                    disp([num2str(diffdims)...
                        ' variables have a different number of dimensions, and']);
                    disp([num2str(difffields)...
                        ' have different fieldnames (contents were not compared).']);
                end
                
                % restore previous formatspacing
                format(formspace);
            otherwise
                error('stakk.m: unidentifiable 3-parameter usage. See ''help stakk'' for more information');
        end
end
% En Taro Adun!

Contact us at files@mathworks.com