FV3 Bundle
c2f.py
Go to the documentation of this file.
1 # (C) Copyright 2009-2016 ECMWF.
2 #
3 # This software is licensed under the terms of the Apache Licence Version 2.0
4 # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5 # In applying this licence, ECMWF does not waive the privileges and immunities
6 # granted to it by virtue of its status as an intergovernmental organisation nor
7 # does it submit to any jurisdiction.
8 
9 
10 #! /usr/bin/env python
11 from argparse import ArgumentParser
12 import re
13 import os
14 parser = ArgumentParser()
15 parser.add_argument("files", type=str, nargs='*', help="files to parse")
16 parser.add_argument("-o", "--output", type=str, help="output")
17 parser.add_argument("-m", "--module", type=str, help="module")
18 parsed = parser.parse_args()
19 files = parsed.files
20 output = parsed.output
21 module = parsed.module
22 used_types = []
23 
24 # A tabulation:
25 TAB = " "
26 
27 # -------------------------------------------------------------------------
28 # These dictionaries give the Fortran type and its KIND for each C type:
29 # -------------------------------------------------------------------------
30 # One word types:
31 TYPES_DICT = {
32  "int":("integer(c_int)","c_int"),
33  "long":("integer(c_long)","c_long"),
34  "short":("integer(c_short)","c_short"),
35  "_Bool":("logical(c_bool)","c_bool"),
36  "boolean":("logical(c_bool)","c_bool"),
37  "double": ("real(c_double)","c_double"),
38  "float":("real(c_float)","c_float"),
39  "size_t": ("integer(c_size_t)","c_size_t"),
40  }
41 
42 # Two words types:
43 TYPES2_DICT = {
44  "long double": ("real(c_long_double)","c_long_double"),
45  "unsigned long":("integer(c_long)","c_long"),
46  "unsigned short":("integer(c_short)","c_short"),
47  "unsigned int":("integer(c_int)","c_int"),
48  }
49 
50 class ParsingError(Exception):
51  def __init__(self, message):
52 
53  # Call the base class constructor with the parameters it needs
54  Exception.__init__(self, message)
55 
56 #---------------------------------------------------------------------------
57 # Regular expressions used to identify the different parts of a C c_line:
58 #---------------------------------------------------------------------------
59 REGEX_RETURNED_TYPE = re.compile( "^ *([_0-9a-zA-Z ]+ *\**&?)" )
60 REGEX_FUNCTION_NAME = re.compile( "([0-9a-zA-Z_]+) *\(" )
61 REGEX_ARGUMENTS = re.compile( "\(([&0-9a-zA-Z_\s\,\*\[\]]*)\).*;$" )
62 REGEX_ARGS = re.compile( " *([&0-9a-zA-Z_\s\*\[\]]+),?" )
63 REGEX_VAR_TYPE = re.compile( " *([_0-9a-zA-Z]+)[ |\*]" )
64 REGEX_TYPE = re.compile( "^ *((const )?\w+)[ \*]?" )
65 REGEX_VAR_NAME = re.compile( "[ |\*&]([_0-9a-zA-Z]+)(?:\[\])?$" )
66 
67 def multiline(line, maxlength):
68  """Split a long line in a multiline, following Fortran syntax."""
69  result = ""
70  while len(line) > maxlength-1:
71  result += line[0:maxlength-1] + "&\n"
72  line = "&"+ line[maxlength-1:]
73  result += line
74  return result
75 
76 def iso_c_binding(declaration, returned):
77  """ Returns the Fortran type corresponding to a C type in the
78  ISO_C_BINDING module and the KIND type """
79  global REGEX_TYPE
80  global TYPES_DICT
81 
82  try:
83  c_type = REGEX_TYPE.search(declaration).group(1)
84  except AttributeError:
85  return "?", "?" # error
86 
87  # Is it an array ?
88  if declaration.find("[") != -1:
89  array = ", dimension(*)"
90  else:
91  array = ""
92 
93  # Is it a pointer ?
94  if declaration.find("*") != -1:
95  # Is it a string (char or gchar array) ?
96  # TODO: what about "unsigned char" "guchar" gunichar ?
97  if ((c_type.find("char") != -1) or (c_type.find("char*") != -1)) and (not returned):
98  if declaration.find("**") != -1:
99  return "type(c_ptr), dimension(*)", "c_ptr"
100  else:
101  return "character(kind=c_char), dimension(*)", "c_char"
102  else:
103  return "type(c_ptr)", "c_ptr"
104 
105  # Other cases:
106  if len(declaration.split()) >= 3: # Two words type
107  for each in TYPES2_DICT:
108  if set(each.split()).issubset(set(declaration.split())):
109  return TYPES2_DICT[each][0] + array, TYPES2_DICT[each][1]
110  else: # It is a one word type
111  for each in TYPES_DICT:
112  if each in c_type.split():
113  return TYPES_DICT[each][0] + array, TYPES_DICT[each][1]
114 
115  # It is finally an unknown type:
116  return "?", "?"
117 
118 def C_F_binding(c_line):
119 
120  type_returned = REGEX_RETURNED_TYPE.search(c_line)
121  try:
122  function_type = type_returned.group(1)
123  except AttributeError:
124  raise ParsingError, "Returned type not found "+ c_line
125 
126  # Will it be a Fortran function or a subroutine ?
127  if (function_type.find("void") != -1) and (function_type.find("*") == -1):
128  f_procedure = "subroutine "
129  f_the_end = "end subroutine"
130  isfunction = False
131  f_use = ""
132  else:
133  f_procedure = "function "
134  f_the_end = "end function"
135  isfunction = True
136  returned_type, iso_c = iso_c_binding(type_returned.group(1), True)
137  f_use = iso_c
138  if returned_type.find("?") != -1:
139  error_flag = True
140  raise ParsingError, "Unknown data type: " + type_returned.group(1) + " " + c_line
141 
142  # f_name is the name of the function in fortran:
143  function_name = REGEX_FUNCTION_NAME.search(c_line)
144  try:
145  f_name = function_name.group(1)
146  except AttributeError:
147  raise ParsingError, "Function name not found "+c_line
148 
149  arguments = REGEX_ARGUMENTS.search(c_line)
150  try:
151  args = REGEX_ARGS.findall(arguments.group(1))
152  except AttributeError:
153  raise ParsingError, "Arguments not found " + c_line
154 
155  # Each argument of the function is analyzed:
156  declarations = ""
157  args_list = ""
158  for arg in args:
159  if arg != "void":
160  try:
161  var_type = REGEX_VAR_TYPE.search(arg).group(1)
162  except AttributeError:
163  raise ParsingError, "Variable type not found " + c_line
164 
165  f_type, iso_c = iso_c_binding(arg, False)
166  if iso_c not in used_types:
167  used_types.append(iso_c)
168 
169  if f_type.find("c_") != -1:
170  if f_use == "":
171  f_use = iso_c
172  else:
173  # each iso_c must appear only once:
174  REGEX_ISO_C = re.compile( "("+iso_c+")"+"([^\w]|$)" )
175  if REGEX_ISO_C.search(f_use) == None:
176  f_use += ", " + iso_c
177  elif f_type.find("?") != -1:
178  raise ParsingError, "Unknown data type: " + arg + " " + c_line
179 
180 
181  try:
182  var_name = REGEX_VAR_NAME.search(arg).group(1)
183  except AttributeError:
184  raise ParsingError,"Variable name not found "+c_line+" "+arg
185 
186  # Array with unknown dimension are passed by adress,
187  # the others by value:
188  if (f_type.find("(*)") != -1):
189  passvar = ""
190  else:
191  if (arg.find("&") != -1):
192  passvar = ""
193  else:
194  passvar = ", value"
195 
196  declarations += 1*TAB + f_type + passvar + " :: " + var_name + "\n"
197  if args_list == "":
198  args_list = var_name
199  else:
200  args_list += ", " + var_name
201  interface = 0*TAB + "! " + c_line + "\n"
202  first_line = 0*TAB + f_procedure + f_name + "(" + args_list + ') bind(c,name="{f_name}")'.format(f_name=f_name)
203 
204  interface += multiline(first_line, 79) + " \n"
205 
206  interface += 1*TAB + "use iso_c_binding, only: " + f_use + "\n"
207  if isfunction:
208  interface += 1*TAB + returned_type + " :: " + f_name + "\n"
209  interface += declarations
210  interface += 0*TAB + f_the_end + "\n\n"
211 
212  return interface
213 
214 def parse_file(c_file):
215  file_name, file_ext = os.path.splitext(c_file)
216  with open(c_file,'r') as open_file:
217  text = open_file.read()
218  repl = r""
219  text = re.sub('(?ms).*?extern "C"\s*{', '', text)
220  text = re.sub('(?ms)}.*$', '', text)
221  text = re.sub('(?ms)^\s+', '', text)
222  lines = [ line.strip() for line in text.strip().splitlines() ]
223  f_file_path = output
224  if( module ):
225  module_name = module
226  else:
227  module_name = file_name.split('/')[-1]+"_c_binding"
228  with open(f_file_path,'w') as f_file:
229  import time
230  f_file_header = "! Automatically generated by c2f.py on {time}\n".format(time=time.asctime(time.localtime()))
231  f_file_header += "! Based on file {filename}\n".format(filename=c_file)
232  f_file_header += "! Please do not modify.\n"
233 
234  f_file.write(f_file_header+"\nmodule {module_name}\nimplicit none\ninterface\n\n".format(module_name=module_name))
235 
236  for line in lines:
237  try:
238  f_file.write( C_F_binding(line) + '\n\n')
239  except ParsingError, e:
240  print("\n----------------------------------------\n"+
241  "Parsing failed for file\n "+c_file+" : \n"+str(e)+
242  "\n----------------------------------------\n"+text)
243  f_file.write("end interface\nend module {module_name}\n".format(module_name=module_name))
244 
245 
246 
247 for file in files:
248  parse_file(file)
def C_F_binding(c_line)
Definition: c2f.py:118
integer, parameter set
def parse_file(c_file)
Definition: c2f.py:214
str
Definition: c2f.py:15
l_size ! loop over number of fields ke do je do ie to je n if(.NOT. d_comm%R_do_buf(list)) cycle from_pe
def __init__(self, message)
Definition: c2f.py:51
def multiline(line, maxlength)
Definition: c2f.py:67
character(len=32) format
Definition: fms_io.F90:535
def iso_c_binding(declaration, returned)
Definition: c2f.py:76