IODA Bundle
fwrap.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 import re
4 
5 PARAM_TYPE_COLUMN = 43
6 CONSTANTS = {}
7 
8 def formatParameter(typ, name):
9  global PARAM_TYPE_COLUMN
10  space = ' ' * (PARAM_TYPE_COLUMN - len(typ))
11  return typ + space + ':: ' + name
12 
13 def declarations(source_cc = 'odbql.cc'):
14  decls = [line for line in [l.strip() for l in open(source_cc).read().splitlines()]
15  if line.find('odbql_') <> -1
16  and not line.startswith('//')
17  and line.find('return') == -1
18  and line.find('virtual') == -1
19  and line.find('::') == -1
20  and line.find('typedef') == -1]
21  return decls
22 
23 def constants(source_h = 'odbql.h'):
24 
25  lines = [line.strip() for line in open(source_h).read().splitlines()
26  if line.startswith('#define ODBQL_')
27  and len(line.split()) > 2]
28 
29  defs = [line.split(None, 2)[1:] for line in lines]
30  return defs
31 
33  try:
34  e = s
35  for c in CONSTANTS:
36  e = e.replace(c, CONSTANTS[c])
37  return ' [' + str(eval(e)) + ']'
38  except: return ''
39 
40 def translate_value_and_comment(value_and_possibly_comment):
41  value = value_and_possibly_comment.split('/*')[0].strip()
42  comment = ''
43 
44  if len(value_and_possibly_comment.split('/*')) > 1:
45  comment = value_and_possibly_comment.split('/*')[1].split('*/')[0]
46 
47  if value.find('|') <> -1:
48  comment = value + ' ' + comment + evaluated_expression(value_and_possibly_comment)
49  l,r = [x.strip(' ()') for x in value.split('|')]
50  i, shift = [x.strip() for x in r.split('<<')]
51  value = 'IOR(%s, LSHIFT(%s,%s))' % (l, i, shift)
52 
53  if value.startswith('0x'):
54  comment = value + ' ' + comment
55  value = eval(value)
56 
57  return value, '! ' + comment
58 
59 def generateParameter(define):
60  global CONSTANTS
61 
62  name, value_and_possibly_comment = define
63 
64  typ = 'integer'
65  if value_and_possibly_comment.find('"') <> -1:
66  typ = 'character(len=*)'
67 
68  if name == 'ODBQL_TRANSIENT':
69  value, comment = '-1', " ! ((odbql_destructor_type)-1)"
70  elif name == 'ODBQL_STATIC':
71  value, comment = '0', ' ! ((odbql_destructor_type)0)'
72  else:
73  value, comment = translate_value_and_comment(value_and_possibly_comment)
74 
75  CONSTANTS[name] = value
76 
77  return '%s, parameter :: %s = %s %s' % (typ, name, value, comment)
78 
80  return """
81 module odbql_constants
82  implicit none
83 
84 """ + '\n'.join(generateParameter(d) for d in defs) + """
85 
86 end module odbql_constants
87 """
88 
89 def normalize_type(t): return re.sub(' [*]', '*', t)
90 
91 def parseParam(p):
92  p = p.strip()
93 
94  # Hack for the destructor type, used in sqlite3 to let user pass
95  # a function that will release memory occupied by strings and blobs'
96  # No idea how to handle this in Fortran at this point, not sure
97  # if we immediately need this functionality anyway.
98  ##
99  if p == 'void(*d)(void*)': return 'void(*)(void*)', 'd'
100 
101  param_name = re.findall(r"[\w']+", p)[-1]
102  param_type = p[:len(p) - len(param_name)].strip()
103  param_type = normalize_type(param_type)
104 
105  return param_type, param_name
106 
107 
109  ret_and_fun_name = decl.split('(', 1)[0].strip()
110  fun_name = re.findall(r"[\w']+", ret_and_fun_name)[-1]
111  return_type = ret_and_fun_name[:-len(fun_name)].strip()
112  return_type = normalize_type(return_type)
113 
114  params = decl.split('(',1)[1].strip().rsplit(')', 1)[0].strip()
115  params = list([parseParam(p) for p in params.split(',')])
116 
117  # filter out void from parameterless functions e.g: const char * odbql_libversion(void)
118  if len(params) == 1 and (len(params[0][0]) == 0 and params[0][1] == 'void'):
119  params = []
120  return (decl, (return_type, fun_name, params))
121 
123  #print 'translate_type_for_binding:', t
124  if t == 'const char*': return 'character(kind=C_CHAR), dimension(*)'
125  if t == 'const char**': return 'character(kind=C_CHAR), dimension(*)' # TODO
126  if t == 'double': return 'real(kind=C_DOUBLE), value'
127  if t == 'int': return 'integer(kind=C_INT), value'
128  if t == 'odbql*': return 'type(C_PTR), VALUE'
129  if t == 'odbql**': return 'type(C_PTR)'
130  if t == 'odbql_stmt*': return 'type(C_PTR), VALUE'
131  if t == 'odbql_stmt**': return 'type(C_PTR)'
132  if t == 'odbql_value*': return 'type(C_PTR), VALUE'
133  if t == 'odbql_value**': return 'type(C_PTR)'
134  if t == 'void(*)(void*)': return 'type(C_PTR), VALUE'
135 
136  raise Exception("Don't know how to translate '" + t + "'")
137 
138 
140  #print 'translate_type_for_binding_return:', t
141  if t == 'const char*': return 'type(C_PTR)'
142  if t == 'const unsigned char*': return 'type(C_PTR)'
143  if t == 'int': return 'integer(kind=C_INT)'
144  if t == 'double': return 'real(kind=C_DOUBLE)'
145  if t == 'odbql_value*': return 'type(C_PTR)'
146  if t == 'error_code_t': return 'integer(kind=C_INT)'
147 
148  raise Exception("Don't know how to translate '" + t + "'")
149 
150 
152  #print 'translate_type_for_fortran:', t
153  if t == 'const char*': return 'character(len=*), intent(in)'
154  if t == 'const char**': return 'character(len=*), intent(out)'
155  if t == 'double': return 'real(kind=C_DOUBLE), value'
156  if t == 'int': return 'integer(kind=C_INT), value'
157  if t == 'odbql*': return 'type(odbql), value'
158  if t == 'odbql**': return 'type(odbql)'
159  if t == 'odbql_stmt*': return 'type(odbql_stmt), value'
160  if t == 'odbql_stmt**': return 'type(odbql_stmt)'
161  if t == 'odbql_value*': return 'type(odbql_value), value'
162  if t == 'odbql_value**': return 'type(odbql_value)'
163  if t == 'void(*)(void*)': return 'type(C_PTR), value'
164 
165  raise Exception("Don't know how to translate '" + t + "'")
166 
167 
169  #print 'translate_type_for_fortran_return:', t
170  if t == 'const char*': return 'character(len=*), intent(out)'
171  if t == 'const unsigned char*': return 'character(len=*), intent(out)' # TODO: think about it
172  if t == 'int': return 'integer(kind=C_INT)'
173  if t == 'double': return 'real(kind=C_DOUBLE)'
174  if t == 'odbql_value*': return 'type(odbql_value)'
175  if t == 'error_code_t': return 'integer(kind=C_INT), intent(out), optional'
176 
177  raise Exception("Don't know how to translate '" + t + "'")
178 
179 def fortranParamTypeDeclaration(p, translate_type = translate_type_for_binding):
180  typ, parameter_name = p
181  fortran_type = translate_type(typ)
182  return formatParameter(fortran_type, parameter_name)
183 
184 nl_indent = '\n '
185 
186 helper_functions = """
187 
188 !> Helper routine to convert C '\\0' terminated strings to Fortran strings
189 
190  subroutine C_to_F_string(c_string_pointer, out_string)
191 
192  use, intrinsic :: iso_c_binding, only: c_ptr,c_f_pointer,c_char,c_null_char
193 
194  type(c_ptr), intent(in) :: c_string_pointer
195  character(len=*), intent(out) :: out_string
196  character(kind=c_char), dimension(:), pointer :: char_array_pointer
197  integer :: i,length
198 
199  char_array_pointer => null()
200  call c_f_pointer(c_string_pointer,char_array_pointer,[255])
201 
202  if (.not.associated(char_array_pointer)) then
203  out_string = "NULL"
204  return
205  end if
206 
207  out_string = " "
208  do i = 1, len(out_string)
209  if (char_array_pointer(i) == c_null_char) exit
210  out_string(i:i) = char_array_pointer(i)
211  end do
212 
213  end subroutine
214 
215 
216 """
217 
218 status_handling_code = """
219 if (present(status)) then
220  status = rc ! let user handle the error
221 else
222  if (rc /= ODBQL_OK .and. rc /= ODBQL_ROW .and. rc /= ODBQL_DONE .and. rc /= ODBQL_METADATA_CHANGED) then
223  write (0,*) 'Error in %(function_name)s'
224  stop
225  end if
226 end if
227 """
228 status_handling_code = nl_indent.join (status_handling_code.splitlines())
229 
230 def parameter_type(p): return p[0]
231 def parameter_name(p): return p[1]
232 
234  if p[1] == 'iCol': return p[1] + '-1'
235  if p[0] == 'const char*': return p[1] + '_tmp'
236  if p[0] == 'odbql*': return p[1] + '%this'
237  if p[0] == 'odbql**': return p[1] + '%this'
238  if p[0] == 'odbql_stmt*': return p[1] + '%this'
239  if p[0] == 'odbql_stmt**': return p[1] + '%this'
240  if p[0] == 'odbql_value*': return p[1] + '%this'
241  if p[0] == 'odbql_value**': return p[1] + '%this'
242  return p[1]
243 
244 def generateWrapper(signature, comment, template):
245 
246  print 'generateWrapper:', signature, comment, template
247  global status_handling_code
248 
249  return_type, function_name, params = signature
250  procedure_keyword = 'function'
251 
252  output_parameter = function_name
253 
254  # filter out user supplied destructor functions (not supported now)
255  fortran_params = [p for p in params if not parameter_type(p) == 'void(*)(void*)']
256  fortran_params_excluding_return_parameter = fortran_params[:]
257 
258  binding_parameter_list = '(' + ','.join([p[1] for p in params]) + ')'
259  actual_binding_parameter_list = '(' + ','.join([actual_parameter(p) for p in params]) + ')'
260 
261  temporary_variables_declarations = nl_indent.join(
262  [formatParameter('character(len=len_trim('+p[1]+')+1)', p[1] + '_tmp') for p in params if p[0] == 'const char*']
263  + [formatParameter('type(C_PTR)', p[1]) for p in params if p[0] == 'void(*)(void*)'])
264  temporary_variables_assignments = nl_indent.join(p[1] + '_tmp = ' + p[1] + '//achar(0)'
265  for p in params if p[0] == 'const char*')
266  call_binding = function_name + '_c' + actual_binding_parameter_list
267 
268  error_handling = ''
269  return_value_tmp = None
270 
271  if return_type == 'odbql_value*':
272  return_value_tmp = output_parameter + "%this"
273 
274 
275  if return_type == 'error_code_t':
276  procedure_keyword = 'subroutine'
277  output_parameter = 'status'
278  return_value_tmp = 'rc'
279  temporary_variables_declarations = nl_indent.join(
280  [temporary_variables_declarations,
281  formatParameter('integer(kind=c_int)', 'rc')])
282  fortran_params.append( (return_type, output_parameter) )
283  error_handling = status_handling_code % locals()
284 
285  if return_type == 'const char*' or return_type == 'const unsigned char*':
286  procedure_keyword = 'subroutine'
287  output_parameter = 'return_value'
288  fortran_params.append( (return_type, output_parameter) )
289 
290  # For C to F string, we call a subroutine rather than make an assignment
291  return_value_assignment = "call C_to_F_string(%s, %s)" % (call_binding, (return_value_tmp or output_parameter))
292 
293  else:
294 
295  # Normal assignment
296  return_value_assignment = (return_value_tmp or output_parameter) + ' = ' + call_binding
297 
298  binding_parameters_declarations = nl_indent.join([fortranParamTypeDeclaration(p) for p in params])
299  binding_return_type_declaration = formatParameter(translate_type_for_binding_return(return_type), function_name + '_c')
300 
301  fortran_parameter_list = '(' + ','.join([p[1] for p in fortran_params]) + ')'
302  fortran_parameters_declarations = nl_indent.join(
303  [fortranParamTypeDeclaration(p, translate_type = translate_type_for_fortran)
304  for p in fortran_params_excluding_return_parameter])
305  fortran_return_type_declaration = formatParameter(translate_type_for_fortran_return(return_type), output_parameter)
306 
307  use_intrinsic = formatParameter('use, intrinsic', 'iso_c_binding')
308 
309  return template % locals()
310 
311 def generateWrappers(decls, header, footer, template):
312  s = header
313  for original, ast in [parseDeclaration(decl) for decl in decls]:
314  s += generateWrapper(ast, original, template) + '\n'
315 
316  s += footer
317  return s
318 
319 
320 
321 def generateBindings(source_cc = '../odb_api/odbql.cc',
322  source_h = '../odb_api/odbql.h',
323  binding_f90 = '../fortran/odbql_binding.f90',
324  wrappers_f90 = '../fortran/odbql_wrappers.f90',
325  constants_f90 = '../fortran/odbql_constants.f90'):
326 
327  decls = declarations(source_cc)
328  defs = constants(source_h)
329  with open(constants_f90, 'w') as f:
330  f.write(generateParameters(defs))
331 
332  ########## odbql_binding.f90: declarations of ISO C bindings for the new ODB API
333  with open(binding_f90, 'w') as f:
334  f.write(generateWrappers(decls, header = """
335 
336 !!!!! THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY !!!!!
337 !! See fwrap.py
338 
339 module odbql_binding
340  use iso_c_binding
341  use, intrinsic :: iso_c_binding
342  implicit none
343 interface
344 """, footer = """
345 end interface
346 end module odbql_binding
347 """, template = """
348 !> %(comment)s
349 
350  function %(function_name)s_c %(binding_parameter_list)s bind(C, name="%(function_name)s")
351  %(use_intrinsic)s
352  %(binding_parameters_declarations)s
353  %(binding_return_type_declaration)s
354  end function %(function_name)s_c
355 
356 
357  """))
358 
359  ############ odbql_wrappers.f90 The Fortran user interface
360 
361  with open(wrappers_f90, 'w') as f:
362  f.write(generateWrappers(decls, header = """
363 
364 !!!!! THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT MANUALLY !!!!!
365 !! See fwrap.py
366 
367 module odbql_wrappers
368  use odbql_binding
369  use odbql_constants
370  implicit none
371 
372  type odbql
373  type(c_ptr) :: this
374  end type
375 
376  type odbql_stmt
377  type(c_ptr) :: this
378  end type
379 
380  type odbql_value
381  type(c_ptr) :: this
382  end type
383 
384 contains
385 
386 %(helper_functions)s
387 
388 """ % globals(), footer = """
389 end module odbql_wrappers
390 """, template = """
391 !> %(comment)s
392 
393  %(procedure_keyword)s %(function_name)s %(fortran_parameter_list)s
394  use odbql_binding
395  %(use_intrinsic)s
396  %(fortran_parameters_declarations)s
397  %(fortran_return_type_declaration)s
398 
399  %(temporary_variables_declarations)s
400 
401  %(temporary_variables_assignments)s
402  %(return_value_assignment)s
403  %(error_handling)s
404 
405  end %(procedure_keyword)s %(function_name)s
406 
407  """
408  ))
409 
410 if __name__ == '__main__': generateBindings()
def fortranParamTypeDeclaration(p, translate_type=translate_type_for_binding)
Definition: fwrap.py:179
def normalize_type(t)
Definition: fwrap.py:89
def declarations(source_cc='odbql.cc')
Definition: fwrap.py:13
def constants(source_h='odbql.h')
Definition: fwrap.py:23
def translate_value_and_comment(value_and_possibly_comment)
Definition: fwrap.py:40
def generateParameter(define)
Definition: fwrap.py:59
def evaluated_expression(s)
Definition: fwrap.py:32
def generateWrappers(decls, header, footer, template)
Definition: fwrap.py:311
def parseParam(p)
Definition: fwrap.py:91
def translate_type_for_binding(t)
Definition: fwrap.py:122
def actual_parameter(p)
Definition: fwrap.py:233
def translate_type_for_fortran(t)
Definition: fwrap.py:151
def parameter_name(p)
Definition: fwrap.py:231
def parameter_type(p)
Definition: fwrap.py:230
def generateParameters(defs)
Definition: fwrap.py:79
def formatParameter(typ, name)
Definition: fwrap.py:8
def translate_type_for_binding_return(t)
Definition: fwrap.py:139
def generateBindings(source_cc='../odb_api/odbql.cc', source_h='../odb_api/odbql.h', binding_f90='../fortran/odbql_binding.f90', wrappers_f90='../fortran/odbql_wrappers.f90', constants_f90='../fortran/odbql_constants.f90')
Definition: fwrap.py:325
def generateWrapper(signature, comment, template)
Definition: fwrap.py:244
def parseDeclaration(decl)
Definition: fwrap.py:108
def translate_type_for_fortran_return(t)
Definition: fwrap.py:168