OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 """ Generator for C++ style thunks """ | |
7 | |
8 import glob | |
9 import os | |
10 import re | |
11 import sys | |
12 | |
13 from idl_log import ErrOut, InfoOut, WarnOut | |
14 from idl_node import IDLAttribute, IDLNode | |
15 from idl_ast import IDLAst | |
16 from idl_option import GetOption, Option, ParseOptions | |
17 from idl_outfile import IDLOutFile | |
18 from idl_parser import ParseFiles | |
19 from idl_c_proto import CGen, GetNodeComments, CommentLines, Comment | |
20 from idl_generator import Generator, GeneratorByFile | |
21 | |
22 Option('thunkroot', 'Base directory of output', | |
23 default=os.path.join('..', 'thunk')) | |
24 | |
25 | |
26 class TGenError(Exception): | |
27 def __init__(self, msg): | |
28 self.value = msg | |
29 | |
30 def __str__(self): | |
31 return repr(self.value) | |
32 | |
33 | |
34 def _GetBaseFileName(filenode): | |
35 """Returns the base name for output files, given the filenode. | |
36 | |
37 Examples: | |
38 'dev/ppb_find_dev.h' -> 'ppb_find' | |
39 'trusted/ppb_buffer_trusted.h' -> 'ppb_buffer_trusted' | |
40 """ | |
41 path, name = os.path.split(filenode.GetProperty('NAME')) | |
42 name = os.path.splitext(name)[0] | |
43 if name.endswith('_dev'): | |
44 # Clip off _dev suffix. | |
45 name = name[:-len('_dev')] | |
46 return name | |
47 | |
48 | |
49 def _GetHeaderFileName(filenode): | |
50 """Returns the name for the header for this file.""" | |
51 path, name = os.path.split(filenode.GetProperty('NAME')) | |
52 name = os.path.splitext(name)[0] | |
53 if path: | |
54 header = "ppapi/c/%s/%s.h" % (path, name) | |
55 else: | |
56 header = "ppapi/c/%s.h" % name | |
57 return header | |
58 | |
59 | |
60 def _GetThunkFileName(filenode, relpath): | |
61 """Returns the thunk file name.""" | |
62 path = os.path.split(filenode.GetProperty('NAME'))[0] | |
63 name = _GetBaseFileName(filenode) | |
64 # We don't reattach the path for thunk. | |
65 if relpath: name = os.path.join(relpath, name) | |
66 name = '%s%s' % (name, '_thunk.cc') | |
67 return name | |
68 | |
69 | |
70 def _MakeEnterLine(filenode, interface, arg, handle_errors, callback): | |
71 """Returns an EnterInstance/EnterResource string for a function.""" | |
72 if arg[0] == 'PP_Instance': | |
73 if callback is None: | |
74 return 'EnterInstance enter(%s);' % arg[1] | |
75 else: | |
76 return 'EnterInstance enter(%s, %s);' % (arg[1], callback) | |
77 elif arg[0] == 'PP_Resource': | |
78 api_name = interface.GetName() | |
79 if api_name.endswith('_Dev'): | |
80 api_name = api_name[:-len('_Dev')] | |
81 api_name += '_API' | |
82 | |
83 enter_type = 'EnterResource<%s>' % api_name | |
84 if callback is None: | |
85 return '%s enter(%s, %s);' % (enter_type, arg[1], | |
86 str(handle_errors).lower()) | |
87 else: | |
88 return '%s enter(%s, %s, %s);' % (enter_type, arg[1], | |
89 callback, | |
90 str(handle_errors).lower()) | |
91 else: | |
92 raise TGenError("Unknown type for _MakeEnterLine: %s" % arg[0]) | |
93 | |
94 | |
95 def _GetShortName(interface, filter_suffixes): | |
96 """Return a shorter interface name that matches Is* and Create* functions.""" | |
97 parts = interface.GetName().split('_')[1:] | |
98 tail = parts[len(parts) - 1] | |
99 if tail in filter_suffixes: | |
100 parts = parts[:-1] | |
101 return ''.join(parts) | |
102 | |
103 | |
104 def _IsTypeCheck(interface, node): | |
105 """Returns true if node represents a type-checking function.""" | |
106 return node.GetName() == 'Is%s' % _GetShortName(interface, ['Dev', 'Private']) | |
107 | |
108 | |
109 def _GetCreateFuncName(interface): | |
110 """Returns the creation function name for an interface.""" | |
111 return 'Create%s' % _GetShortName(interface, ['Dev']) | |
112 | |
113 | |
114 def _GetDefaultFailureValue(t): | |
115 """Returns the default failure value for a given type. | |
116 | |
117 Returns None if no default failure value exists for the type. | |
118 """ | |
119 values = { | |
120 'PP_Bool': 'PP_FALSE', | |
121 'PP_Resource': '0', | |
122 'struct PP_Var': 'PP_MakeUndefined()', | |
123 'int32_t': 'enter.retval()', | |
124 'uint16_t': '0', | |
125 'uint32_t': '0', | |
126 'uint64_t': '0', | |
127 } | |
128 if t in values: | |
129 return values[t] | |
130 return None | |
131 | |
132 | |
133 def _MakeCreateMemberBody(interface, member, args): | |
134 """Returns the body of a Create() function. | |
135 | |
136 Args: | |
137 interface - IDLNode for the interface | |
138 member - IDLNode for member function | |
139 args - List of arguments for the Create() function | |
140 """ | |
141 if args[0][0] == 'PP_Resource': | |
142 body = ' Resource* object =\n' | |
143 body += ' PpapiGlobals::Get()->GetResourceTracker()->' | |
144 body += 'GetResource(%s);\n' % args[0][1] | |
145 body += ' if (!object)\n' | |
146 body += ' return 0;\n' | |
147 body += ' EnterResourceCreation enter(object->pp_instance());\n' | |
148 elif args[0][0] == 'PP_Instance': | |
149 body = ' EnterResourceCreation enter(%s);\n' % args[0][1] | |
150 else: | |
151 raise TGenError('Unknown arg type for Create(): %s' % args[0][0]) | |
152 | |
153 body += ' if (enter.failed())\n' | |
154 body += ' return 0;\n' | |
155 arg_list = ', '.join([a[1] for a in args]) | |
156 if member.GetProperty('create_func'): | |
157 create_func = member.GetProperty('create_func') | |
158 else: | |
159 create_func = _GetCreateFuncName(interface) | |
160 body += ' return enter.functions()->%s(%s);' % (create_func, | |
161 arg_list) | |
162 return body | |
163 | |
164 | |
165 def _MakeNormalMemberBody(filenode, node, member, rtype, args): | |
166 """Returns the body of a typical function. | |
167 | |
168 Args: | |
169 filenode - IDLNode for the file | |
170 node - IDLNode for the interface | |
171 member - IDLNode for the member function | |
172 rtype - Return type for the member function | |
173 args - List of 4-tuple arguments for the member function | |
174 """ | |
175 is_callback_func = args[len(args) - 1][0] == 'struct PP_CompletionCallback' | |
176 | |
177 if is_callback_func: | |
178 call_args = args[:-1] + [('', 'enter.callback()', '', '')] | |
179 else: | |
180 call_args = args | |
181 | |
182 if args[0][0] == 'PP_Instance': | |
183 call_arglist = ', '.join(a[1] for a in call_args) | |
184 function_container = 'functions' | |
185 else: | |
186 call_arglist = ', '.join(a[1] for a in call_args[1:]) | |
187 function_container = 'object' | |
188 | |
189 invocation = 'enter.%s()->%s(%s)' % (function_container, | |
190 member.GetName(), | |
191 call_arglist) | |
192 | |
193 handle_errors = not (member.GetProperty('report_errors') == 'False') | |
194 if is_callback_func: | |
195 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors, | |
196 args[len(args) - 1][1]) | |
197 body += ' if (enter.failed())\n' | |
198 value = member.GetProperty('on_failure') | |
199 if value is None: | |
200 value = 'enter.retval()' | |
201 body += ' return %s;\n' % value | |
202 body += ' return enter.SetResult(%s);\n' % invocation | |
203 elif rtype == 'void': | |
204 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors, | |
205 None) | |
206 body += ' if (enter.succeeded())\n' | |
207 body += ' %s;' % invocation | |
208 else: | |
209 value = member.GetProperty('on_failure') | |
210 if value is None: | |
211 value = _GetDefaultFailureValue(rtype) | |
212 if value is None: | |
213 raise TGenError('No default value for rtype %s' % rtype) | |
214 | |
215 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors, | |
216 None) | |
217 body += ' if (enter.failed())\n' | |
218 body += ' return %s;\n' % value | |
219 body += ' return %s;' % invocation | |
220 return body | |
221 | |
222 | |
223 def DefineMember(filenode, node, member, release, include_version): | |
224 """Returns a definition for a member function of an interface. | |
225 | |
226 Args: | |
227 filenode - IDLNode for the file | |
228 node - IDLNode for the interface | |
229 member - IDLNode for the member function | |
230 release - release to generate | |
231 include_version - include the version in emitted function name. | |
232 Returns: | |
233 A string with the member definition. | |
234 """ | |
235 cgen = CGen() | |
236 rtype, name, arrays, args = cgen.GetComponents(member, release, 'return') | |
237 | |
238 if _IsTypeCheck(node, member): | |
239 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], False, None) | |
240 body += ' return PP_FromBool(enter.succeeded());' | |
241 elif member.GetName() == 'Create': | |
242 body = _MakeCreateMemberBody(node, member, args) | |
243 else: | |
244 body = _MakeNormalMemberBody(filenode, node, member, rtype, args) | |
245 | |
246 signature = cgen.GetSignature(member, release, 'return', func_as_ptr=False, | |
247 include_version=include_version) | |
248 member_code = '%s {\n%s\n}' % (signature, body) | |
249 return cgen.Indent(member_code, tabs=0) | |
250 | |
251 | |
252 class TGen(GeneratorByFile): | |
253 def __init__(self): | |
254 Generator.__init__(self, 'Thunk', 'tgen', 'Generate the C++ thunk.') | |
255 | |
256 def GenerateFile(self, filenode, releases, options): | |
257 savename = _GetThunkFileName(filenode, GetOption('thunkroot')) | |
258 my_min, my_max = filenode.GetMinMax(releases) | |
259 if my_min > releases[-1] or my_max < releases[0]: | |
260 if os.path.isfile(savename): | |
261 print "Removing stale %s for this range." % filenode.GetName() | |
262 os.remove(os.path.realpath(savename)) | |
263 return False | |
264 do_generate = filenode.GetProperty('generate_thunk') | |
265 if not do_generate: | |
266 return False | |
267 | |
268 thunk_out = IDLOutFile(savename) | |
269 self.GenerateHead(thunk_out, filenode, releases, options) | |
270 self.GenerateBody(thunk_out, filenode, releases, options) | |
271 self.GenerateTail(thunk_out, filenode, releases, options) | |
272 return thunk_out.Close() | |
273 | |
274 def GenerateHead(self, out, filenode, releases, options): | |
275 __pychecker__ = 'unusednames=options' | |
276 cgen = CGen() | |
277 | |
278 cright_node = filenode.GetChildren()[0] | |
279 assert(cright_node.IsA('Copyright')) | |
280 out.Write('%s\n' % cgen.Copyright(cright_node, True)) | |
dmichael (off chromium)
2012/11/16 20:37:02
suggestion: "cpp_style=True", just so it's more se
teravest
2012/11/16 20:39:18
Done.
| |
281 | |
282 # Wrap the From ... modified ... comment if it would be >80 characters. | |
283 from_text = 'From %s' % ( | |
284 filenode.GetProperty('NAME').replace(os.sep,'/')) | |
285 modified_text = 'modified %s.' % ( | |
286 filenode.GetProperty('DATETIME')) | |
287 if len(from_text) + len(modified_text) < 74: | |
288 out.Write('// %s %s\n\n' % (from_text, modified_text)) | |
289 else: | |
290 out.Write('// %s,\n// %s\n\n' % (from_text, modified_text)) | |
291 | |
292 | |
293 # TODO(teravest): Don't emit includes we don't need. | |
294 includes = ['ppapi/c/pp_errors.h', | |
295 'ppapi/shared_impl/tracked_callback.h', | |
296 'ppapi/thunk/enter.h', | |
297 'ppapi/thunk/ppb_instance_api.h', | |
298 'ppapi/thunk/resource_creation_api.h', | |
299 'ppapi/thunk/thunk.h'] | |
300 includes.append(_GetHeaderFileName(filenode)) | |
301 includes.append('ppapi/thunk/%s_api.h' % _GetBaseFileName(filenode)) | |
302 for include in sorted(includes): | |
303 out.Write('#include "%s"\n' % include) | |
304 out.Write('\n') | |
305 out.Write('namespace ppapi {\n') | |
306 out.Write('namespace thunk {\n') | |
307 out.Write('\n') | |
308 out.Write('namespace {\n') | |
309 out.Write('\n') | |
310 | |
311 def GenerateBody(self, out, filenode, releases, options): | |
312 __pychecker__ = 'unusednames=options' | |
313 for node in filenode.GetListOf('Interface'): | |
314 # Skip if this node is not in this release | |
315 if not node.InReleases(releases): | |
316 print "Skipping %s" % node | |
317 continue | |
318 | |
319 # Generate Member functions | |
320 if node.IsA('Interface'): | |
321 members = [] | |
322 for child in node.GetListOf('Member'): | |
323 build_list = child.GetUniqueReleases(releases) | |
324 # We have to filter out releases this node isn't in. | |
325 build_list = filter(lambda r: child.InReleases([r]), build_list) | |
326 if len(build_list) == 0: | |
327 continue | |
328 release = build_list[-1] # Pick the newest release. | |
329 member = DefineMember(filenode, node, child, release, False) | |
330 if not member: | |
331 continue | |
332 members.append(member) | |
333 for build in build_list[:-1]: | |
334 member = DefineMember(filenode, node, child, build, True) | |
335 if not member: | |
336 continue | |
337 members.append(member) | |
338 out.Write('\n\n'.join(members)) | |
339 | |
340 def GenerateTail(self, out, filenode, releases, options): | |
341 __pychecker__ = 'unusednames=options' | |
342 cgen = CGen() | |
343 | |
344 version_list = [] | |
345 out.Write('\n\n') | |
346 for node in filenode.GetListOf('Interface'): | |
347 build_list = node.GetUniqueReleases(releases) | |
348 for build in build_list: | |
349 version = node.GetVersion(build).replace('.', '_') | |
350 thunk_name = 'g_' + node.GetName().lower() + '_thunk_' + \ | |
351 version | |
352 thunk_type = '_'.join((node.GetName(), version)) | |
353 version_list.append((thunk_type, thunk_name)) | |
354 | |
355 out.Write('const %s %s = {\n' % (thunk_type, thunk_name)) | |
356 for child in node.GetListOf('Member'): | |
357 rtype, name, arrays, args = cgen.GetComponents( | |
358 child, build, 'return') | |
359 if child.InReleases([build]): # TEST | |
360 out.Write(' &%s,\n' % name) | |
361 out.Write('};\n\n') | |
362 | |
363 out.Write('} // namespace\n') | |
364 out.Write('\n') | |
365 for thunk_type, thunk_name in version_list: | |
366 thunk_decl = 'const %s* Get%s_Thunk() {\n' % (thunk_type, thunk_type) | |
367 if len(thunk_decl) > 80: | |
368 thunk_decl = 'const %s*\n Get%s_Thunk() {\n' % (thunk_type, | |
369 thunk_type) | |
370 out.Write(thunk_decl) | |
371 out.Write(' return &%s;\n' % thunk_name) | |
372 out.Write('}\n') | |
373 out.Write('\n') | |
374 out.Write('} // namespace thunk\n') | |
375 out.Write('} // namespace ppapi\n') | |
376 | |
377 | |
378 tgen = TGen() | |
379 | |
380 | |
381 def Main(args): | |
382 # Default invocation will verify the golden files are unchanged. | |
383 failed = 0 | |
384 if not args: | |
385 args = ['--wnone', '--diff', '--test', '--thunkroot=.'] | |
386 | |
387 ParseOptions(args) | |
388 | |
389 idldir = os.path.split(sys.argv[0])[0] | |
390 idldir = os.path.join(idldir, 'test_thunk', '*.idl') | |
391 filenames = glob.glob(idldir) | |
392 ast = ParseFiles(filenames) | |
393 if tgen.GenerateRange(ast, ['M13', 'M14'], {}): | |
394 print "Golden file for M13-M14 failed." | |
395 failed = 1 | |
396 else: | |
397 print "Golden file for M13-M14 passed." | |
398 | |
399 return failed | |
400 | |
401 | |
402 if __name__ == '__main__': | |
403 sys.exit(Main(sys.argv[1:])) | |
OLD | NEW |