#!/usr/bin/env python from __future__ import print_function sla_url = "https://github.com/Starlink/starlink/archive/master.zip" def interpolate( S , V , d1 = "$", d2 =""): r = S for k,v in V.items(): if type(v) is not str : continue r = r.replace(d1+k+d2,v) return r includes = """ #include #include #include using std::string; using std::ostream; using std::endl; """ pointable = """ // helper to make a pointer to an rvalue #ifndef POINTABLEDEFD #define POINTABLEDEFD template struct _pointable { T x; _pointable(const T& x): x(x) {} operator T*() { return &x; } }; #endif """ struct_template = """ struct $cpp_funcname { $return_pars_declaration; $cpp_funcname( $input_pars_declaration ) { $fortran_funcname_( $call_list ); } std::string __str__() const { std::ostringstream s; s << "$return_pars1 : " << $return_pars2 << endl; return s.str(); } }; inline ostream& operator<<(ostream& s, const $cpp_funcname& o ) { return s << o.__str__(); } """ func_template = """ inline $ret_type $cpp_funcname( $input_pars_declaration ) { return $fortran_funcname_( $call_list ); }; """ class fortran_parameter( object ): def __init__( self , name, datatype, direction, desc ) : self.name = name self.datatype = datatype self.direction= direction self.desc = desc self.input = (direction == 'input') self.output = (direction == 'output') def __str__(self) : return " ".join([ self.name, self.datatype, self.direction, self.desc , str(self.input), str(self.output) ]) def add_par( parlist, direction, name, comment_type , desc="no description" ): v = name.split(",") # some parameters may be both input and output if name in parlist : if direction == "output": parlist[name].output = True return if len(v)==0 : return if len(v)==1 : parlist[name] = fortran_parameter( name, comment_type, direction, desc) return for n in v : add_par( parlist, direction, n, comment_type, desc ) typs = """ d dp DOUBLE D double : double i int INTEGER I : int r real f REAL R : float c*(*) : string char c : char SUBROUTINE : void """ def type_to_cpp( ftype ): D = "" if '(' in ftype and not ftype=="c*(*)" : n = ftype.split("(")[1].split(")")[0] ftype = ftype.split('(')[0] # n is string that may have a , v = n.split(",") try : dims = [int(x) for x in v] except ValueError: # print "cant handle array(?) type ", ftype return None D = "["+ n.replace(",","][") + "]" for l in typs.splitlines() : if not ':' in l : continue fs,c = l.split(":") fs = fs.split() if ftype in fs : return c.strip(), D print ("unknown type", ftype) return None class fortran_function(object): def __init__( self , signature , comment ) : self.comment_parlist = { } self.arglist = [] self.good = True self.comment = comment self.return_type, self.func_name, args_string = signature.split(None,2) self.parse_comment( comment ) self.arglist = self.check_args( args_string ) if self.arglist == False : # emtply list is ok self.msg = 'no arglist' good = False return # print "arglist=",self.arglist for p in self.arglist : ctype = type_to_cpp( p.datatype ) if not ctype : self.good = False if False : print ("not generating slalib wrapper for", self.func_name,) print ("due to non-understood parameter") return if False : print (" err for ", self.func_name) print ("failed to get type for", p) self.good = False msg = "failed to find type for "+str(p) return p.ctype, p.dim = ctype[0], ctype[1] self.build_vars() self.ctype = type_to_cpp( self.return_type )[0] if not self.ctype : self.good = False self.msg = "no return type found" def cdecl(self): return 'extern"C" { ' + self.ctype +" "+ self.func_name.lower()+'_ ( ' + \ ",".join( self.v_cdecl ) + \ '); }' def callvars(self) : return ",".join( self.v_call ); def invars(self) : return ','.join( self.cpp_input_vars ); def outvars(self) : return ';'.join( self.cpp_output_vars ); def retpars(self) : return ','.join( p.name for p in self.arglist if not p.input ); def dbg(self) : for p in self.arglist: print ("arg: ", p) print (self.v_cdecl ) print (self.v_call ) print (self.cpp_input_vars ) print (self.cpp_output_vars ) def build_vars( self ): self.v_cdecl = []; self.v_call = []; self.cpp_input_vars = []; self.cpp_output_vars= []; for p in self.arglist : c = '' if p.input and not p.output : c = 'const ' # print p.input, p.name, p.ctype, p.dim if p.ctype == "string": self.v_cdecl += [c+"char*",c+"int*"] self.v_call += [p.name+".c_str()", "_pointable("+p.name+".length())"] # if a par is both input and output, it should be in the # c++ function par-list, as non-const if p.input : if p.output : c = "" self.cpp_input_vars += [c+"string& "+p.name] else : self.cpp_output_vars += ["string "+p.name] if p.ctype != "string" : if p.dim == '' : # regular value self.v_cdecl.append( c+p.ctype+"*" ) self.v_call.append( "&"+p.name ) if p.input : if p.output : c = "" self.cpp_input_vars.append( c+p.ctype + " " + p.name ); else : self.cpp_output_vars.append( c+p.ctype + " " + p.name ); else : # p.dim = e.g. [3] self.v_cdecl.append( c+p.ctype+p.dim ) self.v_call.append( p.name ) if p.input : if p.output : c = "" self.cpp_input_vars.append ( c+p.ctype + " " + p.name + p.dim); else : self.cpp_output_vars.append ( c+p.ctype + " " + p.name + p.dim); def return_type(self): outtypes = [] for a in self.parmap.values() : if a.direction != "output" : continue outtypes.append( a.datatype ) if self.return_type == "SUBROUTINE": return outtypes else : outtypes.append( self.return_type ) return outtypes def check_args (self, argslist_s ) : args = argslist_s.replace("(","").replace(")","").strip().split(",") argnames_ = map( str.strip, args ) argnames = [x for x in argnames_ if x != ""] r = [] # print "argnames = ", argnames try : for a in argnames : par = self.comment_parlist[a] r.append(par) except KeyError: #print " no info for parameter ", a, argslist_s self.good = False self.msg = " no info for parameter " + str(a) return False return r def parse_comment( self, txt ): v = txt.splitlines() direction = None for line_ in v : line = line_.strip() if line.lower().startswith("given") : direction = "input" if line.lower().startswith("returned") : direction = "output" if any( line.startswith( s ) for s in ["Called:", "Notes:", "Example:"] ) : direction = None if not direction : continue # the first word in a line may be a parameter words = line.split(None,2) if len(words)< 2 : continue add_par( self.comment_parlist, direction, *words ) #print self.comment_parlist def write_cpp( self ): n = sum( p.output for p in self.arglist) if n > 0 : # the output paremeters that are function arguments if self.ctype != "void" : print (" have both a return type and output par for ", self) # self.dbg() self.good = False sys.exit() return ""; return self.write_cpp1("struct") else : # print " function return type = ", self.ctype if self.ctype in "double float int bool".split(): return self.write_cpp1("func") print (" should not get here ", n , self.func_name) self.dbg() sys.exit() def write_cpp1( self , mode ): COM = "" for cl in self.comment.splitlines(): s = cl.strip() if s == "+" : continue if s == "License:": break; COM += '//' + cl + '\n' if mode == "struct" : template = struct_template if mode == "func" : template = func_template cdecl = self.cdecl() cpp_funcname = self.func_name.replace("sla_","").lower() return_pars_declaration = self.outvars() return_pars1 = self.retpars(); if return_pars1.strip() == "" : return_pars2 = '""' # should be a function else: return_pars2 = return_pars1.replace(",",'<<" "<<'); input_pars_declaration = self.invars() fortran_funcname = self.func_name.lower() # symbol is lower case in the .o file call_list = self.callvars(); ret_type = self.ctype return COM + "\n" + interpolate( template , locals()) def parse_fortran_file( file , verb = False ): F = open(file).read().splitlines() decl = "" for l in F: if l.startswith("*") : break decl += l.strip() decl = decl.replace(":","").replace("FUNCTION","").replace("PRECISION","") declaration = decl # then comes a comment block comment=[] for i in range(1, len(F) ): line = F[i] if line.startswith("*") : comment.append( line[1:] ) if verb : print ("\n".join(comment)) fun = fortran_function( declaration, "\n".join(comment) ) return fun def test( ffile ): fun = parse_fortran_file(ffile) if fun.good : code = pointable + " \n\n" + fun.cdecl() +'\n\n' + fun.write_cpp(); print (code) else : return False import ROOT return ROOT.gCling.Declare( code ) _vetos = """ wait.f pvobs.f obs.f veri.f vers.f """ vetos = [v.strip() for v in _vetos.splitlines() if len(v.strip())>3 ] if __name__ == '__main__' : c_extern_decls = "" c_definitions = "" import sys directory = "." L = sys.argv[1:] for f in L: if any ( v in f for v in vetos ) : continue fun = parse_fortran_file(f) #print fun.parmap if not fun.good : continue #print f, fun.outvars(); #print '====================' #fun.dbg() if not fun.good : continue c_extern_decls += fun.cdecl()+"\n" c_definitions += fun.write_cpp() f = open(directory+"/sla.hh",'w') f.write( """ #ifndef SLAAINCLD #define SLAAINCLD """ + includes + """ """ + c_extern_decls + """ namespace sla { """ + pointable + """ """ + c_definitions + """ } #endif""" ) f.close() f = open(directory+"/sla.linkdef.h",'w') f.write(""" #pragma link C++ defined_in "sla.hh"; """) f.close()