image thumbnail

mklib: a pedestrian function library loader generator

by

 

05 May 2005 (Updated )

creates a function library loader from a list of m-files (possibly with subfunctions)

[lib,p]=mklib(lnam,varargin)
% LR = mklib(LN,MF1,...,MFn)
%	to create a library loader file LR.m
%	for all subfunctions found in file(s) MF|1:n|
%
% MF:	valid MATLAB file(s) with function(s)
%	valid formats for each MF|1:n|:
%		strings			'file'
%		cell array of strings	{'file1','file2',...'fileN'}
%	determination of the location of MFs
%		froot			by <which(MF)>
%		MLpath/froot		by <which(MF)>
%		froot.m			in current folder
%		fpath/froot.m		in fpath   folder
%		fpath/froot		file-not-found entry in LR
% LN:	name of the library loader LR.m
% LR:	root name of the library loader
%	library loaders are always created in the current folder
%		fh = LR (literally!)   |   fh = feval(LR)
%			will assign handles from MF subfunctions
%			to structure <fh> with fieldnames set to
%			the name of the resp.ctive subfunction
%		LR   |   help LR   |   help(LR)
%			shows the auto-created help section
%			duplicates and nested functions are listed
%
% NOTE
% -	MKLIB removes an existing library loader at startup!
% -	MKLIB aborts if any
%		MF is a library loader
%		MF.subfunction_name == LN
% -	MKLIB discards ALL subfunctions in a MF by block commenting if any
%		MF.subfunction_name == previous MF.subfunction_name
% -	nested functions are listed but not assigned
% -	users are responsible for proper code termination
%	of all MFs according to conventions (END|RETURN)
% -	MKLIB uses an undocumented feature of <mlint>
%
% EXAMPLE
%%	assume function <flib.m> is in the current folder
%		function r=mysin(v)
%			 r=sind(v);
%		function r=mysum(v)
%			 r=sum(v);
%%	create the library loader <goo.m>
%		lib = mklib('goo','flib');
%%	assign the function handles
%		fh = goo	% or: fh = feval(lib);
%%		fh = 
%%			mysin: @mysin
%%			mysum: @mysum
%%	use a subfunction
%		fh.mysin(0:30:90)
%%	create an alias
%		fa = fh.mysum;
%		fa([30 60])

% created:
%	us	05-May-2005
% modified:
%	us	05-Jan-2006 17:45:18

%--------------------------------------------------------------------------------
function	[lib,p]=mklib(lnam,varargin)

		lrot=[];
		p.magic='MKLIB';
		p.ver='05-Jan-2006 17:45:18';
		p.rel=version;
		p.par=[];
		p.lrot=lrot;
		p.nf=0;
		p.nc=0;
		p.ca=[0 0];
		p.bl=0;
		p.nl=[{'M'},{'S'},{'U'},{'N'}];
		p.nm=zeros(size(p.nl));
		p.f=[];
		p.c=[];
		p.b=[];
		p.s=[];
	if	nargout
		lib=lrot;
	end
	if	nargin < 2
		help(mfilename);
		return;
	end

% create appropriate loader file name in CURRENT folder!
		[lpat,lrot,lext]=fileparts(lnam);
		lnam=fullfile(cd,[lrot,'.m']);
	if	exist(lnam,'file') == 2
		disp(sprintf('MKLIB> loader deleted!  %s',lnam));
		delete(lnam);
	end
		p.lrot=lrot;

% common parameters
		par.cr=		sprintf('\n');
		par.tab=	sprintf('\t');
% library templates header/footer
		par.eofun=	 '___@ENDOFFUNCTION@___';
		par.nrfile=	 '___@MKLIBALLFILES@___';
		par.nrsub=	 '___@MKLIBALLFUNCTIONCALLS@___';
		par.nrall=	 '___@MKLIBALLFUNCTIONS@___';
		par.nrass=	 '___@MKLIBALLFUNCTIONSASSIGNED@___';
		par.nrfun=	 '___@MKLIBNRFUNCTIONS@___';
		par.nrcall=	 '___@MKLIBNRCALLS@___';
		par.valfun=	 '___@MKLIBNRVALIDFUNCTIONSFOUND@___';
		par.buflen=	 '___@MKLIBFILEBUFFERLENGTH@___';
		par.fmtf=	['%% file %7d :' par.tab '%s %s'];
		par.fmts=	['%% %5d:%6d =' par.tab '%s %s'];
		par.fmtd=	['%% %5d:%6d !' par.tab '%s %s'];
		par.magic=	['% MAKELIB      :' par.tab 'a function library loader generator'];
		par.hdr={
				par.magic
				['% version      :' par.tab p.ver]
				['% ML release   :' par.tab p.rel]
				['% -------------']
				['% OUTPUT       >']
				['% library      :' par.tab lnam]
				['% created      :' par.tab datestr(clock)]
				['% -------------']
				['% INPUT        <']
				['% files        :' par.tab par.nrfile]
				['% routines     :' par.tab par.nrsub]
				['%  subroutines :' par.tab par.nrall]
				['%   assigned   :' par.tab par.nrass]
				['%  other       :' par.tab par.nrcall]
				['% lines        :' par.tab par.buflen]
				['% -------------']
		};
		par.fcn={
				par.cr
				['function' par.tab 'fh=' lrot '(varargin)']
				[par.tab 'if' par.tab '~nargout']
				[par.tab par.tab 'help(mfilename);']
				[par.tab 'else']
				[par.tab par.tab 'fh=[];']
		};
		par.ftr={
				[par.tab par.tab par.eofun];
				par.cr
		};
		p.par=par;

% prepare file list
		argv={};
	for	i=1:nargin-1
	if	iscell(varargin{i})
		targ=varargin{i};
		argv=[argv;targ(:)];
	else
		argv=[argv;varargin(i)];
	end
	end
		argv=argv(:);
		argc=numel(argv);
		flst={};
		com={};
		cc=0;
		nc=0;
		mc=0;

% read file(s) and create list of functions
	for	j=1:argc
		fin=argv{j};
		p=get_functions(j,fin,p);
	end
		p=prune_functions(p);

	for	j=1:p.nf
		cmc=0;
		cna=0;
		cnc=0;
		cnd=0;
		cns=0;
		f=p.f(j,:);
		fnam=f{2};
		sx=find([p.c{:,1}]==j);
		s=p.c(sx,:);
		ns=length(sx);
		par.hdr{end+1,1}=sprintf(par.fmtf,j,fnam);

	if	~isempty(s)
	if	f{6} == -1
		par.hdr{end+1,1}=['%  subroutines :' par.tab '0' par.tab par.tab 'script?'];
		disp(sprintf('MKLIB> no subfunctions  %s',fnam));
	else
		par.hdr{end+1,1}=['%  subroutines :' par.tab par.nrfun];

	for	i=1:ns
		snam=s{i,3};
	if	strcmp(snam,lrot)
		disp(        'MKLIB> conflict         subfunction == library name');
		disp(sprintf('                        %s > %s',fnam,snam));
		lrot=[];
		return;
	end
		ss=s{i,5};
	switch	ss
	case	-10
		mc=mc+1;
		cmc=cmc+1;
		cns=cns+1;
		disp(sprintf('MKLIB> -nested          %s > %s',fnam,snam));
		par.hdr{end+1,1}=sprintf(par.fmts,-mc,-cmc,'-nested',[par.tab snam]);
	case	{-1 -2 1 10 2 20}
		nc=nc+1;
		cnc=cnc+1;
		cns=cns+1;
	if	ss < 0
		cnd=cnd+1;
		p.ca(2)=p.ca(2)+1;
		disp(sprintf('MKLIB> -duplicate       %s > %s',fnam,snam));
		par.hdr{end+1,1}=sprintf(par.fmtd,-nc,-cnc,'-duplicate',[par.tab snam]);
	else
		p.ca(1)=p.ca(1)+1;
		cc=cc+1;
		cna=cna+1;
		disp(sprintf('MKLIB> +handle          %s > %s',fnam,snam));
		par.hdr{end+1,1}=sprintf(par.fmts,nc,cnc,'+handle',[par.tab snam]);
		com{cc,1}=sprintf('%s%sfh.%s=@%s;',par.tab,par.tab,snam,snam);
	end
	end
	end	% list of subroutines

		par.hdr=strrep(par.hdr,par.nrfun,sprintf('\t\t%-1d [SUB %-1d: NST -%-1d/DUP -%-1d]',cna,cns,cmc,cnd));

	end	% list of functions
	end
	end	% list of files

		ftr=strrep(par.ftr,par.eofun,'end');
		par.hdr=strrep(par.hdr,par.nrcall,sprintf('%-1d',p.nm(3)));
		par.hdr=strrep(par.hdr,par.nrfile,sprintf('%-1d',p.nf));
		par.hdr=strrep(par.hdr,par.nrsub,sprintf('%-1d',p.nc));
		par.hdr=strrep(par.hdr,par.nrall,sprintf('%-1d\t[SUB: %-1d / NST: %-1d]',nc+mc,nc,mc));
		par.hdr=strrep(par.hdr,par.nrass,sprintf('%-1d\t[SUB: %-1d / DUP: %-1d]',p.ca(1),nc,p.ca(2)));
		par.hdr=strrep(par.hdr,par.buflen,sprintf('%-1d',p.bl));

		tcom=com;
		tcom{end+1,1}=[par.tab 'end'];
		com=char([par.hdr;par.fcn;tcom;ftr;p.b]);
		trm=repmat(par.cr,size(com,1),1);
		com=[com,trm];

		lnam=write_loader(lnam,com,p);
		lint=mlint(lnam,'-id');
	if	~isempty(lint)
		id=struct2cell(lint);
		id=strmatch('SyntaxErr:EndCount',id(end,:).');
	if	~isempty(id)
		ftr=strrep(par.ftr,par.eofun,'return');
		com=char([par.hdr;par.fcn;tcom;ftr;p.b]);
		com=[com,trm];
		lnam=write_loader(lnam,com,p);
	end
	end

		disp(sprintf('MKLIB> loader created   %s',lnam));
	if	nargout
		lib=lrot;
	end
		return;
%--------------------------------------------------------------------------------
% SUBROUTINES
%--------------------------------------------------------------------------------
function	[fnam,buf]=read_file(fin,p)

		buf=[];
		[fpat,frot,fext]=fileparts(fin);
	if	isempty(fpat) && isempty(fext)
		[fpat,frot,fext]=fileparts(which(fin));
	end
		fnam=fullfile(fpat,[frot,fext]);
	if	exist(fnam) ~= 2
		disp(sprintf('MKLIB> file not found   %s',fin));
		fnam=fin;
		return;
	elseif	isempty(fext)
		fnam=fullfile(fpat,[frot,'.m']);
	end
		[fp,msg]=fopen(fnam,'rt');
	if	fp > 0
		buf=fread(fp,inf,'*char').';
		fclose(fp);
	while	~isempty(buf) & isspace(buf(end))
		buf=buf(1:end-1);
	end
	else
		disp(sprintf('MKLIB> could not open   %s',fin));
		fnam=fin;
		return;
	end
	if	strfind(buf,p.par.magic) == 1
		error('MKLIB> input is already a library loader\n       %s',fnam);
	end
		return;
%--------------------------------------------------------------------------------
function	lnam=write_loader(lnam,com,p)

		[fp,msg]=fopen(lnam,'wb');
	if	fp > 0
		fwrite(fp,com.','char');
		fclose(fp);
	else
		error([msg,': ',p.par.tab,lnam]);
	end
	while	exist(lnam,'file') ~= 2
		pause(0);	% allow for break!
	end
		return;
%--------------------------------------------------------------------------------
function	p=get_functions(ix,fin,p)

		ns=0;
		s={};
		[fnam,buf]=read_file(fin,p);
		hasbuf=true;
	if	isempty(buf)
		hasbuf=false;
		buf='empty';
		fnam=[fnam ' (not found or empty)'];
	else
% retrieve functions from <mlint> via an undocumented option
		txt=sprintf('mlint(''-calls'',''%s'');',fnam);
		s=evalc(txt);
		s=strread(s,'%s','delimiter','');
		p.s=[p.s;s];
	end
	if	hasbuf
		tbuf=		strread(buf.','%s',...
 				'whitespace','',...
				'delimiter','');
 		buf=[	{sprintf('%% %s',fnam)}
				'LINE'
				'COMMENT BEGIN'
				tbuf
				'COMMENT END'
		];
		p.bl=p.bl+numel(tbuf);
	else
		buf=[	{sprintf('%% %s',fnam)}
				'LINE'
		];
	end
		buf{2}=['% ' repmat('-',1,size(fnam,2))];

% ... decode function specs
		pat=sprintf('^%s|',p.nl{:});
		pat=pat(1:end-1);
		sx=regexp(s,pat);
		sx=find(~cellfun('isempty',sx));
		s=s(sx);
		m=regexp(s,'^\w','match');	% function type
		s=regexp(s,'\w+$','match');	% function name
		m=[m{:}].';
		s=[s{:}].';
		ns=size(s,1);

		nb=size(p.b,1);
		sb=size(buf,1);
		ixb=nb+1;
		ixe=nb+sb;
% ... accumulate func specs
		ixm=[];
	for	i=char(p.nl(:)).'
		ixm=[ixm,numel(find(strcmp(m,i)))];
	end
	if	~ixm(1) && any(ixm)
		ixm(1)=-1;
	elseif	~any(ixm)
		ixm(1)=-2;
	end
		p.nm=p.nm+ixm;
		p.nf=p.nf+1;
		p.f=[p.f;{ix,fnam,ixb,ixe,any(ixm)},num2cell(ixm)];
		ixf=repmat({ix},ns,1);
		ixs=num2cell(1:ns).';
		ixb=repmat(num2cell(ixb),ns,1);
		ixe=repmat(num2cell(ixe),ns,1);
		ixm=repmat({ixm},ns,1);
		p.nc=p.nc+numel(s);
		p.c=[p.c;[ixf,ixs,s,m]];

% ... accumulate func contents
		p.b=[p.b;buf];
		return;
%--------------------------------------------------------------------------------
function	p=prune_functions(p)

%	nr	spec
%	--	-----------------
%	1	M unique
%	10	M with duplicates
%	-1	M duplicate
%	2	S unique
%	20	S with duplicates
%	-2	S duplicate
%	-10	N
%	-20	U

		par=p.par;
	if	~p.nc
		return;
	end
		p.c=[p.c,repmat({0},size(p.c,1),1)];
		ix=find(strncmp('N',p.c(:,4),1));
		p.c(ix,5)={-10};
		ix=find(strncmp('U',p.c(:,4),1));
		p.c(ix,5)={-20};

% mark duplicates
		ix=find(strncmp('M',p.c(:,4),1));
		p.c(ix,5)={1};
	for	i=ix(:).'
		s=p.c{i,3};
		id=strmatch(s,p.c(ix,3));
	if	length(id) > 1
		p.c(ix(id(1)),5)={10};
	end
		p.c(ix(id(2:end)),5)={-1};
	end

		ix=find(strncmp('S',p.c(:,4),1));
		p.c(ix,5)={2};
	for	i=ix(:).'
		s=p.c{i,3};
		id=strmatch(s,p.c(ix,3));
	if	length(id) > 1
		p.c(ix(id(1)),5)={20};
	end
		p.c(ix(id(2:end)),5)={-2};
	end

% mark buffer
		mrk=[p.c{:,end}];
% remove any function with duplicates using block-commenting
		ix=find(abs(mrk)==1  |abs(mrk)==2);
	for	i=1:2
		ix=find(mrk==-i);
		fn=[p.c{ix,1}];
	for	j=fn(:).'
		ixb=p.f{j,3}+2;
		ixe=p.f{j,4};
		p.b([ixb ixe])={'%{','%}'};
	end
	end

% tag scripts
		ix=find([p.f{:,6}]==-1);
	for	i=ix(:).'
		ixb=p.f{i,3}+2;
		ixe=p.f{i,4};
		p.b([ixb ixe])={'%{','%}'};
	end
% tag valid function entries
		mrk=[p.c{:,end}];
		ix=find(mrk>0);
		fn=unique([p.c{ix,1}]);
	for	i=fn(:).'
		ixb=p.f{i,3}+2;
		ixe=p.f{i,4};
		p.b([ixb ixe])={par.valfun};
	end
		ix=strmatch(par.valfun,p.b,'exact');
		p.b(ix)=[];
		return;
%--------------------------------------------------------------------------------

Contact us