1 # copyright 2016 Apache 2 sddekit authors
4 This module helps with certain development tasks related to
5 SDDEKit's API, such as generating binding code for higher-level
8 Currently the supported workflow is as follows
10 - generate a preprocessed_header()
11 - generate_fn_ptr_field_wrapper_files()
13 though the intention is to support
15 - generation of ctypes Structure subclasses and suitable function signatures
16 - generation of default Python classes to wrap structures (
24 from os
.path
import exists
, join
, abspath
28 from pycparser
import c_ast
30 HERE
= os
.path
.dirname(os
.path
.abspath(__file__
))
31 UP
= os
.path
.dirname(HERE
)
33 # preprocess simple header {{{
35 def get_fake_libc_include_path():
36 "Find fake libc or download it from GitHub."
37 if exists(join(HERE
, 'pycparser-master')):
38 os
.environ
['PYCPARSER'] = join(HERE
, 'pycparser-master')
39 if 'PYCPARSER' not in os
.environ
:
40 if '-y' not in sys
.argv
:
41 yn
= raw_input("fake libc includes required from pycparser, download? [y/N]")
46 import urllib
, zipfile
47 url
= 'https://github.com/eliben/pycparser/archive/master.zip'
48 tmp
, _
= urllib
.urlretrieve(url
)
49 zipfile
.ZipFile(tmp
).extractall()
50 os
.environ
['PYCPARSER'] = HERE
52 print ("Please download pycparser source from "
53 "https://github.com/eliben/pycparser "
54 "and provide path as environment variable PYCPARSER=/path/to.")
56 if 'PYCPARSER' in os
.environ
:
57 libc
= abspath(join(os
.environ
['PYCPARSER'], 'utils', 'fake_libc_include'))
58 import pdb
; pdb
.set_trace()
61 def build_cpp_cmd(header
='sddekit_simple.h', cc
='gcc', libc
=None):
62 "Build command list for preprocessing the header file."
63 cmd
= [os
.environ
.get('CC', cc
), '-nostdinc']
64 libc
= libc
or get_fake_libc_include_path()
66 cmd
.append('-I%s' % (libc
, ))
67 cmd
+= ['-E', join(UP
, 'src', header
)]
70 PPFNAME
= 'sddekit_simple_pp.h'
72 def preprocessed_header(cmd
=None, redo
=False, ppfname
=PPFNAME
):
73 "Preprocess header and return resulting string."
74 if exists(join(HERE
, ppfname
)) and not redo
:
75 with
open(ppfname
, 'r') as fd
:
78 cmd
= cmd
or build_cpp_cmd()
80 proc
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
)
82 header
= proc
.stdout
.read().decode('utf-8').replace('\r', '')
83 with
open('sddekit_simple_pp.h', 'w') as fd
:
87 def header_ast(src
=None, ppfname
=PPFNAME
):
88 src
= src
or preprocessed_header()
89 cp
= pycparser
.CParser()
90 ast
= cp
.parse(src
, ppfname
)
93 # preprocess simple header }}}
97 class App(c_ast
.NodeVisitor
):
99 Subclass of NodeVisitor with simple class method to build instance,
100 visit a given node, and return instance.
105 def apply(cls
, node
):
112 "Visits a type to extract it's pointer level, declaration name and type."
119 def visit_PtrDecl(self
, node
):
121 self
.generic_visit(node
)
123 def visit_TypeDecl(self
, node
):
124 self
.name
= node
.declname
125 self
.generic_visit(node
)
127 def visit_IdentifierType(self
, node
):
128 self
.typenames
= ' '.join(node
.names
)
132 "Visits a function declaration to extract name, result and argument types."
139 def visit_FuncDecl(self
, node
):
140 self
.restype
= TypeInfo
.apply(node
.type)
141 self
.name
= self
.restype
.name
142 self
.argtypes
= [TypeInfo
.apply(child
) for _
, child
in node
.args
.children()]
145 class VisitStructFnPtrFields(App
):
147 Visits function pointers which are fields of structs.
149 This will not do anything by itself, but should probably be subclassed
150 with a visit_FuncDecl method to do something with the resulting
151 function pointer declarations.
159 self
.struct_name
= []
161 def visit_Typedef(self
, node
):
163 self
.generic_visit(node
)
166 def visit_PtrDecl(self
, node
):
168 self
.generic_visit(node
)
171 def visit_Struct(self
, node
):
173 self
.struct_name
.append(node
.name
)
175 self
.generic_visit(node
)
176 self
.struct_name
.pop()
180 class GenFnPtrFieldWrappers(VisitStructFnPtrFields
):
182 Visits function pointer in struct declarations to extract
183 type and name information in order to generate header and implementation
184 of regular functions which wrap the function pointer.
188 _h_template
= "{restype} {struct_name}_{field_name}({arg_types});\n"
190 _c_template
= "{restype} {struct_name}_{field_name}({arg_type_names})\n{{\n\treturn {first_arg}->{field_name}({arg_names});\n}}\n\n"
193 super(GenFnPtrFieldWrappers
, self
).__init
__()
194 self
.header_lines
= []
195 self
.source_lines
= []
197 def visit_FuncDecl(self
, node
):
198 if self
.struct
and self
.ptr
:
199 fi
= FuncInfo
.apply(node
)
200 arg_names
, arg_types
= [], []
201 for i
, arg
in enumerate(fi
.argtypes
):
202 arg_names
.append('a%d' % (i
, ))
203 arg_types
.append(arg
.typenames
+ ('*' * arg
.ptr
))
205 'restype': fi
.restype
.typenames
+ ('*' * fi
.restype
.ptr
),
206 'struct_name': self
.struct_name
[-1],
207 'field_name': fi
.name
,
208 'arg_types': ', '.join(arg_types
),
209 'arg_names': ', '.join(arg_names
),
210 'arg_type_names': ', '.join('%s %s' % (t
, n
) for n
, t
in zip(arg_names
, arg_types
)),
211 'first_arg': arg_names
[0],
213 self
.header_lines
.append(self
._h
_template
.format(**data
))
214 self
.source_lines
.append(self
._c
_template
.format(**data
))
218 def generate_fn_ptr_field_wrapper_files(path
=None, redo
=False, filename
='fn_ptr_field_wrappers'):
220 Generates a header and source file containing regular functions wrapping
221 all struct fields which are function pointers.
226 Path to write files to.
228 Name to give header and source files.
233 hpath
= join(path
, filename
+ '.h')
234 cpath
= hpath
[:-1] + 'c'
235 if exists(hpath
) and exists(cpath
) and redo
is False:
237 wrappers
= GenFnPtrFieldWrappers
.apply(api
.header_ast())
238 with
open(hpath
, 'w') as fd
:
239 fd
.write('''/* copyright 2016 Apache 2 sddekit authors */\n
240 /* This file was automatically generated based on src/sddekit_simple.h */\n
242 for line
in wrappers
.header_lines
:
245 with
open(cpath
, 'w') as fd
:
246 fd
.write('''/* copyright 2016 Apache 2 sddekit authors */\n
247 /* This file was automatically generated based on src/sddekit_simple.h */\n
248 #include "sddekit.h"\n
250 for line
in wrappers
.source_lines
:
253 if __name__
== '__main__':
254 allcmds
= ['generate_fn_ptr_field_wrapper_files', 'preprocessed_header']
256 if len(cmds
) == 0 or any(cmd
not in allcmds
for cmd
in cmds
):
257 print ('usage: python api.py <cmd> [<cmd> ...]')
258 print ('where <cmd> is one of %r' % (allcmds
,))
260 for cmd
in sys
.argv
[1:]:
263 # vim: sw=4 sts=4 et foldmethod=marker