#! /usr/bin/env python # $URL: https://repos.deltares.nl/repos/openda/openda_1/public/trunk/core/native/src/cta/cta_vector.c $ # $Revision: 2751 $, $Date: 2011-09-09 08:58:46 +0200 (Fri, 09 Sep 2011) $ # # COSTA: Problem solving environment for data assimilation # Copyright (C) 2012 Arjo Segers # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ Generate Fortran90 interfaces from function headers. Tool for OpenDA native core (COSTA). Uses: explicit and implicit tags: implicit: header files containing usr, methods containing CTAI explicit: CTAEXPORT to export or CTANOEXPORT or CTANOF90 to ingnore. """ # modules: import os import shutil ######################################################################## ### ### routines ### ######################################################################## def cta_f90_interface( procname, procargs, comments ) : """ Return list with Fortran90 source lines with the interface to C function. Arguments: procname Procedure name. procargs List with function argument descriptions. Each item of the list is a dictionairy that of the form: { 'name' : 'values', # variable name 'type' : 'int', # data type 'intent' : 'in', # intent 'dims' : '(*)' } # array shape (if relevant) comments List with the original DoxyGen comment in the C header file. """ # target name: newprocname = procname.replace('CTA','CTA_F90') # create argument list: argsline = '' for iarg in range(len(procargs)) : farg = procargs[iarg] if iarg > 0 : argsline = argsline+', ' argsline = argsline+farg['name'] #endfor # init list with required parameters: params = [] # scan arguments: for arg in procargs : # search for known parameters: for param in ['CTA_HANDLE_IKIND','CTA_TIME_RKIND'] : # needed for this type ? if param in arg['type'] : # add if not present yet: if param not in params : params.append(param) #endif #endfor # known parameters #endfor # arguments # void arguments ? with_void_args = False for arg in procargs : if 'void' in arg['type'] : with_void_args = True break #endif #endfor # init ifcult: ifc = [] # indent etc: indent = ' ' # add original comment: for comment in comments : ifc.append( indent+'! %s' % comment ) #endfor # extra seperation comment: ifc.append( indent+'!' ) # comment ? if with_void_args : indent = ' !' # start interface: ifc.append( indent+'interface %s' % newprocname ) # routine header: ifc.append( indent+' subroutine %s( %s )' % (procname,argsline) ) # use parameters if necessary: for param in params : ifc.append( indent+' use CTA_F90_Parameters, only : %s' % param ) #endfor # loop over arguments: for arg in procargs : # add argument line: ifc.append( indent+' %-30s, intent(%-5s) :: %s%s' % (arg['type'],arg['intent'],arg['name'],arg['dims']) ) #endfor # end of routine interface: ifc.append( indent+' end subroutine %s' % procname ) # end of interface: ifc.append( indent+'end interface' ) ## add external declaration if void arguments were present .. #if with_void_args : ifc.append( ' external :: %s' % procname ) ifc.append( '' ) # ok return ifc,[] #enddef # cta_f90_interface # *** def cta_f90_interface_generic( procname, procargs, comments, specials ) : """ Return list with Fortran90 source lines with the interface to C function. Implement a number of specific routines for different argument data types, for example 'integer', 'real(4)', 'real(8)', etc. Add a generic interface to collect the specific routines. Arguments: procname Procedure name. procargs List with function argument descriptions. Each item of the list is a dictionairy that of the form: { 'name' : 'values', # variable name 'type' : 'int', # data type 'intent' : 'in', # intent 'dims' : '(*)' } # array shape (if relevant) comments List with the original DoxyGen comment in the C header file. """ # target name: newprocname = procname.replace('CTA','CTA_F90') # current specials: if procname in specials.keys() : special = specials[procname] else : special = {} #endif ## info ... #print 'xxx3 special = ', special # dummy: argname_nval = 'no-nval' argname_datatype = 'no-datatype' multishape = False # create argument lists: argsline = '' argsline_call = '' # loop over initial arguments: for iarg in range(len(procargs)) : # current: farg = procargs[iarg] # extract: argname = farg['name'] ## info ... #print 'xxx4 testing argument "%s" ...' % argname # add to line with arguments to called routine: if len(argsline_call) > 0 : argsline_call = argsline_call+', ' argsline_call = argsline_call+argname # generic argument ? if ('generic' in special.keys()) and (argname == special['generic']) : ## info ... #print ' x4 generic variable ...' # multi shape dimension ? if (argname+'_dims' in special.keys()) and (special[argname+'_dims'] == '(*)') : #print ' x4 multi shape ...' multishape = True #endif #endif # 'nval' is not needed, implied by specific routine: if argname == 'nval' : # store for later usage: argname_nval = argname # next: continue #endif # 'datatype' is not needed, implied by specific routine: if argname == 'datatype' : # store for later usage: argname_datatype = argname # next: continue #endif # add to line: if len(argsline) > 0 : argsline = argsline+', ' argsline = argsline+argname #endfor # init list with required parameters: params = [] # scan arguments: for arg in procargs : # search for known parameters: for param in ['CTA_HANDLE_IKIND','CTA_TIME_RKIND'] : # needed for this type ? if param in arg['type'] : # add if not present yet: if param not in params : params.append(param) #endif #endfor # known parameters #endfor # arguments # init ifcult: ifc = [] rtn = [] # generic types: gtypes = ['integer','real4','real8'] if procname == 'CTA_Vector_SetVal' : gtypes.append('char') # add original comment: for comment in comments : ifc.append( ' ! %s' % comment ) #endfor # extra seperation comment: ifc.append( ' !' ) # start interface: ifc.append( ' interface %s' % newprocname ) # loop over types: for gtype in gtypes : ## info ... #print ' xxx generic type : ', gtype, '; multishape ', multishape # set: if gtype == 'integer' : spectype = 'integer' cta_kind = 'CTA_INTEGER' elif gtype == 'real4' : spectype = 'real(4)' cta_kind = 'CTA_REAL' elif gtype == 'real8' : spectype = 'real(8)' cta_kind = 'CTA_DOUBLE' elif gtype == 'char' : spectype = 'character(len=*)' cta_kind = 'CTA_STRING' else : print 'ERROR - generic type "%s" not supported' % gtype raise Exception #endif # multiple shapes ? if multishape and (gtype != 'char') : # loop over suported ranks: for rank in range(1,8) : # specific routine: rname = '%s_%s_%id' % (procname,spectype.replace('(','').replace(')',''),rank) # add line to interface: ifc.append( ' module procedure %s' % rname ) # routine header: rtn.append( ' subroutine %s( %s )' % (rname,argsline) ) # use parameters if necessary: for param in params : rtn.append( ' use CTA_F90_Parameters, only : %s' % param ) #endfor # use kind parameter: rtn.append( ' use CTA_F90_Parameters, only : %s' % cta_kind ) # loop over arguments: for arg in procargs : # extract: argname = arg['name'] argtype = arg['type'] argdims = arg['dims'] # skip some ... if argname == argname_nval : continue if argname == argname_datatype : continue # change to specific type: if argtype == 'void' : argtype = spectype # adhoc ... if argname+'_dims' in special.keys() : argdims = special[argname+'_dims'] # replace: if argdims == '(*)' : for k in range(rank) : if k == 0 : argdims = '(:' else : argdims = argdims+',:' #endif #endfor argdims = argdims+')' #endif ## info ... #print ' xxx argument : ', argname, argdims # add argument line: rtn.append( ' %-30s, intent(%-5s) :: %s%s' % (argtype,arg['intent'],arg['name'],argdims) ) #endfor # add call to C-routine: rtn.append( ' call %s( %s )' % (procname,argsline_call.replace(argname_datatype,cta_kind).replace(argname_nval,'size(val)')) ) # end of routine interface: rtn.append( ' end subroutine %s' % rname ) rtn.append( '' ) #endfor # ranks else : # specific routine: rname = '%s_%s' % (procname,gtype) # add line to interface: ifc.append( ' module procedure %s' % rname ) # routine header: rtn.append( ' subroutine %s( %s )' % (rname,argsline) ) # use parameters if necessary: for param in params : rtn.append( ' use CTA_F90_Parameters, only : %s' % param ) #endfor # use kind parameter: rtn.append( ' use CTA_F90_Parameters, only : %s' % cta_kind ) # other, not always necessary actually: rtn.append( ' use CTA_F90_Parameters, only : CTA_OK' ) # loop over arguments: for arg in procargs : # extract: argname = arg['name'] argtype = arg['type'] argdims = arg['dims'] # skip some ... if argname == argname_nval : continue if argname == argname_datatype : continue # change to specific type: if argtype == 'void' : argtype = spectype # adhoc ... if argname+'_dims' in special.keys() : argdims = special[argname+'_dims'] # add argument line: rtn.append( ' %-30s, intent(%-5s) :: %s%s' % (argtype,arg['intent'],arg['name'],argdims) ) #endfor # switch: if (procname == 'CTA_Vector_SetVal') and (gtype == 'char') : # add calls to C-routines: rtn.append( ' integer(CTA_HANDLE_IKIND) :: sval' ) rtn.append( ' call CTA_String_Create( sval, status )' ) rtn.append( ' if (status/=CTA_OK) return' ) rtn.append( ' call CTA_String_Set( sval, val, status )' ) rtn.append( ' if (status/=CTA_OK) return' ) rtn.append( ' call %s( %s )' % (procname,argsline_call.replace('val','sval').replace(argname_datatype,cta_kind).replace(argname_nval,'size(val)')) ) rtn.append( ' if (status/=CTA_OK) return' ) rtn.append( ' call CTA_String_Free( sval, status )' ) rtn.append( ' if (status/=CTA_OK) return' ) else : # add call to C-routine: rtn.append( ' call %s( %s )' % (procname,argsline_call.replace(argname_datatype,cta_kind).replace(argname_nval,'size(val)')) ) #endif # end of routine interface: rtn.append( ' end subroutine %s' % rname ) rtn.append( '' ) #endif # multiple shapes #endfor # generic types # end of interface: ifc.append( ' end interface' ) ifc.append( '' ) # add routine for 'handle' type ? if procname in ['CTA_Vector_GetVal','CTA_Vector_SetVal'] : # specific routines: # CTA_Vector_GetVal -> CTA_F90_Vector_GetHandle # CTA_Vector_SetVal -> CTA_F90_Vector_SetHandle rname = procname.replace('CTA_','CTA_F90_').replace('Val','Handle') # which kind ? spectype = 'integer' cta_kind = 'CTA_HANDLE' # routine header: rtn.append( ' subroutine %s( %s )' % (rname,argsline) ) # use parameters if necessary: for param in params : rtn.append( ' use CTA_F90_Parameters, only : %s' % param ) #endfor # use kind parameter: rtn.append( ' use CTA_F90_Parameters, only : %s' % cta_kind ) # other rtn.append( ' use CTA_F90_Parameters, only : CTA_OK' ) # loop over arguments: for arg in procargs : # extract: argname = arg['name'] argtype = arg['type'] argdims = arg['dims'] # skip some ... if argname == argname_nval : continue if argname == argname_datatype : continue # change to specific type: if argtype == 'void' : argtype = spectype # adhoc ... if argname+'_dims' in special.keys() : argdims = special[argname+'_dims'] # add argument line: rtn.append( ' %-30s, intent(%-5s) :: %s%s' % (argtype,arg['intent'],arg['name'],argdims) ) #endfor # add call to C-routine: rtn.append( ' call %s( %s )' % (procname,argsline_call.replace(argname_datatype,cta_kind).replace(argname_nval,'size(val)')) ) # end of routine interface: rtn.append( ' end subroutine %s' % rname ) rtn.append( '' ) #endif # handle type # ok return ifc,rtn #enddef # cta_f90_interface_generic # *** def cta_module_from_hfile( hfile, modname, specials ) : """ Create F90 module from C header file. """ # basename: bname = os.path.basename(hfile).rstrip('.h') # read file: f = open( hfile, 'r' ) lines = f.readlines() f.close() # start of function header: fheader_comment_start = '/** \\brief' # init results: interface_lines = [] routine_lines = [] # loop over lines: while len(lines) > 0 : # next line: line = lines.pop(0).strip() # headers start with DoxyGen desxription: if not line.startswith(fheader_comment_start) : continue # collect comment: comments = [ line.replace('/**','',1) ] while True : # next line: line = lines.pop(0).strip() # end of comment ? then leave: if line.startswith('*/') : break # add: comments.append(line.replace('*','',1)) #endwhile # collect function definition; # first get next line: line = lines.pop(0).strip() # remove trailing comment: xxx /* ... */ if '/*' in line : line,cmnt = line.split('/*') # init function line: fline = line ## testing ... ##testing = 'CTA_TreeVector_GetVals' #testing = 'CTA_TreeVector_SetVals' #if testing not in fline : continue #print 'TESTING "%s" ...' % testing # info ... print ' found procedure line: %s ...' % fline # skip pre-processor functions: if fline.startswith('#define') : print ' --> pre-processor function; skip ...' continue #endif # add rest: while not fline.endswith(';') : # next line: line = lines.pop(0).strip() # remove trailing comment: xxx /* ... */ if '/*' in line : line,cmnt = line.split('/*') # add: fline = fline+line #endwhile # split in function result/name and arguments: ret_and_name,argline = fline.rstrip(');').split('(') # split in result and name: procret,procname = ret_and_name.rsplit(None,1) # info: print ' name : %s' % procname print ' args : %s' % argline print ' ret : %s' % procret # no internal routines: if procname.startswith('CTAI_') : print ' --> internal procedure, skip ...' continue #endif # no usr modules: if hfile.find('usr_')>=0 : print ' --> example procedure, skip ...' continue #endif # explicitly skip this ? if (procname in specials.keys()) and specials[procname].has_key('skip') : print ' --> special, skip this procedure ...' continue #endif # by default no return status assumed: with_return_status = False # only certain return values supported ... if procret == 'CTAEXPORT int' : # set flag: with_return_status = True elif procret == 'CTAEXPORT void' : # noting special .. pass elif procret.startswith('CTANOF90') : # skip because of this label print ' --> CTANOEXPORT found, skip' continue elif procret.startswith('CTANOEXPORT') : # skip because of this label print ' --> CTANOEXPORT found, skip' continue else : # unknown .. print ' --> ERROR: unsupported return value type, skip ...' raise Exception #endif # add status argument ? if with_return_status : # extend argument line: if len(argline) > 0 : argline = argline+', ' argline = argline+'int status' # loop over comment lines to search for '\return' tag; # note that if no such tag is found, an error will be raised # later on since no '\param status' description is present for icomment in range(len(comments)) : # contains \return key ? if '\\return' in comments[icomment] : # change return status from function result to argument: comments[icomment] = comments[icomment].replace('\\return','\\param status O') # leave loop over comments: break #endif #endfor # comment lines #endif # return status argument # extract argument info from comment: # argis['data']{'intent' : 'in', # 'description' : 'input data'} argis = {} # loop over for comment in comments : # cleanup: comment = comment.strip() # only the parameter descriptions: if not comment.startswith('\param') : continue # split: try : tag,name,ichar,description = comment.split(None,3) except : print 'ERROR - could not split comment into tag, name intent, and description :' print 'ERROR - comment line : %s' % comment print 'ERROR - file : %s' % hfile raise Exception # convert intent: ichar = ichar.lower() if ichar in ['i','(i)','(input)'] : intent = 'in' elif ichar in ['o','(o)','(output)'] : intent = 'out' elif ichar in ['io','(io)','(input/output)'] : intent = 'inout' else : print 'ERROR - could not translate intent "%s"' % ichar print 'ERROR - comment line : %s' % comment print 'ERROR - file : %s' % hfile raise Exception #endif # store: argis[name] = { 'intent' : intent, 'description' : description } #endfor # init list with fortan argument descriptions: fargs = [] # any arguments at all ? if len(argline) > 0 : # split into c-arguments: cargs = argline.split(',') # translate into fortran arguments: for carg in cargs : # cleanup: carg = carg.strip() ## remove 'const' part: #if carg.startswith('const') : carg = carg[6:] # split into type and name: try : argtype,argname = carg.rsplit(None,1) except : print 'ERROR - could not split carg "%s"' % carg raise Exception #endtry # dereferenced point ? remove starts: if argname.startswith('**') : argname = argname[2:] # pointer values ? ispointer = argname.startswith('*') if ispointer : argname = argname[1:] # translate type: ctype = argtype if ctype in ['char','const char','char*','const char*'] : ftype = 'character(len=*)' elif ctype in ['int','const int','int*'] : ftype = 'integer' elif ctype == 'double' : if 'time' in procname.lower() : ftype = 'real(CTA_TIME_RKIND)' else : ftype = 'real(8)' #endif elif ctype in ['CTA_Datatype','BOOL','FILE'] : ftype = 'integer' elif ctype.startswith('CTA_') or ctype.startswith('const CTA_') : ftype = 'integer(CTA_HANDLE_IKIND)' elif ctype in ['void','const void'] : ftype = 'void' else : print 'ERROR - could not translate C-type : %s' % ctype raise Exception #endif # cleanup name: argname = argname.replace('*','') # array ? if '[' in argname : argname,dimlist = argname.rstrip(']').split('[') ndim = dimlist.count(',')+1 argdims = '(' for idim in range(ndim) : if idim > 0 : argdims = argdims+',' argdims = argdims+'*' #endfor argdims = argdims+')' else : argdims = '' #endif # check .. if argname not in argis.keys() : print 'ERROR - no argument description found for "%s"' % argname print 'ERROR - procedure : %s' % procname print 'ERROR - filename : %s' % hfile raise Exception #endif # extract intent: intent = argis[argname]['intent'] # input pointer ? probably an array: if ispointer and (intent == 'in') and \ (ctype not in ['const char','FILE']) : argdims = '(*)' #endif # add: fargs.append( { 'type' : ftype, 'intent' : intent, 'name' : argname, 'dims' : argdims } ) #endfor # arguments #endif # any arguments at all # get f90 interface lines: if (procname in specials.keys()) and ('generic' in specials[procname]) : ## info ... #print 'TESTING: create generic interfaces' # convert: interface,routines = cta_f90_interface_generic( procname, fargs, comments, specials ) else : ## info ... #print 'TESTING: create interface' # convert: interface,routines = cta_f90_interface( procname, fargs, comments ) #endif ## testing ... #if procname == 'CTA_File_Set' : # for iline in ilines : print iline # print '' # raise Exception ##endif # add: if len(interface) > 0 : interface_lines.extend( interface ) # add: if len(routines) > 0 : routine_lines.extend( routines ) #endwhile # init empty module: mlines = [] # interfaces defined ? if (len(interface_lines) > 0) or (len(routine_lines) > 0) : # add module header: mlines.append( 'module %s' % modname ) mlines.append( '' ) mlines.append( ' implicit none' ) mlines.append( '' ) mlines.append( ' public' ) mlines.append( '' ) if len(interface_lines) : mlines.extend(interface_lines) #endif mlines.append( '' ) if len(routine_lines) > 0 : mlines.append( 'contains' ) mlines.append( '' ) mlines.extend( routine_lines ) mlines.append( 'end module %s' % modname ) mlines.append( '' ) #endif # ok return mlines #enddef # cta_module_from_hfile #################################################################### ### ### main program ### #################################################################### if __name__ == "__main__" : # info ... print '' print '** genenerate cta_f90 interface **' print '' # directory with include files: #hdir = 'openda_1.0/public/core/native/include' #hdir = 'openda_1.1_beta/core/native/include/' #hdir = 'openda_2.0/core/native/include/' #hdir = 'openda_2.x/core/native/include/' hdir = '../../include' # extra source files, mainly templates: srcdir = '.' # Adhoc fixes .. # Fill a dictionairy with CTA routine names for which something should be fixed. # Assign a dictionairy to this with one or more of the following key/value pairs: # # "generic" : # is generic, thus integer, real, etc. # # "_dims" : # dimensions of are not explicitly # # mentioned in header file, therefore specify them here specials = {} specials['CTA_Vector_GetVal' ] = { 'generic' : 'vals', 'val_dims' : '' } # should have been "val" ... specials['CTA_Vector_GetVals' ] = { 'generic' : 'vals', 'vals_dims' : '(*)' } specials['CTA_Vector_SetVal' ] = { 'generic' : 'val' , 'val_dims' : '' } specials['CTA_Vector_SetVals' ] = { 'generic' : 'vals', 'vals_dims' : '(*)' } # specials['CTA_Vector_AppendVal' ] = { 'generic' : 'val' , 'val_dims' : '' } specials['CTA_Vector_AppendVal' ] = { 'skip' : True } specials['CTA_Vector_SetConstant' ] = { 'generic' : 'val' , 'val_dims' : '' } specials['CTA_TreeVector_GetVal' ] = { 'generic' : 'val' , 'val_dims' : '' } specials['CTA_TreeVector_GetVals' ] = { 'generic' : 'val' , 'val_dims' : '(*)' } specials['CTA_TreeVector_SetVal' ] = { 'generic' : 'val' , 'val_dims' : '' } specials['CTA_TreeVector_SetVals' ] = { 'generic' : 'val' , 'val_dims' : '(*)' } # should have been "vals" ... specials['CTA_TreeVector_AppendVal' ] = { 'generic' : 'val' , 'val_dims' : '' } specials['CTA_TreeVector_SetConstant'] = { 'generic' : 'val' , 'val_dims' : '' } specials['CTA_Pack_Add' ] = { 'generic' : 'data', 'data_dims' : '(*)' } specials['CTA_Pack_Get' ] = { 'generic' : 'data', 'data_dims' : '(*)' } # destination: mdir = 'generated' # create destination directory if necessary: if not os.path.isdir(mdir) : os.makedirs(mdir) # list of created modules: modnames = [] # info ... print 'scan "%s" for header files ...' % hdir # loop over all files: for fname in os.listdir(hdir) : # only header files ... if not fname.startswith('cta_') : continue if not fname.endswith('.h') : continue ## testing ... #if fname != 'cta_treevector.h' : continue #print 'TESTING: only file "%s" ...' % fname ## info ... #print ' header file "%s" ...' % fname # full path: hfile = os.path.join(hdir,fname) # split: bname,ext = os.path.splitext(fname) # target module name: modname = bname.replace('cta_','cta_f90_') # avoid problems with module names that are the same as one of their routines: if modname in ['cta_f90_flush'] : modname = modname+'_mod' #endif # info ... print '' print 'processing %s ...' % hfile # get module lines: mlines = cta_module_from_hfile( hfile, modname, specials ) # write ? if len(mlines) > 0 : # name of module file: mfile = os.path.join( mdir, modname+'.f90' ) # write: f = open( mfile, 'w' ) for mline in mlines : f.write( '%s\n' % mline ) #endfor f.close() # add to collection: modnames.append( modname ) else : print ' (no procedures found)' #endif #endfor # filenames # collection module: mname = 'cta_f90' # info ... print '' print 'create collection module %s ...' % mname # # copy parameter module: pfile = 'cta_f90_parameters.f90' print ' copy %s ...' % pfile shutil.copy( os.path.join(srcdir,pfile), os.path.join(mdir,pfile) ) # # copy makefile #mfile = 'cta_f90.make' #print ' copy %s ...' % mfile #shutil.copy( os.path.join(srcdir,mfile), os.path.join(mdir,'Makefile') ) # # read contained routines: incfile = 'cta_f90_contains.inc' print ' read %s ...' % incfile f = open( os.path.join(srcdir,incfile) ) lines = f.readlines() f.close() # cleanup: inclines = [] for line in lines : inclines.append( line.rstrip() ) #endfor # # fill lines: print ' fill main module ...' mlines = [] mlines.append( 'module %s' % mname ) mlines.append( '' ) mlines.append( ' use cta_f90_parameters' ) for modname in modnames : mlines.append( ' use %s' % modname ) #endfor mlines.append( '' ) mlines.append( ' implicit none' ) mlines.append( '' ) mlines.append( ' public' ) mlines.append( '' ) mlines.append( 'contains' ) mlines.append( '' ) mlines.extend( inclines ) mlines.append( '' ) mlines.append( 'end module %s' % mname ) # target file: mfile = os.path.join( mdir, mname+'.f90' ) # write: print ' write %s ...' % mfile f = open( mfile, 'w' ) for mline in mlines : f.write( '%s\n' % mline ) #endfor f.close() # ok print '' print 'End.' print '' #endif # __main__