function linevals = missingsemicolons(targetfiles, suppress_subdir_eval)
% LINEVALS = MISSINGSEMICOLONS(TARGETFILES, SUPPRESS_SUBDIR_EVAL)
%
% Returns in 'linevals' a structure containing the line numbers and
% offending strings of lines that are likely candidates for missing
% terminal semicolon. Lines commencing with keywords that typically
% do not produce an output at the command line
% (e.g., 'if', 'return', 'continue', etc.) are skipped.
%
% TARGETFILES: Input may be either a string indicating a path to a
% single mfile (e.g., 'aviplayer.m', 'c:\brett\targetfiless\aviplayer.m'),
% a directory of files (eg. dir('*.m')), OR a name/path to the target
% directory. If a directory is input, the program examines all files in
% the directory AND, by default, subdirectories, compiling in a
% single structure the individual output for each file. To suppress
% inclusion of subdirectories, enter 1 for the optional second argument.
%
% SUPPRESS_SUBDIR_EVAL (optional; default = 0): By default, subdirectories
% are included if TARGETFILES is the name of a directory. To suppress this
% behavior, enter a 1 for this argument. (If TARGETFILES is NOT a directory,
% this argument is ignored.)
%
% EXAMPLES:
% suspects = missingsemicolons('aviplayer.m')
%
% a=dir('keyword*.m'); suspects = missingsemicolons(a);
%
% suspects = missingsemicolons('c:\brett\mfiles\afm');
%
% This function uses regular expressions, and thus will not work in
% versions prior to 6.5. In the "credit-where-credit-is-due" category,
% please note that this function calls Peter Acklam's excellent function
% MLSTRIPCOMMENTS, which is available for download from ML Central. I could
% not have written this program without Peter's assistance.
%
% PERFORMANCE NOTE: If you are still getting extraneous output at the
% command line after addressing all instances of forgotten semicolons
% returned by missingsemicolons.m, consider commands "hidden within
% strings." For instance, eval('a=1') will produce output, but will not be
% caught by this program. Similarly, output generated by GUIcallbacks enclosing
% strings will generally not be detected by missingsemicolons.m.
%
% Brett Shoelson, Ph.D.
% shoelson@helix.nih.gov
% 6/25/04
if ~nargin | nargin > 2
error(sprintf('You must enter one or two arguments. The first may be the name of a single targetfiles,\nor a (structure) containing the a directory of targetfiless,\nor the name of a directory.\nThe second (optional) argument suppresses recursive analysis of subdirectories. (See HELP comments for details.)'));
elseif nargin == 1
suppress_subdir_eval = 0;
end
if ~isstruct(targetfiles) && strcmp(targetfiles,'mstmpfile.m')
error('Please do not try to examine this temporary file!');
end
linevals = [];
if isstruct(targetfiles)
for ii = 1:length(targetfiles)
fprintf('Examining file %s (%i of %i)....\n',targetfiles(ii).name,ii,length(targetfiles));
if ~strcmp(targetfiles(ii).name,'mstmpfile.m')
linevals = examinefile(targetfiles(ii).name,linevals);
end
end
elseif isdir(targetfiles)
if ~suppress_subdir_eval
paths = genpath(targetfiles);
%Find instances of the path separator
seplocs = findstr(paths,pathsep);
%Parse paths into individual subdirectories
if ~isempty(seplocs)
directories = cell(length(seplocs),1);
directories{1} = paths(1:seplocs(1)-1);
for ii = 1:length(seplocs)-1
directories{ii+1} = paths(seplocs(ii)+1:seplocs(ii+1)-1);
end
end
else
directories = {targetfiles};
end
for ii = 1:length(directories)
tmp = dir([directories{ii},'\*.m']);
for jj = 1:length(tmp)
if ~strcmp(tmp(jj).name,'mstmpfile.m')
fprintf('Examining file %s....\n',tmp(jj).name);
linevals = examinefile(tmp(jj).name,linevals);
end
end
end
elseif strcmp(class(targetfiles),'char')
linevals = examinefile(targetfiles,linevals);
else
error('Inappropriate input argument. Enter a single string or a structure.');
end
function [linevals] = examinefile(fname,linevals)
mlstripcommentsfile(fname,'mstmpfile.m');
fid = fopen('mstmpfile.m', 'r');
if fid == -1
fprintf('Unable to open/examine file %s. It will be skipped.\n', fname);
return
end
lnum = 0;
while 1
lnum = lnum+1;
if lnum == 1, last3 = [];else,if length(tmpline)>2,last3 = tmpline(end-2:end);else last3 = [];end;end
if strcmp(last3,'...'),prevline = tmpline(1:end-3);else,prevline = [];end
tmpline = fgetl(fid);
if ~ischar(tmpline),break, end
% Remove leading whitespace:
tmpline = regexprep(tmpline, '^\s+', '');
% Remove trailing whitespace:
tmpline = regexprep(tmpline, '\s+$', '');
if ~isempty(tmpline)
space = findstr(' ',tmpline);
if isempty(space),space = length(tmpline)+1;end
openparen = findstr('(',tmpline);
if isempty(openparen),openparen = length(tmpline)+1;end
openbracket = findstr('[',tmpline);
if isempty(openbracket),openbracket = length(tmpline)+1;end
opencurly = findstr('{',tmpline);
if isempty(opencurly),opencurly = length(tmpline)+1;end
space = min([min(space),min(openparen),min(openbracket),min(opencurly)]);
keyword = tmpline(1:space-1);
if ~isempty(prevline),tmpline = [prevline,tmpline];continue;end
if length(tmpline)>2,tmp=tmpline(end-2:end);else,tmp=[];end
if ~strcmp(tmpline(end),';') & ~strcmp(tmp,'...') & ~ismember(keyword,...
{'if','for','function','disp','while','end','return','global','else','continue','elseif','try','catch',...
'switch','case','break','continue','otherwise','warning','set','uicontrol','persistent','clear','load',...
'fprintf','cd','drawnow','beep','pause','error','delete','release','eval',...
'setappdata','orient','close','otherwise','hold','cla','clc'})
if isempty(tmpline)
beep
pause
end
if isempty(linevals)
linevals = struct('Filename',fname,'LineNumber',lnum,'String',tmpline);
else
linevals(length(linevals)+1).Filename = fname;
linevals(length(linevals)).LineNumber = lnum;
linevals(length(linevals)).String = tmpline;
end
end
end
end
fclose(fid);
try
delete('mstmpfile.m');
end
return