Merge pull request #113 from gitter-badger/gitter-badge
[sddekit.git] / src / api.py
blob6a25bbc7dae83c3dfee052f962a4f60441462082
1 # copyright 2016 Apache 2 sddekit authors
3 """
4 This module helps with certain development tasks related to
5 SDDEKit's API, such as generating binding code for higher-level
6 languages.
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 (
18 """
20 import io
21 import os
22 import sys
23 import pycparser
24 from os.path import exists, join, abspath
25 import subprocess
26 import ctypes
27 import pycparser
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]")
42 else:
43 sys.argv.remove('-y')
44 yn = 'y'
45 if yn in ('y', 'Y'):
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
51 else:
52 print ("Please download pycparser source from "
53 "https://github.com/eliben/pycparser "
54 "and provide path as environment variable PYCPARSER=/path/to.")
55 libc = None
56 if 'PYCPARSER' in os.environ:
57 libc = abspath(join(os.environ['PYCPARSER'], 'utils', 'fake_libc_include'))
58 import pdb; pdb.set_trace()
59 return libc
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()
65 if libc:
66 cmd.append('-I%s' % (libc, ))
67 cmd += ['-E', join(UP, 'src', header)]
68 return cmd
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:
76 header = fd.read()
77 else:
78 cmd = cmd or build_cpp_cmd()
79 print (cmd)
80 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
81 proc.wait()
82 header = proc.stdout.read().decode('utf-8').replace('\r', '')
83 with open('sddekit_simple_pp.h', 'w') as fd:
84 fd.write(header)
85 return header
87 def header_ast(src=None, ppfname=PPFNAME):
88 src = src or preprocessed_header()
89 cp = pycparser.CParser()
90 ast = cp.parse(src, ppfname)
91 return ast
93 # preprocess simple header }}}
95 # ast vistors {{{
97 class App(c_ast.NodeVisitor):
98 """
99 Subclass of NodeVisitor with simple class method to build instance,
100 visit a given node, and return instance.
104 @classmethod
105 def apply(cls, node):
106 obj = cls()
107 obj.visit(node)
108 return obj
111 class TypeInfo(App):
112 "Visits a type to extract it's pointer level, declaration name and type."
114 def __init__(self):
115 self.ptr = 0
116 self.name = ''
117 self.typenames = ''
119 def visit_PtrDecl(self, node):
120 self.ptr += 1
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)
131 class FuncInfo(App):
132 "Visits a function declaration to extract name, result and argument types."
134 def __init__(self):
135 self.restype = None
136 self.name = ''
137 self.argtypes = []
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.
155 def __init__(self):
156 self.struct = 0
157 self.ptr = 0
158 self.typedef = False
159 self.struct_name = []
161 def visit_Typedef(self, node):
162 self.typedef = True
163 self.generic_visit(node)
164 self.typedef = False
166 def visit_PtrDecl(self, node):
167 self.ptr += 1
168 self.generic_visit(node)
169 self.ptr -= 1
171 def visit_Struct(self, node):
172 self.struct += 1
173 self.struct_name.append(node.name)
174 if not self.typedef:
175 self.generic_visit(node)
176 self.struct_name.pop()
177 self.struct -= 1
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"
192 def __init__(self):
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))
204 data = {
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))
216 # ast visitors }}}
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.
223 Parameters
224 ----------
225 path : string
226 Path to write files to.
227 filename : string
228 Name to give header and source files.
231 import api
232 path = path or './'
233 hpath = join(path, filename + '.h')
234 cpath = hpath[:-1] + 'c'
235 if exists(hpath) and exists(cpath) and redo is False:
236 return
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
241 ''')
242 for line in wrappers.header_lines:
243 fd.write(line)
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
249 ''')
250 for line in wrappers.source_lines:
251 fd.write(line)
253 if __name__ == '__main__':
254 allcmds = ['generate_fn_ptr_field_wrapper_files', 'preprocessed_header']
255 cmds = sys.argv[1:]
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,))
259 else:
260 for cmd in sys.argv[1:]:
261 eval(cmd)(redo=True)
263 # vim: sw=4 sts=4 et foldmethod=marker